@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.
- package/README.md +115 -0
- 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
|
+
}
|