@quran.ws/qvp-react-native 0.1.0 → 0.3.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/LICENSE +172 -0
- package/README.md +30 -28
- package/android/build.gradle +2 -2
- package/android/src/main/java/{net/quranpedia → ws/quran}/qvp/rn/Marshal.kt +33 -32
- package/android/src/main/java/{net/quranpedia → ws/quran}/qvp/rn/QvpModule.kt +55 -35
- package/android/src/main/java/{net/quranpedia → ws/quran}/qvp/rn/QvpPackage.kt +1 -1
- package/android/src/main/java/{net/quranpedia → ws/quran}/qvp/rn/QvpPageViewManager.kt +9 -6
- package/android/src/main/java/{net/quranpedia → ws/quran}/qvp/rn/QvpRnPageView.kt +54 -28
- package/package.json +1 -1
- package/src/index.tsx +147 -104
package/LICENSE
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Copyright 2026 Quran.ws
|
|
2
|
+
|
|
3
|
+
This work is dedicated as a waqf for the sake of Allah, seeking His pleasure
|
|
4
|
+
through the benefit it brings to people. We publish it to enable the widest
|
|
5
|
+
possible beneficial use, with as few restrictions as possible.
|
|
6
|
+
|
|
7
|
+
Quran.ws publishes code under the MIT License, and data and content under the
|
|
8
|
+
Creative Commons Attribution 4.0 International License,
|
|
9
|
+
with a standing waiver of attribution for use inside a product.
|
|
10
|
+
|
|
11
|
+
We license only what we have the right to license; third-party material keeps
|
|
12
|
+
the licence and terms of its own rights holders.
|
|
13
|
+
|
|
14
|
+
Full policy: https://github.com/quran-ws/guidelines
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
WHAT IS COVERED BY WHAT
|
|
19
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
crates/ MIT
|
|
22
|
+
docs/ CC BY 4.0
|
|
23
|
+
packages/ MIT
|
|
24
|
+
scripts/ MIT
|
|
25
|
+
web/ MIT
|
|
26
|
+
|
|
27
|
+
Where a directory or file carries its own LICENSE or NOTICE, that notice
|
|
28
|
+
governs for that material. Names, logos and trademarks are not licensed.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
ATTRIBUTION WAIVED FOR USE IN PRODUCTS
|
|
33
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
As rights holders of our original data and content, we grant a permanent,
|
|
36
|
+
worldwide, royalty-free, irrevocable waiver of the attribution requirement of
|
|
37
|
+
CC BY 4.0 (Section 3(a)) when the material is used within an application,
|
|
38
|
+
website, service, API, bot, tool, research work, or product, whether free or
|
|
39
|
+
commercial. This permission expands what you may do and adds no restriction to
|
|
40
|
+
CC BY 4.0.
|
|
41
|
+
|
|
42
|
+
Attribution is still required on republication — when the data or content
|
|
43
|
+
itself is made available to others as a resource in its own right. The test:
|
|
44
|
+
if a third party can obtain the data AS DATA from what you distribute, that is
|
|
45
|
+
republication, and CC BY 4.0 attribution applies.
|
|
46
|
+
|
|
47
|
+
Suggested attribution:
|
|
48
|
+
|
|
49
|
+
Data from [resource] by Quran.ws, licensed under CC BY 4.0.
|
|
50
|
+
Source: [canonical URL]. Version: [version]. Modified: [yes/no].
|
|
51
|
+
|
|
52
|
+
Use for training, fine-tuning, evaluating, indexing or retrieval by machine
|
|
53
|
+
learning systems is permitted and treated as use, not republication.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
57
|
+
THIRD-PARTY MATERIAL — KING FAHD GLORIOUS QUR'AN PRINTING COMPLEX
|
|
58
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
The Qur'anic text editions, mushaf typesettings and fonts in this repository
|
|
61
|
+
derive from material published by the King Fahd Glorious Qur'an Printing
|
|
62
|
+
Complex. That material is not ours to license. It is used under the Complex's
|
|
63
|
+
own published usage rights, reproduced in full below in Arabic and English.
|
|
64
|
+
|
|
65
|
+
Our processing, correction, organisation or transformation of that material
|
|
66
|
+
creates no ownership in it and does not override these terms.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
───────────────────────────────────────────────────────────── حقوق الاستخدام ───
|
|
70
|
+
|
|
71
|
+
بناء على موافقة معالي وزير الشؤون الإسلامية والدعوة والإرشاد، المشرف العام على
|
|
72
|
+
مجمع الملك فهد لطباعة المصحف الشريف، يتشـرَّف المجمع بإتاحة نسخة رقمية كاملة من
|
|
73
|
+
مصحف المدينة النبوية المطبوع في المجمع، وذلك بالصيغ التالية:
|
|
74
|
+
|
|
75
|
+
رسم المتجهات Illustrator
|
|
76
|
+
وثيقة بي دي إف PDF
|
|
77
|
+
صور ذات جودة عالية Images
|
|
78
|
+
خط حاسوبي True Type Font
|
|
79
|
+
|
|
80
|
+
ويمكن استخدام النسخة المذكورة مجاناً في المجالات الشخصية والأعمال الفردية كافة،
|
|
81
|
+
وأعمال الجهات الحكومية والجهات والمؤسسات الخاصة والأهلية، وأعمال الطباعة
|
|
82
|
+
الورقية*، والنشـر المكتبي والإعلامي، ومواقع الإنترنت والبرامج الحاسوبية وغيرها،
|
|
83
|
+
وذلك داخل المملكة العربية السعودية وخارجها.
|
|
84
|
+
|
|
85
|
+
نسأل الله أن ينفع المسلمين بهذا العمل المبارك.
|
|
86
|
+
|
|
87
|
+
والحمد لله رب العالمين
|
|
88
|
+
|
|
89
|
+
* أعمال طباعة المصاحف داخل المملكة واستيرادها من الخارج لغرض البيع التجاري
|
|
90
|
+
تخضع للأمر السامي الكريم ذي الرقم 136/8 المؤرخ في 1/2/1406هـ وأكّد على ذلك
|
|
91
|
+
الأمر السامي الكريم رقم 9/ب/46356 بتاريخ 28/9/1424هـ. والذي وجه بمنع طباعة
|
|
92
|
+
المصاحف في الداخل في غير المجمع، وكذلك منع استيرادها من الخارج لغرض البيع
|
|
93
|
+
التجاري.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
──────────────────────────────────────────────────────────────── Copyright ────
|
|
97
|
+
|
|
98
|
+
Based on the approval of His Excellency the Minister of Islamic Affairs,
|
|
99
|
+
Endowments, Da'wah and Guidance, General Supervisor of the King Fahd Qur'an
|
|
100
|
+
Printing Complex.
|
|
101
|
+
|
|
102
|
+
The Qur'an Printing Complex is honored to present to the Muslim public a
|
|
103
|
+
complete free digital copy of Mus'haf al-Madinah published by the Complex, in
|
|
104
|
+
the following formats:
|
|
105
|
+
|
|
106
|
+
• Adobe Illustrator files
|
|
107
|
+
• PDF files
|
|
108
|
+
• High quality images
|
|
109
|
+
• True Type Font
|
|
110
|
+
|
|
111
|
+
Mus'haf al-Madinah in these previous formats can be used for free in all
|
|
112
|
+
personal, individual businesses, in works of governmental departments &
|
|
113
|
+
agencies, in the publications of both private and national institutions, also
|
|
114
|
+
suitable for Qur'an printing,* digital publishing, & for media use, can be used
|
|
115
|
+
also in websites, software, and other similar intermediates.
|
|
116
|
+
|
|
117
|
+
We ask Allah that He renders this blessed project beneficial to all Muslims.
|
|
118
|
+
|
|
119
|
+
* Printing of Qur'anic copies in the Kingdom, and their import from outside for
|
|
120
|
+
sale are subject to the Royal Decree No 136/8 dated 1/2/1406 AH which has been
|
|
121
|
+
reiterated by the Royal Decree No 9/B/46356 dated 28/9/1424 AH which prohibit
|
|
122
|
+
printing of Qur'anic copies in the Kingdom except in the Complex, likewise
|
|
123
|
+
their import from outside for sale.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
127
|
+
ACCURACY AND NO WARRANTY
|
|
128
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
129
|
+
|
|
130
|
+
This material is provided AS IS, without warranty of any kind, as stated in
|
|
131
|
+
the MIT License and in CC BY 4.0 (Sections 5 and 6).
|
|
132
|
+
|
|
133
|
+
Qur'anic text, transliteration, translation, recitation and mushaf rendering
|
|
134
|
+
may contain errors, and releases are corrected over time. Anyone publishing
|
|
135
|
+
Qur'anic text to end users is responsible for verifying it against an
|
|
136
|
+
authorised printed mushaf and for tracking our corrections.
|
|
137
|
+
|
|
138
|
+
Corrections: corrections@quran.ws — treated as a priority.
|
|
139
|
+
Licensing questions: legal@quran.ws
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
143
|
+
FULL LICENCE TEXTS
|
|
144
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
|
|
146
|
+
The full CC BY 4.0 legal text is in LICENSES/CC-BY-4.0.txt and at
|
|
147
|
+
https://creativecommons.org/licenses/by/4.0/legalcode
|
|
148
|
+
|
|
149
|
+
MIT License
|
|
150
|
+
|
|
151
|
+
Copyright (c) 2026 Quran.ws
|
|
152
|
+
|
|
153
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
154
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
155
|
+
in the Software without restriction, including without limitation the rights
|
|
156
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
157
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
158
|
+
furnished to do so, subject to the following conditions:
|
|
159
|
+
|
|
160
|
+
The above copyright notice and this permission notice shall be included in all
|
|
161
|
+
copies or substantial portions of the Software.
|
|
162
|
+
|
|
163
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
164
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
165
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
166
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
167
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
168
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
169
|
+
SOFTWARE.
|
|
170
|
+
|
|
171
|
+
This notice is published in Arabic and English. Where the two differ, the
|
|
172
|
+
Arabic version governs.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @quran.ws/qvp-react-native
|
|
2
2
|
|
|
3
3
|
React Native wrapper for the QVP vector mushaf engine. Android-first: a native view
|
|
4
|
-
(`<QvpPageView />`) hosting the Kotlin library's `QvpPageView` (`
|
|
4
|
+
(`<QvpPageView />`) hosting the Kotlin library's `QvpPageView` (`ws.quran.qvp`,
|
|
5
5
|
`packages/android/qvp`, JNI over `crates/qvp-ffi/include/qvp.h`) plus a promise-based module
|
|
6
6
|
(`QvpModule`) for queries. The wrapper is *thin*: no hit-testing, layout or styling logic in JS or
|
|
7
7
|
in the module — it marshals props/targets/selectors and reconciles declarative arrays against engine
|
|
@@ -37,7 +37,7 @@ URIs: `asset://pages/042.qvp`, `file:///…`, `/abs/path`, or `base64:…`.
|
|
|
37
37
|
## The view
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
|
-
import { QvpPageView, useQvp, Sel, T, LAYER, KIND,
|
|
40
|
+
import { QvpPageView, useQvp, Sel, T, LAYER, KIND, DECORATION, rgba } from '@quran.ws/qvp-react-native';
|
|
41
41
|
|
|
42
42
|
const qvp = useQvp();
|
|
43
43
|
<QvpPageView
|
|
@@ -46,12 +46,13 @@ const qvp = useQvp();
|
|
|
46
46
|
pageUri="asset://pages/042.qvp"
|
|
47
47
|
wordsUri="asset://pages/042.words.json" // optional sidecar (rasm_imlai/qpc/rasm/search forms)
|
|
48
48
|
padTop={12} padBottom={12} padSide={8} // dp
|
|
49
|
-
lineSpacing={1}
|
|
49
|
+
lineSpacing={1} fillHeight={false} // engine layout knobs
|
|
50
|
+
// spacing only opens up: lineSpacing < 1 is clamped to "as printed"
|
|
50
51
|
paperColor="#fffdf7" defaultInk="#231f20"
|
|
51
52
|
theme={{ diacritics: '#1a73e8', dots: '#c62828', waqf: '#0a7d32', ms: 200 }} // page.theme(...) — one handle
|
|
52
53
|
styles={[
|
|
53
54
|
{ id: 'hide-marks', selector: Sel.kind(KIND.MARK), hide: true }, // page.hide(sel)
|
|
54
|
-
{ id: 'gold', selector: Sel.
|
|
55
|
+
{ id: 'gold', selector: Sel.decoration(DECORATION.AYAH_MARK), color: '#b8860b', ms: 300, layer: LAYER.THEME + 1 },
|
|
55
56
|
{ id: 'mark', selector: Sel.wordMark(12, 1), color: '#ef6c00', ms: 200, layer: LAYER.TOP }, // 2nd diacritic of word 12
|
|
56
57
|
{ id: 'ayah', target: '2:255', color: '#0a7d32' }, // page.styleTarget(...)
|
|
57
58
|
]}
|
|
@@ -61,31 +62,31 @@ const qvp = useQvp();
|
|
|
61
62
|
]}
|
|
62
63
|
mask={{ target: '2:255', mode: 'hide' }} // or 'block' | 'blur' (+ blockColor, padX, padY, radius)
|
|
63
64
|
reveal={{ lit: 2, grey: '#c9c4b8', ink: '#231f20', ms: 150, at: 3 }} // greyed page; `at` drives revealGoto
|
|
64
|
-
onWordTap={({ word, hit }) => …} // word: {
|
|
65
|
-
|
|
65
|
+
onWordTap={({ word, hit }) => …} // word: {index, surah, ayah, word, line, …, text, wordKey, aid, forms, label, paths[]}
|
|
66
|
+
onDecorationTap={({ decoration, hit }) => …} // ayah markers, banners, basmalah, rosettes, sajdah signs
|
|
66
67
|
onEmptyTap={() => …}
|
|
67
68
|
onSelectionChanged={({ words, text, citation, textWithCitation }) => …} // long-press-drag selection
|
|
68
|
-
onPageLoad={info => …} // {page, width, height, nLines, nAyahs, nWords, nPaths,
|
|
69
|
+
onPageLoad={info => …} // {page, width, height, nLines, nAyahs, nWords, nPaths, nDecorations, forms, loadMs, bytes}
|
|
69
70
|
onRevealChanged={({ steps, at }) => …}
|
|
70
71
|
onError={({ message }) => …}
|
|
71
72
|
/>
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
Reconciliation: each `highlights` entry maps to one engine handle. A new id → `highlight`; a changed
|
|
75
|
-
`target` → `
|
|
76
|
-
`
|
|
77
|
-
`
|
|
76
|
+
`target` → `moveHighlight` (the band slides); a changed `style` → `restyleHighlight`; a removed id →
|
|
77
|
+
`removeHighlight` (fades out). `styles` works the same over `style` / `styleTarget` / `hide` / `recolorStyle` /
|
|
78
|
+
`removeStyle`; `theme` is one handle; `mask` re-applies `maskOptions` + `mask` when the object changes
|
|
78
79
|
(`null` → `unmask`); `reveal` calls `revealStart` when its config changes and `revealGoto` when `at`
|
|
79
80
|
changes (`null` → `revealStop`). When `pageUri` changes, the old page is freed and every declarative
|
|
80
81
|
prop is re-applied to the new page.
|
|
81
82
|
|
|
82
|
-
Gestures are the Kotlin view's: tap → gap-aware `
|
|
83
|
+
Gestures are the Kotlin view's: tap → gap-aware `hitTestView` → `onWordTap` / `onDecorationTap` /
|
|
83
84
|
`onEmptyTap`; long-press-drag → engine `select` with a band in `LAYER.SELECTION`; pinch / pan;
|
|
84
85
|
double-tap resets. Props `selectionEnabled`, `zoomEnabled`, `hitMaxDistance`, `selectionBand`.
|
|
85
86
|
|
|
86
87
|
Colours: `'#rgb' | '#rrggbb' | '#rrggbbaa' | 0xRRGGBBAA` (`css()` / `rgba(c, alpha)` helpers).
|
|
87
|
-
Targets: `'page' | '2:255' | '2:255:3' | '2:255-257' | 'line:7' | 'surah:2' | wordIdx | [
|
|
88
|
-
Selectors: `Sel.page/path/wordPath/wordMark/wordMarkNamed/wordBody/wordMarks/word/ayah/line/mark/category/family/kind/
|
|
88
|
+
Targets: `'page' | '2:255' | '2:255:3' | '2:255-257' | 'line:7' | 'surah:2' | wordIdx | [index…] | T.*`.
|
|
89
|
+
Selectors: `Sel.page/path/wordPath/wordMark/wordMarkNamed/wordBody/wordMarks/word/ayah/line/mark/category/family/kind/decoration/decorationIndex`.
|
|
89
90
|
|
|
90
91
|
## The module
|
|
91
92
|
|
|
@@ -94,42 +95,43 @@ Everything takes the view's react tag (`findNodeHandle`) — or use the bound fo
|
|
|
94
95
|
|
|
95
96
|
```ts
|
|
96
97
|
const qvp = useQvp();
|
|
97
|
-
await qvp.info(); qvp.words(); qvp.word(i); qvp.ayahs(); qvp.lines(); qvp.
|
|
98
|
+
await qvp.info(); qvp.words(); qvp.word(i); qvp.ayahs(); qvp.lines(); qvp.decorations();
|
|
98
99
|
await qvp.search('الرحمان', { mode: 'includes' }); // [{word, wordKey, text, index, loose}]
|
|
99
100
|
await qvp.text('2:255', { form: 'search', wordSep: ' ' });
|
|
100
|
-
await qvp.
|
|
101
|
+
await qvp.targetWords('line:7'); qvp.findWord(2, 255, 3); qvp.wordForm(i, 'rasm_imlai'); qvp.hasForm('qpc');
|
|
101
102
|
await qvp.attachWords(jsonString); // when you do not use the wordsUri prop
|
|
102
|
-
await qvp.surahs(); qvp.divisions(); qvp.
|
|
103
|
-
await qvp.ayahWordCount(2, 255); qvp.reciteMap(2, 255, 4); qvp.wordLabel(i); qvp.ayahLabel(
|
|
103
|
+
await qvp.surahs(); qvp.divisions(); qvp.ayahMarks(); qvp.rosettes(); qvp.sajdahs(); qvp.ayahKeys();
|
|
104
|
+
await qvp.ayahWordCount(2, 255); qvp.reciteMap(2, 255, 4); qvp.wordLabel(i); qvp.ayahLabel(ayahIndex);
|
|
104
105
|
await qvp.citation([12, 13, 14]);
|
|
105
|
-
await qvp.cropSvg('2:255', { pad: 3, keepMarkers: true, background: '#fffdf7' }); qvp.
|
|
106
|
+
await qvp.cropSvg('2:255', { pad: 3, keepMarkers: true, background: '#fffdf7' }); qvp.cropBounds(target);
|
|
106
107
|
await qvp.select(anchor, focus); qvp.clearSelection(); qvp.selection(); qvp.selectionText('rasm_uthmani', true);
|
|
107
|
-
await qvp.
|
|
108
|
-
await qvp.maskHidden(); qvp.maskWords(); qvp.
|
|
109
|
-
await qvp.
|
|
108
|
+
await qvp.unmaskNext(1); qvp.maskBack(1); qvp.unmaskWord(i); qvp.maskWord(i); qvp.unmaskAll(); qvp.maskAll();
|
|
109
|
+
await qvp.maskHidden(); qvp.maskWords(); qvp.revealStepCount(); qvp.revealPosition(); qvp.revealStepOf(i);
|
|
110
|
+
await qvp.hitTestView(x, y, { maxDistance: 6 }); qvp.hitTestExactView(x, y); qvp.hitTest(px, py); qvp.hitTestExact(px, py); // view dp or page units
|
|
111
|
+
await qvp.wordBoundsView(i); qvp.currentLayout(); qvp.layoutLineSpacingToFill(); qvp.relayout(); qvp.resetView(); qvp.stats();
|
|
110
112
|
|
|
111
113
|
// engine-wide (Qvp.*)
|
|
112
114
|
await Qvp.strip(s); Qvp.fold(s); Qvp.normalize(s); Qvp.looseKey(s); // = Qvp.arabic(kind, s)
|
|
113
|
-
await
|
|
114
|
-
Qvp.markName(7); Qvp.kindName(1); Qvp.categoryName(1); Qvp.familyName(3); Qvp.version
|
|
115
|
+
await qvp.layoutLineSpacingToFill(); qvp.layoutWastedFraction(); qvp.grid()
|
|
116
|
+
Qvp.markName(7); Qvp.kindName(1); Qvp.categoryName(1); Qvp.familyName(3); Qvp.nameId('marks', 'fathah'); Qvp.version; await Qvp.markCategory(7); Qvp.engineName()
|
|
115
117
|
|
|
116
118
|
// atlas (cross-page)
|
|
117
119
|
const atlas = await QvpAtlas.load('asset://pages/atlas.qva');
|
|
118
120
|
await atlas.pageOf(2, 255); atlas.pageRange(42); atlas.surah(36); atlas.surahs(); atlas.pageOfSurah(36);
|
|
119
|
-
await atlas.juz(30); atlas.hizb(3); atlas.rubuAlHizb(7); atlas.
|
|
120
|
-
await atlas.pagesOfJuz(30); atlas.
|
|
121
|
-
// tag-level equivalents: Qvp.atlasPageOf(id, s, a), Qvp.
|
|
121
|
+
await atlas.juz(30); atlas.hizb(3); atlas.rubuAlHizb(7); atlas.juzOf(2, 255); atlas.divisionOf('hizb', 2, 255);
|
|
122
|
+
await atlas.pagesOfJuz(30); atlas.searchSurahs('cow' | 'البقرة' | '2'); atlas.free();
|
|
123
|
+
// tag-level equivalents: Qvp.atlasPageOf(id, s, a), Qvp.atlasSearchSurahs(id, text), Qvp.atlasPagesOfJuz(id, n), Qvp.atlasJuzOf(id, s, a), …
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
All page calls run on the UI thread (where the view draws), so the engine is never touched from two
|
|
125
|
-
threads. Only `maskHidden`, `unmask` (via the `mask` prop), `
|
|
127
|
+
threads. Only `maskHidden`, `unmask` (via the `mask` prop), `unmaskNext` etc. mutate state; queries are
|
|
126
128
|
microseconds.
|
|
127
129
|
|
|
128
130
|
## Layout of this package
|
|
129
131
|
|
|
130
132
|
```
|
|
131
133
|
android/build.gradle library module; depends on project(':qvp') or compiles its sources
|
|
132
|
-
android/src/main/java/
|
|
134
|
+
android/src/main/java/ws/quran/qvp/rn/
|
|
133
135
|
QvpRnPageView.kt QvpPageView subclass: page loading, prop → handle reconciliation, events
|
|
134
136
|
QvpPageViewManager.kt SimpleViewManager<QvpRnPageView> (props, events, commit after each prop batch)
|
|
135
137
|
QvpModule.kt ReactContextBaseJavaModule: page queries by react tag, engine helpers, atlas
|
package/android/build.gradle
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @quran.ws/qvp-react-native — Android module. Thin: marshalling + handle reconciliation over the Kotlin
|
|
2
|
-
// library `
|
|
2
|
+
// library `ws.quran.qvp` (packages/android/qvp), which is JNI over crates/qvp-ffi/include/qvp.h.
|
|
3
3
|
//
|
|
4
4
|
// Linking the Kotlin library, two ways:
|
|
5
5
|
// 1. (preferred) the app's settings.gradle includes it as a Gradle project:
|
|
@@ -25,7 +25,7 @@ def qvpProject = findProject(":qvp")
|
|
|
25
25
|
def qvpDir = file(rootProject.findProperty("qvpAndroidDir") ?: "$projectDir/../../../android/qvp")
|
|
26
26
|
|
|
27
27
|
android {
|
|
28
|
-
namespace "
|
|
28
|
+
namespace "ws.quran.qvp.rn"
|
|
29
29
|
compileSdk safeExtGet("compileSdkVersion", 35)
|
|
30
30
|
defaultConfig {
|
|
31
31
|
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
package
|
|
1
|
+
package ws.quran.qvp.rn
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.ReadableArray
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
6
|
import com.facebook.react.bridge.WritableArray
|
|
7
7
|
import com.facebook.react.bridge.WritableMap
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import ws.quran.qvp.*
|
|
9
|
+
import ws.quran.qvp.Target
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Marshalling only: JS shapes (docs/API.md) ↔ Kotlin library types. No hit-testing, layout or
|
|
13
13
|
* styling decisions live here — every call goes to [QvpPage] / [QvpAtlas], which go to the engine.
|
|
14
14
|
*
|
|
15
15
|
* Targets from JS: "page" | "2:255" | "2:255:3" | "2:255-257" | "line:7" | "surah:2" | word index |
|
|
16
|
-
* [word indices] | {
|
|
17
|
-
* Selectors from JS: {
|
|
16
|
+
* [word indices] | {target, a, b, c, words} (target = QVP_TARGET_* or its name).
|
|
17
|
+
* Selectors from JS: {selector, a, b, c} (selector = QVP_SELECTOR_* or its name). Colours: '#rgb' | '#rrggbb' | '#rrggbbaa'.
|
|
18
18
|
*/
|
|
19
19
|
object Marshal {
|
|
20
20
|
// ── colours ──
|
|
@@ -37,7 +37,7 @@ object Marshal {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
private val TARGET_KINDS = listOf("page", "word", "words", "ayah", "ayahRange", "line", "surah", "range")
|
|
40
|
-
private val SEL_KINDS = listOf("page", "path", "wordPath", "wordMark", "wordMarkNamed", "wordBody", "wordMarks", "word", "ayah", "line", "mark", "category", "family", "kind", "
|
|
40
|
+
private val SEL_KINDS = listOf("page", "path", "wordPath", "wordMark", "wordMarkNamed", "wordBody", "wordMarks", "word", "ayah", "line", "mark", "category", "family", "kind", "decoration", "decorationIndex")
|
|
41
41
|
private fun kindOf(v: Any?, names: List<String>): Int = when (v) {
|
|
42
42
|
is Number -> v.toInt()
|
|
43
43
|
is String -> names.indexOf(v).let { if (it >= 0) it else v.toIntOrNull() ?: -1 }
|
|
@@ -53,7 +53,7 @@ object Marshal {
|
|
|
53
53
|
is IntArray -> Target.words(v)
|
|
54
54
|
is Map<*, *> -> {
|
|
55
55
|
val a = num(v["a"]) ?: 0; val b = num(v["b"]) ?: 0; val c = num(v["c"]) ?: 0
|
|
56
|
-
when (kindOf(v["
|
|
56
|
+
when (kindOf(v["target"], TARGET_KINDS)) {
|
|
57
57
|
0 -> Target.page(); 1 -> Target.word(a); 2 -> Target.words(ints(v["words"] ?: v["a"]))
|
|
58
58
|
3 -> Target.ayah(a, b); 4 -> Target.ayahRange(a, b, c); 5 -> Target.line(a); 6 -> Target.surah(a); 7 -> Target.range(a, b)
|
|
59
59
|
else -> Target.words(IntArray(0))
|
|
@@ -62,16 +62,16 @@ object Marshal {
|
|
|
62
62
|
else -> Target.words(IntArray(0))
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/** JS selector {
|
|
65
|
+
/** JS selector {selector, a, b, c} → [Selector], or null when malformed. */
|
|
66
66
|
fun selector(v: Any?): Selector? {
|
|
67
67
|
val m = v as? Map<*, *> ?: return null
|
|
68
68
|
val a = num(m["a"]) ?: 0; val b = num(m["b"]) ?: 0; val c = num(m["c"]) ?: 0
|
|
69
|
-
return when (kindOf(m["
|
|
69
|
+
return when (kindOf(m["selector"], SEL_KINDS)) {
|
|
70
70
|
0 -> Selector.page(); 1 -> Selector.path(a); 2 -> Selector.wordPath(a, b); 3 -> Selector.wordMark(a, b)
|
|
71
|
-
4 -> Selector.wordMarkNamed(a, (m["mark"] as? String) ?:
|
|
71
|
+
4 -> Selector.wordMarkNamed(a, (m["mark"] as? String) ?: QvpEngine.markName(b), c)
|
|
72
72
|
5 -> Selector.wordBody(a); 6 -> Selector.wordMarks(a); 7 -> Selector.word(a); 8 -> Selector.ayah(a, b); 9 -> Selector.line(a)
|
|
73
73
|
10 -> (m["mark"] as? String)?.let { Selector.mark(it) } ?: Selector.mark(a)
|
|
74
|
-
11 -> Selector.category(a); 12 -> Selector.family(a); 13 -> Selector.kind(a); 14 -> Selector.
|
|
74
|
+
11 -> Selector.category(a); 12 -> Selector.family(a); 13 -> Selector.kind(a); 14 -> Selector.decoration(a); 15 -> Selector.decorationIndex(a)
|
|
75
75
|
else -> null
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -117,40 +117,41 @@ object Marshal {
|
|
|
117
117
|
// ── Kotlin → JS ──
|
|
118
118
|
fun word(p: QvpPage, w: QvpWord): Map<String, Any?> {
|
|
119
119
|
val forms = LinkedHashMap<String, String>()
|
|
120
|
-
for (f in Form.entries) if (f == Form.RASM_UTHMANI || p.hasForm(f)) forms[f.name.lowercase()] = p.wordForm(w.
|
|
120
|
+
for (f in Form.entries) if (f == Form.RASM_UTHMANI || p.hasForm(f)) forms[f.name.lowercase()] = p.wordForm(w.index, f)
|
|
121
121
|
val paths = (w.firstPath until w.firstPath + w.nPaths).map { i ->
|
|
122
122
|
val kind = p.pathKind(i); val mark = p.pathMark(i); val cat = p.pathCategory(i); val fam = p.pathFamily(i)
|
|
123
|
-
mapOf("
|
|
123
|
+
mapOf("index" to i, "kind" to kind, "kindName" to QvpEngine.kindName(kind), "mark" to mark, "markName" to QvpEngine.markName(mark), "nthMark" to p.pathNthMark(i),
|
|
124
124
|
"nthInWord" to p.pathNthInWord(i), "category" to cat, "categoryName" to QvpEngine.categoryName(cat), "family" to fam, "familyName" to QvpEngine.familyName(fam), "line" to p.pathLine(i))
|
|
125
125
|
}
|
|
126
|
-
return mapOf("
|
|
126
|
+
return mapOf("index" to w.index, "surah" to w.surah, "ayah" to w.ayah, "word" to w.word, "line" to w.line, "lineIndex" to w.lineIndex, "ayahIndex" to w.ayahIndex,
|
|
127
127
|
"x0" to w.x0, "y0" to w.y0, "x1" to w.x1, "y1" to w.y1, "text" to w.text, "firstPath" to w.firstPath, "nPaths" to w.nPaths,
|
|
128
|
-
"wordKey" to w.wordKey, "ayahKey" to w.ayahKey, "forms" to forms, "label" to p.wordLabel(w.
|
|
128
|
+
"wordKey" to w.wordKey, "ayahKey" to w.ayahKey, "forms" to forms, "label" to p.wordLabel(w.index), "paths" to paths)
|
|
129
129
|
}
|
|
130
|
-
fun
|
|
130
|
+
fun decoration(d: QvpDecoration): Map<String, Any?> = mapOf("index" to d.index, "decoration" to d.decoration, "decorationName" to QvpEngine.decorationName(d.decoration).ifEmpty { "other" },
|
|
131
131
|
"surah" to d.surah, "ayah" to d.ayah, "line" to d.line, "x0" to d.x0, "y0" to d.y0, "x1" to d.x1, "y1" to d.y1, "text" to d.text, "firstPath" to d.firstPath, "nPaths" to d.nPaths)
|
|
132
|
-
fun hit(p: QvpPage, h:
|
|
132
|
+
fun hit(p: QvpPage, h: QvpHit): Map<String, Any?> {
|
|
133
133
|
val w = if (h.word >= 0) p.words[h.word] else null
|
|
134
|
-
return mapOf("word" to h.word, "path" to h.path, "
|
|
134
|
+
return mapOf("word" to h.word, "path" to h.path, "decoration" to h.decoration, "line" to h.line, "distance" to h.distance, "isExact" to h.isExact, "wordKey" to w?.wordKey, "ayahKey" to w?.ayahKey)
|
|
135
135
|
}
|
|
136
|
-
fun ayah(a: QvpAyah): Map<String, Any?> = mapOf("
|
|
137
|
-
"
|
|
138
|
-
fun line(l: QvpLine): Map<String, Any?> = mapOf("
|
|
136
|
+
fun ayah(a: QvpAyah): Map<String, Any?> = mapOf("index" to a.index, "surah" to a.surah, "ayah" to a.ayah, "fragment" to a.fragment, "fragments" to a.fragments, "flags" to a.flags, "rubuAlHizb" to a.rubuAlHizb, "firstWord" to a.firstWord, "nWords" to a.nWords,
|
|
137
|
+
"ayahMarkDecoration" to a.ayahMarkDecoration, "bbox" to listOf(a.x0, a.y0, a.x1, a.y1))
|
|
138
|
+
fun line(l: QvpLine): Map<String, Any?> = mapOf("index" to l.index, "lineNumber" to l.lineNumber, "isHeader" to l.isHeader, "firstWord" to l.firstWord, "nWords" to l.nWords, "bbox" to listOf(l.x0, l.y0, l.x1, l.y1),
|
|
139
139
|
"bandY0" to l.bandY0, "bandY1" to l.bandY1, "centre" to l.centre)
|
|
140
|
-
fun surah(s: QvpSurah): Map<String, Any?> = mapOf("number" to s.number, "ayahCount" to s.ayahCount, "hasBanner" to s.hasBanner, "hasBasmalah" to s.hasBasmalah, "place" to s.place, "
|
|
140
|
+
fun surah(s: QvpSurah): Map<String, Any?> = mapOf("number" to s.number, "ayahCount" to s.ayahCount, "hasBanner" to s.hasBanner, "hasBasmalah" to s.hasBasmalah, "place" to s.place, "bannerDecoration" to s.bannerDecoration,
|
|
141
141
|
"arabic" to s.arabic, "latin" to s.latin, "english" to s.english)
|
|
142
|
-
fun division(d: QvpDivision): Map<String, Any?> = mapOf("
|
|
143
|
-
fun
|
|
144
|
-
fun
|
|
145
|
-
fun
|
|
146
|
-
fun
|
|
147
|
-
fun
|
|
148
|
-
fun
|
|
142
|
+
fun division(d: QvpDivision): Map<String, Any?> = mapOf("division" to d.division.name.lowercase(), "number" to d.number, "surah" to d.surah, "ayah" to d.ayah, "line" to d.line, "ayahIndex" to d.ayahIndex)
|
|
143
|
+
fun surahHeader(h: QvpSurahHeader): Map<String, Any?> = mapOf("decoration" to h.decoration, "surah" to h.surah, "line" to h.line, "x0" to h.x0, "y0" to h.y0, "x1" to h.x1, "y1" to h.y1, "titleX0" to h.titleX0, "titleY0" to h.titleY0, "titleX1" to h.titleX1, "titleY1" to h.titleY1)
|
|
144
|
+
fun ayahMark(m: QvpAyahMark): Map<String, Any?> = mapOf("decoration" to m.decoration, "surah" to m.surah, "ayah" to m.ayah, "line" to m.line, "cx" to m.cx, "cy" to m.cy, "r" to m.r, "ornamentPath" to m.ornamentPath, "numeralPath" to m.numeralPath)
|
|
145
|
+
fun rosette(r: QvpRosette): Map<String, Any?> = mapOf("decoration" to r.decoration, "surah" to r.surah, "ayah" to r.ayah, "juz" to r.juz, "hizb" to r.hizb, "nisf" to r.nisf, "rubuAlHizb" to r.rubuAlHizb, "rubuAlHizbInHizb" to r.rubuAlHizbInHizb)
|
|
146
|
+
fun sajdah(s: QvpSajdah): Map<String, Any?> = mapOf("decoration" to s.decoration, "surah" to s.surah, "ayah" to s.ayah, "signPath" to s.signPath)
|
|
147
|
+
fun match(m: QvpMatch): Map<String, Any?> = mapOf("word" to m.word, "index" to m.index, "isLooseMatch" to m.isLooseMatch, "wordKey" to m.wordKey, "text" to m.text)
|
|
148
|
+
fun cropBounds(c: QvpCropBounds): Map<String, Any?> = mapOf("x0" to c.x0, "y0" to c.y0, "x1" to c.x1, "y1" to c.y1, "nWords" to c.nWords, "ayahMarkDecoration" to c.ayahMarkDecoration)
|
|
149
|
+
fun atlasSurah(s: QvpAtlasSurah): Map<String, Any?> = mapOf("number" to s.number, "page" to s.page, "ayahCount" to s.ayahCount, "place" to s.place, "arabic" to s.arabic, "latin" to s.latin, "english" to s.english)
|
|
149
150
|
fun atlasRubuAlHizb(r: QvpAtlasRubuAlHizb): Map<String, Any?> = mapOf("rubuAlHizb" to r.rubuAlHizb, "surah" to r.surah, "ayah" to r.ayah, "page" to r.page, "ayahKey" to r.ayahKey)
|
|
150
|
-
fun layout(l: QvpLayout): Map<String, Any?> = mapOf("scale" to l.scale, "
|
|
151
|
+
fun layout(l: QvpLayout): Map<String, Any?> = mapOf("scale" to l.scale, "offsetX" to l.offsetX, "offsetY" to l.offsetY, "contentW" to l.contentW, "contentH" to l.contentH, "lineSpacing" to l.lineSpacing, "fitScale" to l.fitScale, "fitX" to l.fitX, "fitY" to l.fitY, "lineDy" to l.lineDy.toList(),
|
|
151
152
|
"slots" to l.slotTop.indices.map { listOf(l.slotTop[it], l.slotBottom[it]) })
|
|
152
|
-
fun pageInfo(p: QvpPage): Map<String, Any?> = mapOf("page" to p.pageNo, "width" to p.width, "height" to p.height, "nLines" to p.nLines, "nAyahs" to p.nAyahs, "nWords" to p.nWords, "nPaths" to p.nPaths, "
|
|
153
|
-
"
|
|
153
|
+
fun pageInfo(p: QvpPage): Map<String, Any?> = mapOf("page" to p.pageNo, "width" to p.width, "height" to p.height, "nLines" to p.nLines, "nAyahs" to p.nAyahs, "nWords" to p.nWords, "nPaths" to p.nPaths, "nDecorations" to p.nDecorations,
|
|
154
|
+
"lineSpacing" to p.lineSpacing, "forms" to Form.entries.filter { it == Form.RASM_UTHMANI || p.hasForm(it) }.map { it.name.lowercase() })
|
|
154
155
|
fun selection(p: QvpPage): Map<String, Any?> {
|
|
155
156
|
val ws = p.selection()
|
|
156
157
|
return mapOf("words" to ws.toList(), "text" to (if (ws.isEmpty()) "" else p.text(Target.words(ws))), "citation" to (if (ws.isEmpty()) "" else p.citation(ws)),
|