@quran.ws/tajwid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +115 -0
  2. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @quran.ws/tajwid
2
+
3
+ Compiles the [tajweed rule corpus](../rules) and reports where each rule
4
+ applies in Quranic text.
5
+
6
+ ```bash
7
+ npm install @quran.ws/tajwid @quran.ws/tajwid-rules
8
+ ```
9
+
10
+ ```ts
11
+ import corpus from '@quran.ws/tajwid-rules'
12
+ import { Tajweed, sliceSpan } from '@quran.ws/tajwid'
13
+
14
+ const tajweed = new Tajweed(corpus)
15
+
16
+ for (const span of tajweed.analyze(ayahText)) {
17
+ console.log(span.hukumId, sliceSpan(ayahText, span))
18
+ }
19
+ ```
20
+
21
+ ## It returns positions, not markup
22
+
23
+ ```ts
24
+ interface Span {
25
+ start: number // code-point position in the text YOU passed in
26
+ end: number // half-open
27
+ ruleId: string // 'madd-muttasil.1'
28
+ hukumId: string // 'madd-muttasil'
29
+ categoryId: string // 'madd-far-hamz'
30
+ topicId: string // 'madd'
31
+ }
32
+ ```
33
+
34
+ For example, analysing ayah 112:1 (قُلۡ هُوَ ٱللَّهُ أَحَدٌ) returns four spans, one
35
+ of which is:
36
+
37
+ ```js
38
+ { start: 22, end: 24, ruleId: 'qalqalah-kubra.1', hukumId: 'qalqalah-kubra', ... }
39
+ // sliceSpan(text, span) === 'دٌ' — the final د of أَحَدٌ, with its tanween
40
+ ```
41
+
42
+ Colours, HTML, ANSI, SVG overlays and mushaf-page coordinates all build on top
43
+ of this, and each platform needs a different one. Returning positions keeps the
44
+ engine useful for all of them; returning HTML would serve only one.
45
+
46
+ `start` and `end` count **code points**, not bytes and not UTF-16 units, so the
47
+ same numbers mean the same thing in PHP, Python and Swift. All Arabic and all
48
+ Quranic annotation marks sit in the Basic Multilingual Plane, so for Quranic
49
+ text the positions also work directly as JavaScript string indices —
50
+ `sliceSpan` handles the general case anyway.
51
+
52
+ ## Cutting the text is where Arabic breaks
53
+
54
+ Colouring a span means giving it its own element, and a browser shapes each
55
+ element on its own — so the word comes apart at every change of colour, and a
56
+ cut taken at the raw offset can land between a letter and the shadda written on
57
+ it. The text is unaltered either way, nothing is raised, and it reads as a font
58
+ problem. Two exports carry the fix:
59
+
60
+ ```ts
61
+ import { bridgeJoins, clusterEnd } from '@quran.ws/tajwid'
62
+
63
+ const end = clusterEnd(text, span.end) // past the marks on that letter
64
+ const chunks = bridgeJoins([before, span, after]) // joiners across each cut
65
+ ```
66
+
67
+ `toHtml` and [`@quran.ws/tajwid-react`](../react) already use both. Write your
68
+ own renderer — an SVG overlay, a canvas, a native view — and you need them:
69
+ [docs/rendering.md](../../docs/rendering.md) says why, including why the joiner
70
+ has to be conditional.
71
+
72
+ ## Spans overlap, on purpose
73
+
74
+ One letter can demonstrate more than one ruling, and `analyze` reports all of
75
+ them. Choosing what to draw is a display decision, not an engine decision:
76
+
77
+ ```ts
78
+ import { resolveOverlaps } from '@quran.ws/tajwid'
79
+
80
+ resolveOverlaps(spans) // earliest wins, longest wins on a tie
81
+ ```
82
+
83
+ An app that teaches tajweed probably wants to keep the overlaps rather than
84
+ flatten them to one layer.
85
+
86
+ ## Choosing rules
87
+
88
+ ```ts
89
+ new Tajweed(corpus, { only: ['madd'] }) // a topic
90
+ new Tajweed(corpus, { only: ['madd-muttasil'] }) // a hukum
91
+ new Tajweed(corpus, { only: ['madd-muttasil.1'] }) // one rule
92
+ new Tajweed(corpus, { school: 'ibn-al-jazari' }) // pick a school
93
+ new Tajweed(corpus, { includeDisabled: true }) // for working ON the corpus
94
+ ```
95
+
96
+ `school` matters where the corpus models two authorities side by side — Ibn
97
+ al-Jazarī counts five ranks of tafkheem, Ibn al-Ṭaḥḥān counts three, and both
98
+ are present. Ahkam with no school attribution are always kept.
99
+
100
+ ## Your text is never changed
101
+
102
+ Matching runs on an internal normalised copy. Nothing in the pipeline applies
103
+ Unicode normalisation, strips diacritics, or drops waqf marks, and `analyze`
104
+ never returns a modified copy of your string — only positions into it.
105
+
106
+ ## Run it at build time
107
+
108
+ The Quran is a fixed text, so for production the expected setup is: annotate
109
+ every ayah once and ship the positions, instead of compiling every pattern on
110
+ every request. Record which text edition you computed against — the same rule
111
+ lands on different positions in different editions of the Uthmani script.
112
+
113
+ ## Licence
114
+
115
+ MIT. The rule corpus is a separate work under CC BY 4.0.
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@quran.ws/tajwid",
3
+ "version": "0.1.0",
4
+ "description": "Compiles the tajweed rule corpus and reports where each rule applies in Quranic text, as code-point offsets.",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist/",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/quran-ws/quran-tajweed.git",
28
+ "directory": "packages/core"
29
+ },
30
+ "keywords": [
31
+ "quran",
32
+ "tajweed",
33
+ "arabic",
34
+ "uthmani",
35
+ "hafs"
36
+ ]
37
+ }