npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@quran.ws/tajwid

v0.1.0

Published

Compiles the tajweed rule corpus and reports where each rule applies in Quranic text, as code-point offsets.

Readme

@quran.ws/tajwid

Compiles the tajweed rule corpus and reports where each rule applies in Quranic text.

npm install @quran.ws/tajwid @quran.ws/tajwid-rules
import corpus from '@quran.ws/tajwid-rules'
import { Tajweed, sliceSpan } from '@quran.ws/tajwid'

const tajweed = new Tajweed(corpus)

for (const span of tajweed.analyze(ayahText)) {
  console.log(span.hukumId, sliceSpan(ayahText, span))
}

It returns positions, not markup

interface Span {
  start: number      // code-point position in the text YOU passed in
  end: number        // half-open
  ruleId: string     // 'madd-muttasil.1'
  hukumId: string    // 'madd-muttasil'
  categoryId: string // 'madd-far-hamz'
  topicId: string    // 'madd'
}

For example, analysing ayah 112:1 (قُلۡ هُوَ ٱللَّهُ أَحَدٌ) returns four spans, one of which is:

{ start: 22, end: 24, ruleId: 'qalqalah-kubra.1', hukumId: 'qalqalah-kubra', ... }
// sliceSpan(text, span) === 'دٌ'  — the final د of أَحَدٌ, with its tanween

Colours, HTML, ANSI, SVG overlays and mushaf-page coordinates all build on top of this, and each platform needs a different one. Returning positions keeps the engine useful for all of them; returning HTML would serve only one.

start and end count code points, not bytes and not UTF-16 units, so the same numbers mean the same thing in PHP, Python and Swift. All Arabic and all Quranic annotation marks sit in the Basic Multilingual Plane, so for Quranic text the positions also work directly as JavaScript string indices — sliceSpan handles the general case anyway.

Cutting the text is where Arabic breaks

Colouring a span means giving it its own element, and a browser shapes each element on its own — so the word comes apart at every change of colour, and a cut taken at the raw offset can land between a letter and the shadda written on it. The text is unaltered either way, nothing is raised, and it reads as a font problem. Two exports carry the fix:

import { bridgeJoins, clusterEnd } from '@quran.ws/tajwid'

const end = clusterEnd(text, span.end)          // past the marks on that letter
const chunks = bridgeJoins([before, span, after]) // joiners across each cut

toHtml and @quran.ws/tajwid-react already use both. Write your own renderer — an SVG overlay, a canvas, a native view — and you need them: docs/rendering.md says why, including why the joiner has to be conditional.

Spans overlap, on purpose

One letter can demonstrate more than one ruling, and analyze reports all of them. Choosing what to draw is a display decision, not an engine decision:

import { resolveOverlaps } from '@quran.ws/tajwid'

resolveOverlaps(spans) // earliest wins, longest wins on a tie

An app that teaches tajweed probably wants to keep the overlaps rather than flatten them to one layer.

Choosing rules

new Tajweed(corpus, { only: ['madd'] })                    // a topic
new Tajweed(corpus, { only: ['madd-muttasil'] })           // a hukum
new Tajweed(corpus, { only: ['madd-muttasil.1'] })         // one rule
new Tajweed(corpus, { school: 'ibn-al-jazari' })           // pick a school
new Tajweed(corpus, { includeDisabled: true })             // for working ON the corpus

school matters where the corpus models two authorities side by side — Ibn al-Jazarī counts five ranks of tafkheem, Ibn al-Ṭaḥḥān counts three, and both are present. Ahkam with no school attribution are always kept.

Your text is never changed

Matching runs on an internal normalised copy. Nothing in the pipeline applies Unicode normalisation, strips diacritics, or drops waqf marks, and analyze never returns a modified copy of your string — only positions into it.

Run it at build time

The Quran is a fixed text, so for production the expected setup is: annotate every ayah once and ship the positions, instead of compiling every pattern on every request. Record which text edition you computed against — the same rule lands on different positions in different editions of the Uthmani script.

Licence

MIT. The rule corpus is a separate work under CC BY 4.0.