Word identity

Text, page artwork, tajwīd spans and recitation timings are separate datasets built by separate pipelines. The only reason they can talk to each other is that they agree on how to name a word. This page is about that agreement — what the keys are, which dataset carries which, and what each one does and does not survive.

The obvious key, the word itself, does not work. Here is the first word of the ayah Ḥafṣ numbers 1:4, as each of our seven editions prints it:

RiwayahAs printedCode points
Ḥafṣ
hafs
مَٰلِكِU+0645 U+064E U+0670 U+0644 U+0650 U+0643 U+0650
Shuʿbah
shubah
مَٰلِكِU+0645 U+064E U+0670 U+0644 U+0650 U+0643 U+0650
Warsh
warsh
مَلِكِU+0645 U+064E U+0644 U+0650 U+0643 U+0650
Qālūn
qalun
مَلِكِU+0645 U+064E U+0644 U+0650 U+0643 U+0650
Dūrī
duri
مَلِكِU+0645 U+064E U+0644 U+0650 U+0643 U+0650
Sūsī
susi
مَّلِكِU+0645 U+064E U+0651 U+0644 U+0650 U+0643 U+0650
Bazzī
bazzi
مَلِكِU+0645 U+064E U+0644 U+0650 U+0643 U+0650

Word position 10 of surah 1 in each edition’s own words array, from public/demo/fatiha.json. It is the same word in every reading — the first word of the ayah Ḥafṣ numbers 1:4 — and the same shared word number in Quran Text.

Same word. Three spellings. Even in editions where it looks identical, the code points around it can differ, so a === b is not a test for "the same word", and neither is a normalised comparison — see Encoding and normalisation. A word needs a name that is not its text.

The two names a word has

Our datasets use two, and they answer different questions.

surah:ayah:wordshared word number
Looks like"1:4:1"11
Meansthe n-th word of an ayah, in one edition's own numberingone integer for this word in every edition
Carried byQuran SVG Elements (data-word-key), Quran Engine, Quran Text (m.word(s, a, i))Quran Text (numbering, data/word-index.json)
Survives a change of riwayahno — ayah numbers differ between editionsyes — that is what it is for
Survives a new release of the source packageyes, unless the publisher re-divides the ayahno — it is a position in the union of the seven texts
Use it toaddress a word on a rendered page; join to a Ḥafṣ-keyed datasetsay "this word" in more than one edition at once

surah:ayah:word is per edition

It is the key on every word group in a split page, and it is the key you get back from a tap:

<g class="word" data-word-key="1:1:2" data-rasm-uthmani="ٱللَّهِ">

That line is from page 1 of the Elements bundle, which is a Ḥafṣ muṣḥaf. The 1 in the middle is a Ḥafṣ ayah number. In the Warsh edition the basmalah is not a numbered ayah, so Warsh's 1:1:2 is لِلهِ, the second word of ࡴ۬لْحَمْدُ لِلهِ — a different word. The key is only a key together with the edition it came from.

A surah:ayah:word key without its riwayah is ambiguous

Nothing throws when you look up Ḥafṣ 1:4:1 in a Warsh dataset. You get a word — the first word of Warsh's fourth ayah, which is Ḥafṣ's fifth. Persist the riwayah beside the key, exactly as you would for an ayah reference. The ayah half of the key has every problem described in ayah-counting systems.

The shared number is per corpus

Quran Text assigns one integer to each word, dense from 1, identical in all seven muṣḥaf files. Ḥafṣ position 10 in surah 1 is number 11 in every edition, so the table above can show seven spellings of "the same word" without matching any text. Attach a translation, a root, or an audio segment to number 11 once and it is attached in all seven.

The trade is that the number is a position in the union of the seven texts. A future release of a source package that adds or removes one word shifts every number after it, and stored rows keep resolving — to the neighbouring word. For anything you write to a database, Quran Text also ships a content-derived key, surah:pointed#occurrence, which looks like this on a word-index record:

{ "number": 73951, "surah": 72, "index": 153, "key": "72:لو#1", "status": "word_boundary" }

72:لو#1 means "the first occurrence in surah 72 of the word whose pointed skeleton is لو". It survives a rebuild because it is derived from the text, not from a count.

Position and number are different integers, and close

Inside a muṣḥaf file, every array named *_starts holds a position — an index into this edition's words. Only the numbering block holds the shared number. Surah 112 begins one position earlier in the Warsh file than in the Ḥafṣ file, because Warsh prints one word fewer before it; the shared number of its first word is the same in both. Mix the two and your text is off by a word or two, with no error.

Joining across blocks

The practical recipe is: rendered pages speak surah:ayah:word in Ḥafṣ; the text dataset translates that into the shared number; everything else hangs off the number.

  1. From a tap to a key

    On an Elements page, the key is on the group you tapped. This is the code the demo on this site runs, trimmed.

    svg.addEventListener("click", (e) => {
      const word = e.target.closest("g.word");
      if (!word) return;
      const key = word.dataset.wordKey;      // "1:1:2" — Ḥafṣ numbering, because the page is Ḥafṣ
    });
    
  2. From a key to the shared number

    Quran Text's word index resolves Ḥafṣ surah, ayah, position to the number in one lookup.

    const idx = await WordIndex.load("data/word-index.json");
    idx.find(1, 4, 1).number;   // 11
    

    The hafs field on every word-index record — { surah, ayah, position } in the Kufan count — exists so that a word-level dataset keyed on Ḥafṣ coordinates, which most existing ones are, joins in a single lookup.

  3. From the number to any edition

    const w = hafs.word(1, 4, 1);
    w.number;              // 11
    w.to(warsh).text;      // مَلِكِ
    w.to(susi).text;       // مَّلِكِ
    
Word boundaries are each project's decision

Elements and Quran Text both count 77,432 words in the Ḥafṣ muṣḥaf, and both derive word boundaries from the publisher's typesetting — but separately. Elements' own format notes say its boundaries agree with other word-by-word decompositions almost everywhere and that a handful of words are split differently. So a surah:ayah:word key from one is almost always the same word in the other. If you join them, spot-check rather than assume, and treat a mismatch as a finding to report, not a bug to patch locally.

What is not a word key

Try it live

Click a word on the real page and see its key, its five text forms and its box — everything hangs off data-word-key.

How this data is made

No publisher package supplies word-level data, so the word boundaries in Quran Text are derived from the publisher's own typesetting and the shared numbering is built by aligning the seven texts on the bare letter skeleton of each word; the build asserts on every file that words.length + joined + missing == numbering.total. Elements assigns ink to words from the vectorised artwork and checks that every data-word-key in an index resolves to a word group on the page it names. Neither cross-checks the other today.