@quran.ws/qvp-react-native 0.1.0 → 0.3.1

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.
@@ -1,10 +1,10 @@
1
- package net.quranpedia.qvp.rn
1
+ package ws.quran.qvp.rn
2
2
 
3
3
  import com.facebook.react.bridge.*
4
4
  import com.facebook.react.module.annotations.ReactModule
5
5
  import com.facebook.react.uimanager.UIManagerHelper
6
- import net.quranpedia.qvp.*
7
- import net.quranpedia.qvp.Target
6
+ import ws.quran.qvp.*
7
+ import ws.quran.qvp.Target
8
8
 
9
9
  /**
10
10
  * Promise-based queries on the page behind a `<QvpPageView />` (looked up by react tag) plus engine-wide
@@ -41,24 +41,26 @@ class QvpModule(private val ctx: ReactApplicationContext) : ReactContextBaseJava
41
41
  // ── page facts ──
42
42
  @ReactMethod fun info(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> Marshal.pageInfo(p) + mapOf("loadMs" to v.loadMs, "bytes" to v.pageBytes) }
43
43
  @ReactMethod fun words(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.words.map { Marshal.word(p, it) } }
44
- @ReactMethod fun word(tag: Int, idx: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.words.getOrNull(idx)?.let { Marshal.word(p, it) } }
44
+ @ReactMethod fun word(tag: Int, index: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.words.getOrNull(index)?.let { Marshal.word(p, it) } }
45
45
  @ReactMethod fun ayahs(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahs.map { Marshal.ayah(it) } }
46
46
  @ReactMethod fun lines(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.lines.map { Marshal.line(it) } }
47
- @ReactMethod fun decos(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.decos.map { Marshal.deco(it) } }
47
+ @ReactMethod fun decorations(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.decorations.map { Marshal.decoration(it) } }
48
48
  @ReactMethod fun findWord(tag: Int, s: Int, a: Int, w: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.findWord(s, a, w) }
49
- @ReactMethod fun resolve(tag: Int, target: Dynamic, promise: Promise) = withPage(tag, promise) { _, p -> p.resolve(tgt(target, p)).toList() }
50
- @ReactMethod fun wordForm(tag: Int, idx: Int, form: String?, promise: Promise) = withPage(tag, promise) { _, p -> p.wordForm(idx, Marshal.form(form)) }
49
+ @ReactMethod fun targetWords(tag: Int, target: Dynamic, promise: Promise) = withPage(tag, promise) { _, p -> p.targetWords(tgt(target, p)).toList() }
50
+ @ReactMethod fun wordForm(tag: Int, index: Int, form: String?, promise: Promise) = withPage(tag, promise) { _, p -> p.wordForm(index, Marshal.form(form)) }
51
51
  @ReactMethod fun hasForm(tag: Int, form: String?, promise: Promise) = withPage(tag, promise) { _, p -> p.hasForm(Marshal.form(form)) }
52
52
  @ReactMethod fun attachWords(tag: Int, json: String, promise: Promise) = withPage(tag, promise) { _, p -> p.attachWords(json) }
53
53
 
54
54
  // ── metadata ──
55
55
  @ReactMethod fun surahs(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.surahs().map { Marshal.surah(it) } }
56
56
  @ReactMethod fun divisions(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.divisions().map { Marshal.division(it) } }
57
+ @ReactMethod fun surahHeaders(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.surahHeaders().map { Marshal.surahHeader(it) } }
58
+ @ReactMethod fun surahHeadersView(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.surahHeadersView().map { Marshal.surahHeader(it) } }
57
59
  @ReactMethod fun ayahMarks(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahMarks().map { Marshal.ayahMark(it) } }
58
60
  @ReactMethod fun rosettes(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.rosettes().map { Marshal.rosette(it) } }
59
61
  @ReactMethod fun sajdahs(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.sajdahs().map { Marshal.sajdah(it) } }
60
62
  @ReactMethod fun ayahKeys(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahKeys().map { mapOf("surah" to it.first, "ayah" to it.second, "ayahKey" to "${it.first}:${it.second}") } }
61
- @ReactMethod fun ayahWordCount(tag: Int, s: Int, a: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahWordCount(s, a).let { mapOf("count" to it.first, "complete" to it.second) } }
63
+ @ReactMethod fun ayahWordCount(tag: Int, s: Int, a: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahWordCount(s, a).let { mapOf("count" to it.first, "isComplete" to it.second) } }
62
64
  @ReactMethod fun reciteMap(tag: Int, s: Int, a: Int, n: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.reciteMap(s, a, n)?.toList() }
63
65
  @ReactMethod fun wordLabel(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.wordLabel(i) }
64
66
  @ReactMethod fun ayahLabel(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.ayahLabel(i) }
@@ -68,21 +70,33 @@ class QvpModule(private val ctx: ReactApplicationContext) : ReactContextBaseJava
68
70
  val o = opt(opts); p.text(tgt(target, p), Marshal.form(o["form"]), (o["wordSep"] as? String) ?: " ", (o["lineSep"] as? String) ?: "\n") }
69
71
  @ReactMethod fun search(tag: Int, query: String, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { _, p ->
70
72
  val o = opt(opts)
71
- p.search(query, Marshal.form(o["form"], Form.SEARCH), Marshal.searchMode(o["mode"]), o["normalize"] != false, o["loose"] != false, (o["limit"] as? Number)?.toInt() ?: 0).map { Marshal.match(it) } }
73
+ p.search(query, Marshal.form(o["form"], Form.SEARCH), Marshal.searchMode(o["mode"]), o["normalize"] != false, o["looseMatch"] != false, (o["limit"] as? Number)?.toInt() ?: 0).map { Marshal.match(it) } }
72
74
  @ReactMethod fun citation(tag: Int, words: ReadableArray, promise: Promise) = withPage(tag, promise) { _, p -> p.citation(ints(words)) }
73
- @ReactMethod fun arabic(kind: String, s: String, promise: Promise) = ui(promise) { when (kind) { "strip" -> QvpEngine.strip(s); "fold" -> QvpEngine.fold(s); "normalize" -> QvpEngine.normalize(s); "loose", "looseKey" -> QvpEngine.looseKey(s); else -> s } }
75
+ @ReactMethod fun arabic(op: String, s: String, promise: Promise) = ui(promise) { when (op) { "strip" -> QvpEngine.strip(s); "fold" -> QvpEngine.fold(s); "normalize" -> QvpEngine.normalize(s); "loose", "looseKey" -> QvpEngine.looseKey(s); else -> s } }
74
76
 
75
77
  // ── hit testing / layout (the view already does gestures; these are for scroll-into-view etc.) ──
76
- @ReactMethod fun hitTestViewEx(tag: Int, x: Double, y: Double, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { v, p ->
78
+ private fun hitOptions(o: Map<String, Any?>) = QvpHitOptions((o["maxDistance"] as? Number)?.toFloat() ?: QvpDefaults.TAP_DISTANCE, (o["gapBias"] as? Number)?.toFloat() ?: QvpDefaults.GAP_BIAS, o["preferExact"] != false)
79
+ /** Exact outline hit at a point in page units. */
80
+ @ReactMethod fun hitTestExact(tag: Int, x: Double, y: Double, promise: Promise) = withPage(tag, promise) { _, p -> p.hitTestExact(x.toFloat(), y.toFloat())?.let { Marshal.hit(p, it) } }
81
+ /** Gap-aware hit at a point in page units. */
82
+ @ReactMethod fun hitTest(tag: Int, x: Double, y: Double, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { _, p -> p.hitTest(x.toFloat(), y.toFloat(), hitOptions(opt(opts)))?.let { Marshal.hit(p, it) } }
83
+ /** Exact outline hit at a point in view dp (density and pan/zoom removed here, the layout in the engine). */
84
+ @ReactMethod fun hitTestExactView(tag: Int, x: Double, y: Double, promise: Promise) = withPage(tag, promise) { v, p -> val d = v.resources.displayMetrics.density
85
+ p.hitTestExactView(((x * d).toFloat() - v.inner.viewOx) / v.inner.viewScale, ((y * d).toFloat() - v.inner.viewOy) / v.inner.viewScale)?.let { Marshal.hit(p, it) } }
86
+ @ReactMethod fun hitTestView(tag: Int, x: Double, y: Double, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { v, p ->
77
87
  val o = opt(opts); val d = v.resources.displayMetrics.density
78
- p.hitTestViewEx(((x * d).toFloat() - v.inner.viewOx) / v.inner.viewScale, ((y * d).toFloat() - v.inner.viewOy) / v.inner.viewScale, QvpHitOptions((o["maxDistance"] as? Number)?.toFloat() ?: 6f, (o["gapBias"] as? Number)?.toFloat() ?: 0.6f, o["exactFirst"] != false))?.let { Marshal.hit(p, it) } }
79
- @ReactMethod fun wordBoxView(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> val d = v.resources.displayMetrics.density
80
- p.wordBoxView(i)?.let { b -> mapOf("x0" to (v.inner.viewOx + b[0] * v.inner.viewScale) / d, "y0" to (v.inner.viewOy + b[1] * v.inner.viewScale) / d, "x1" to (v.inner.viewOx + b[2] * v.inner.viewScale) / d, "y1" to (v.inner.viewOy + b[3] * v.inner.viewScale) / d) } }
88
+ p.hitTestView(((x * d).toFloat() - v.inner.viewOx) / v.inner.viewScale, ((y * d).toFloat() - v.inner.viewOy) / v.inner.viewScale, hitOptions(o))?.let { Marshal.hit(p, it) } }
89
+ @ReactMethod fun wordBoundsView(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> val d = v.resources.displayMetrics.density
90
+ p.wordBoundsView(i)?.let { b -> mapOf("x0" to (v.inner.viewOx + b[0] * v.inner.viewScale) / d, "y0" to (v.inner.viewOy + b[1] * v.inner.viewScale) / d, "x1" to (v.inner.viewOx + b[2] * v.inner.viewScale) / d, "y1" to (v.inner.viewOy + b[3] * v.inner.viewScale) / d) } }
81
91
  @ReactMethod fun currentLayout(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.currentLayout?.let { Marshal.layout(it) } }
82
92
  @ReactMethod fun relayout(tag: Int, promise: Promise) = withPage(tag, promise) { v, _ -> v.inner.relayout(); v.resetView(); null }
83
93
  @ReactMethod fun resetView(tag: Int, promise: Promise) = withPage(tag, promise) { v, _ -> v.resetView(); null }
84
- @ReactMethod fun gapToFill(pw: Double, ph: Double, lines: Int, vw: Double, vh: Double, max: Double, promise: Promise) = ui(promise) { QvpEngine.gapToFill(pw.toFloat(), ph.toFloat(), lines, vw.toFloat(), vh.toFloat(), max.toFloat()) }
85
- @ReactMethod fun wastedFraction(pw: Double, ph: Double, vw: Double, vh: Double, promise: Promise) = ui(promise) { QvpEngine.wastedFraction(pw.toFloat(), ph.toFloat(), vw.toFloat(), vh.toFloat()) }
94
+ /** The leading that fills this view's viewport, from the spec the view lays out with. */
95
+ @ReactMethod fun layoutLineSpacingToFill(tag: Int, max: Double, promise: Promise) = withPage(tag, promise) { v, p -> p.layoutLineSpacingToFill(v.inner.layoutSpec(), max.toFloat()) }
96
+ @ReactMethod fun layoutWastedFraction(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.layoutWastedFraction(v.inner.layoutSpec()) }
97
+ /** The page's height at the printed pitch for the spec this view lays out with. */
98
+ @ReactMethod fun layoutPrintedHeight(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.layoutPrintedHeight(v.inner.layoutSpec()) }
99
+ @ReactMethod fun grid(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> mapOf("lines" to p.grid.lines, "lineSpacing" to p.grid.lineSpacing) }
86
100
  @ReactMethod fun stats(tag: Int, promise: Promise) = ui(promise) { view(tag)?.stats() }
87
101
  @ReactMethod fun invalidate(tag: Int, promise: Promise) = ui(promise) { view(tag)?.inner?.invalidate(); null }
88
102
 
@@ -90,26 +104,26 @@ class QvpModule(private val ctx: ReactApplicationContext) : ReactContextBaseJava
90
104
  @ReactMethod fun select(tag: Int, anchor: Int, focus: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.select(anchor, focus); v.inner.invalidate(); Marshal.selection(p) }
91
105
  @ReactMethod fun clearSelection(tag: Int, promise: Promise) = withPage(tag, promise) { v, _ -> v.inner.clearSelection(); null }
92
106
  @ReactMethod fun selection(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> Marshal.selection(p) }
93
- @ReactMethod fun selectionText(tag: Int, form: String?, citation: Boolean, promise: Promise) = withPage(tag, promise) { _, p -> p.selectionText(Marshal.form(form), citation) }
107
+ @ReactMethod fun selectionText(tag: Int, form: String?, includeCitation: Boolean, promise: Promise) = withPage(tag, promise) { _, p -> p.selectionText(Marshal.form(form), includeCitation) }
94
108
 
95
109
  // ── memorisation (stepwise ops; `mask` / `reveal` props hold the declarative part) ──
96
- @ReactMethod fun revealNext(tag: Int, n: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.revealNext(n).also { v.inner.invalidate() } }
97
- @ReactMethod fun hideBack(tag: Int, n: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.hideBack(n).also { v.inner.invalidate() } }
98
- @ReactMethod fun revealWord(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.revealWord(i).also { v.inner.invalidate() } }
99
- @ReactMethod fun hideWord(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.hideWord(i).also { v.inner.invalidate() } }
100
- @ReactMethod fun revealAll(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.revealAll(); v.inner.invalidate(); null }
101
- @ReactMethod fun hideAll(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.hideAll(); v.inner.invalidate(); null }
110
+ @ReactMethod fun unmaskNext(tag: Int, n: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.unmaskNext(n).also { v.inner.invalidate() } }
111
+ @ReactMethod fun maskBack(tag: Int, n: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.maskBack(n).also { v.inner.invalidate() } }
112
+ @ReactMethod fun unmaskWord(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.unmaskWord(i).also { v.inner.invalidate() } }
113
+ @ReactMethod fun maskWord(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.maskWord(i).also { v.inner.invalidate() } }
114
+ @ReactMethod fun unmaskAll(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.unmaskAll(); v.inner.invalidate(); null }
115
+ @ReactMethod fun maskAll(tag: Int, promise: Promise) = withPage(tag, promise) { v, p -> p.maskAll(); v.inner.invalidate(); null }
102
116
  @ReactMethod fun maskHidden(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.maskHidden().toList() }
103
117
  @ReactMethod fun maskWords(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.maskWords().toList() }
104
- @ReactMethod fun revealSteps(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.revealSteps() }
105
- @ReactMethod fun revealAt(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.revealAt()?.toDouble() }
118
+ @ReactMethod fun revealStepCount(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.revealStepCount() }
119
+ @ReactMethod fun revealPosition(tag: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.revealPosition()?.toDouble() }
106
120
  @ReactMethod fun revealStepOf(tag: Int, i: Int, promise: Promise) = withPage(tag, promise) { _, p -> p.revealStepOf(i).toDouble() }
107
121
 
108
122
  // ── crop ──
109
- @ReactMethod fun cropBox(tag: Int, target: Dynamic, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { _, p ->
110
- val o = opt(opts); p.cropBox(tgt(target, p), (o["pad"] as? Number)?.toFloat() ?: 2f, o["keepAyahMarks"] != false)?.let { Marshal.cropBox(it) } }
123
+ @ReactMethod fun cropBounds(tag: Int, target: Dynamic, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { _, p ->
124
+ val o = opt(opts); p.cropBounds(tgt(target, p), (o["pad"] as? Number)?.toFloat() ?: QvpDefaults.CROP_PAD, o["keepAyahMarks"] != false)?.let { Marshal.cropBounds(it) } }
111
125
  @ReactMethod fun cropSvg(tag: Int, target: Dynamic, opts: ReadableMap?, promise: Promise) = withPage(tag, promise) { _, p ->
112
- val o = opt(opts); p.cropSvg(tgt(target, p), (o["pad"] as? Number)?.toFloat() ?: 2f, o["keepAyahMarks"] != false, Marshal.color(o["background"], 0)) }
126
+ val o = opt(opts); p.cropSvg(tgt(target, p), (o["pad"] as? Number)?.toFloat() ?: QvpDefaults.CROP_PAD, o["keepAyahMarks"] != false, Marshal.color(o["background"], 0)) }
113
127
 
114
128
  // ── atlas ──
115
129
  @ReactMethod fun loadAtlas(uri: String, promise: Promise) = ui(promise) {
@@ -128,21 +142,27 @@ class QvpModule(private val ctx: ReactApplicationContext) : ReactContextBaseJava
128
142
  @ReactMethod fun freeAtlas(id: Int, promise: Promise) = ui(promise) { atlases.remove(id)?.close(); atlasByUri.entries.removeAll { it.value == id }; null }
129
143
  @ReactMethod fun atlasPageOf(id: Int, s: Int, a: Int, promise: Promise) = withAtlas(id, promise) { it.pageOf(s, a) }
130
144
  @ReactMethod fun atlasPageRange(id: Int, page: Int, promise: Promise) = withAtlas(id, promise) { it.pageRange(page)?.let { r -> mapOf("first" to mapOf("surah" to r.first.first, "ayah" to r.first.second), "last" to mapOf("surah" to r.second.first, "ayah" to r.second.second)) } }
131
- @ReactMethod fun atlasPages(id: Int, promise: Promise) = withAtlas(id, promise) { it.pages() }
145
+ @ReactMethod fun atlasPageCount(id: Int, promise: Promise) = withAtlas(id, promise) { it.pageCount() }
132
146
  @ReactMethod fun atlasSurah(id: Int, n: Int, promise: Promise) = withAtlas(id, promise) { it.surah(n)?.let { s -> Marshal.atlasSurah(s) } }
133
147
  @ReactMethod fun atlasSurahs(id: Int, promise: Promise) = withAtlas(id, promise) { it.surahs().map { s -> Marshal.atlasSurah(s) } }
134
148
  @ReactMethod fun atlasPageOfSurah(id: Int, n: Int, promise: Promise) = withAtlas(id, promise) { it.pageOfSurah(n) }
135
- @ReactMethod fun atlasDivision(id: Int, kind: String, n: Int, promise: Promise) = withAtlas(id, promise) { it.division(Marshal.division(kind), n)?.let { r -> Marshal.atlasRubuAlHizb(r) } }
136
- @ReactMethod fun atlasDivisionAt(id: Int, kind: String, s: Int, a: Int, promise: Promise) = withAtlas(id, promise) { it.divisionAt(Marshal.division(kind), s, a) }
137
- @ReactMethod fun atlasJuzAt(id: Int, s: Int, a: Int, promise: Promise) = withAtlas(id, promise) { it.juzAt(s, a) }
149
+ @ReactMethod fun atlasDivision(id: Int, division: String, n: Int, promise: Promise) = withAtlas(id, promise) { it.division(Marshal.division(division), n)?.let { r -> Marshal.atlasRubuAlHizb(r) } }
150
+ @ReactMethod fun atlasDivisionOf(id: Int, division: String, s: Int, a: Int, promise: Promise) = withAtlas(id, promise) { it.divisionOf(Marshal.division(division), s, a) }
151
+ @ReactMethod fun atlasJuzOf(id: Int, s: Int, a: Int, promise: Promise) = withAtlas(id, promise) { it.juzOf(s, a) }
138
152
  @ReactMethod fun atlasPagesOfJuz(id: Int, n: Int, promise: Promise) = withAtlas(id, promise) { it.pagesOfJuz(n)?.let { r -> listOf(r.first, r.second) } }
139
- @ReactMethod fun atlasFindSurah(id: Int, text: String, promise: Promise) = withAtlas(id, promise) { it.findSurah(text).map { s -> Marshal.atlasSurah(s) } }
153
+ @ReactMethod fun atlasSearchSurahs(id: Int, text: String, promise: Promise) = withAtlas(id, promise) { it.searchSurahs(text).map { s -> Marshal.atlasSurah(s) } }
140
154
 
141
155
  // ── names ──
142
156
  @ReactMethod fun markName(m: Int, promise: Promise) = ui(promise) { QvpEngine.markName(m) }
143
157
  @ReactMethod fun markFromName(s: String, promise: Promise) = ui(promise) { QvpEngine.markFromName(s) }
144
- override fun getConstants(): Map<String, Any> = mapOf("version" to QvpEngine.version(), "marks" to QVP_MARKS,
145
- "kinds" to (0..4).map { QvpEngine.kindName(it) }, "families" to (0..7).map { QvpEngine.familyName(it) }, "categories" to (0..8).map { QvpEngine.categoryName(it) })
158
+ @ReactMethod fun markCategory(m: Int, promise: Promise) = ui(promise) { QvpEngine.markCategory(m) }
159
+ @ReactMethod fun engineName(promise: Promise) = ui(promise) { QvpEngine.engineName() }
160
+ /** Every name table, read from the engine; the JavaScript side resolves names with these. */
161
+ override fun getConstants(): Map<String, Any> = mapOf("version" to QvpEngine.version(), "formatVersion" to QvpEngine.formatVersion(),
162
+ "marks" to QvpEngine.names(QvpEngine.Names.MARK), "kinds" to QvpEngine.names(QvpEngine.Names.KIND),
163
+ "families" to QvpEngine.names(QvpEngine.Names.FAMILY), "categories" to QvpEngine.names(QvpEngine.Names.CATEGORY),
164
+ "decorations" to QvpEngine.names(QvpEngine.Names.DECORATION), "divisions" to QvpEngine.names(QvpEngine.Names.DIVISION),
165
+ "places" to QvpEngine.names(QvpEngine.Names.PLACE))
146
166
 
147
167
  override fun invalidate() { atlases.values.forEach { it.close() }; atlases.clear(); atlasByUri.clear(); super.invalidate() }
148
168
  companion object { const val NAME = "QvpModule" }
@@ -1,4 +1,4 @@
1
- package net.quranpedia.qvp.rn
1
+ package ws.quran.qvp.rn
2
2
 
3
3
  import com.facebook.react.ReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
@@ -1,11 +1,12 @@
1
- package net.quranpedia.qvp.rn
1
+ package ws.quran.qvp.rn
2
2
 
3
3
  import com.facebook.react.bridge.ReadableArray
4
4
  import com.facebook.react.bridge.ReadableMap
5
5
  import com.facebook.react.uimanager.SimpleViewManager
6
6
  import com.facebook.react.uimanager.ThemedReactContext
7
7
  import com.facebook.react.uimanager.annotations.ReactProp
8
- import net.quranpedia.qvp.QvpColor
8
+ import ws.quran.qvp.QvpColor
9
+ import ws.quran.qvp.QvpDefaults
9
10
 
10
11
  /** `<QvpPageView />`: props are marshalled onto [QvpRnPageView]; [onAfterUpdateTransaction] commits them in one pass. */
11
12
  class QvpPageViewManager : SimpleViewManager<QvpRnPageView>() {
@@ -27,14 +28,16 @@ class QvpPageViewManager : SimpleViewManager<QvpRnPageView>() {
27
28
  @ReactProp(name = "padBottom", defaultFloat = 0f) fun setPadBottom(v: QvpRnPageView, f: Float) = v.setPadBottomDp(f)
28
29
  @ReactProp(name = "padSide", defaultFloat = 0f) fun setPadSide(v: QvpRnPageView, f: Float) = v.setPadSideDp(f)
29
30
  @ReactProp(name = "lineSpacing", defaultFloat = 1f) fun setLineSpacing(v: QvpRnPageView, f: Float) = v.setLineSpacingProp(f)
30
- @ReactProp(name = "lineGap", defaultFloat = 0f) fun setLineGap(v: QvpRnPageView, f: Float) = v.setLineGapProp(f)
31
31
  @ReactProp(name = "fillHeight", defaultBoolean = false) fun setFillHeight(v: QvpRnPageView, b: Boolean) = v.setFillHeightProp(b)
32
+ @ReactProp(name = "surahFrames", defaultBoolean = true) fun setSurahFrames(v: QvpRnPageView, b: Boolean) = v.setSurahFramesProp(b)
32
33
  @ReactProp(name = "paperColor") fun setPaperColor(v: QvpRnPageView, s: String?) { v.paperColor = if (s.isNullOrBlank()) 0 else QvpColor.argb(QvpColor.parse(s)); v.invalidate() }
33
- @ReactProp(name = "defaultInk") fun setDefaultInk(v: QvpRnPageView, s: String?) { v.defaultInkProp = s }
34
+ @ReactProp(name = "defaultInk") fun setDefaultColor(v: QvpRnPageView, s: String?) { v.defaultInkProp = s }
34
35
  @ReactProp(name = "selectionBand") fun setSelectionBand(v: QvpRnPageView, s: String?) { if (!s.isNullOrBlank()) v.selectionBand = QvpColor.parse(s) }
35
36
  @ReactProp(name = "selectionEnabled", defaultBoolean = true) fun setSelectionEnabled(v: QvpRnPageView, b: Boolean) { v.selectionEnabled = b }
36
37
  @ReactProp(name = "zoomEnabled", defaultBoolean = true) fun setZoomEnabled(v: QvpRnPageView, b: Boolean) { v.zoomEnabled = b }
37
- @ReactProp(name = "hitMaxDistance", defaultFloat = 6f) fun setHitMaxDistance(v: QvpRnPageView, f: Float) { v.hitOptions = v.hitOptions.copy(maxDistance = f) }
38
+ @ReactProp(name = "zoomMode") fun setZoomMode(v: QvpRnPageView, s: String?) { v.zoomModeProp = s }
39
+ @ReactProp(name = "zoomStep", defaultInt = -1) fun setZoomStep(v: QvpRnPageView, i: Int) { v.zoomStepProp = i }
40
+ @ReactProp(name = "hitMaxDistance", defaultFloat = QvpDefaults.TAP_DISTANCE) fun setHitMaxDistance(v: QvpRnPageView, f: Float) { v.hitOptions = v.hitOptions.copy(maxDistance = f) }
38
41
  @ReactProp(name = "theme") fun setTheme(v: QvpRnPageView, m: ReadableMap?) { v.themeProp = Marshal.plain(m) }
39
42
  @ReactProp(name = "styles") fun setStyles(v: QvpRnPageView, a: ReadableArray?) { v.stylesProp = Marshal.plain(a) }
40
43
  @ReactProp(name = "highlights") fun setHighlights(v: QvpRnPageView, a: ReadableArray?) { v.highlightsProp = Marshal.plain(a) }
@@ -43,6 +46,6 @@ class QvpPageViewManager : SimpleViewManager<QvpRnPageView>() {
43
46
 
44
47
  companion object {
45
48
  const val NAME = "QvpPageView"
46
- val EVENTS = listOf("onWordTap", "onDecoTap", "onEmptyTap", "onSelectionChanged", "onPageLoad", "onRevealChanged", "onError")
49
+ val EVENTS = listOf("onWordTap", "onDecorationTap", "onEmptyTap", "onSelectionChanged", "onPageLoad", "onRevealChanged", "onSwipe", "onZoomChanged", "onError")
47
50
  }
48
51
  }
@@ -1,4 +1,4 @@
1
- package net.quranpedia.qvp.rn
1
+ package ws.quran.qvp.rn
2
2
 
3
3
  import android.util.Base64
4
4
  import android.util.Log
@@ -8,8 +8,8 @@ import com.facebook.react.uimanager.ThemedReactContext
8
8
  import com.facebook.react.uimanager.UIManagerHelper
9
9
  import com.facebook.react.uimanager.events.Event
10
10
  import android.widget.FrameLayout
11
- import net.quranpedia.qvp.*
12
- import net.quranpedia.qvp.Target
11
+ import ws.quran.qvp.*
12
+ import ws.quran.qvp.Target
13
13
  import java.io.File
14
14
 
15
15
  /**
@@ -36,14 +36,11 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
36
36
  val w = r - l; val h = b - t
37
37
  inner.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY))
38
38
  inner.layout(0, 0, w, h)
39
- centre()
40
- }
41
- /** Fit + centre: the library's resetView fits by height and pins the pan at x = 0; centre the pan horizontally (pan is host state). */
42
- fun resetView() { inner.resetView(); centre() }
43
- private fun centre() {
44
- val l = page?.currentLayout ?: return
45
- inner.viewOx = ((inner.width - l.contentW * inner.viewScale) / 2f).coerceAtLeast(0f); inner.invalidate()
39
+ // A size change reaches the child's onSizeChanged, which lays the page out again and
40
+ // fits it; a pass that changed nothing leaves the reader's view where it is.
46
41
  }
42
+ /** Fit and centre, as the engine's layout says. */
43
+ fun resetView() { inner.resetView() }
47
44
 
48
45
  // ── desired props (set by the manager) ──
49
46
  var pageUri: String? = null
@@ -68,14 +65,18 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
68
65
  private var appliedReveal: Map<String, Any?>? = null // without "at"
69
66
  private var appliedRevealAt: Long? = null
70
67
  private var appliedInk: Int? = null
71
- var revealSteps = 0; private set
68
+ var revealStepCount = 0; private set
72
69
  var loadMs = 0.0; private set
73
70
  var pageBytes = 0; private set
74
71
 
75
72
  init {
76
73
  inner.onWordTap = { w, h -> page?.let { p -> emit("onWordTap", mapOf("word" to Marshal.word(p, w), "hit" to Marshal.hit(p, h))) } }
77
- inner.onDecoTap = { d, h -> page?.let { p -> emit("onDecoTap", mapOf("deco" to Marshal.deco(d), "hit" to Marshal.hit(p, h))) } }
74
+ inner.onDecorationTap = { d, h -> page?.let { p -> emit("onDecorationTap", mapOf("decoration" to Marshal.decoration(d), "hit" to Marshal.hit(p, h))) } }
78
75
  inner.onEmptyTap = { emit("onEmptyTap", emptyMap()) }
76
+ // The engine says a flick turns the page and by how many, in reading order. Which page
77
+ // that is belongs to the app, so this is an event and not a page change.
78
+ inner.onSwipe = { pages -> emit("onSwipe", mapOf("pages" to pages)) }
79
+ inner.onZoomChanged = { z -> emit("onZoomChanged", mapOf("mode" to z.mode.name.lowercase(), "step" to z.step, "zoom" to z.zoom)) }
79
80
  inner.onSelectionChanged = { page?.let { p -> emit("onSelectionChanged", Marshal.selection(p)) } }
80
81
  }
81
82
 
@@ -89,8 +90,31 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
89
90
  fun setPadBottomDp(v: Float) { inner.padBottom = v * density; layoutDirty = true }
90
91
  fun setPadSideDp(v: Float) { inner.padSide = v * density; layoutDirty = true }
91
92
  fun setLineSpacingProp(v: Float) { inner.lineSpacing = v; layoutDirty = true }
92
- fun setLineGapProp(v: Float) { inner.lineGap = v; layoutDirty = true }
93
+
94
+ // ── the reader's zoom control ──
95
+ // What a pinch does to the page: reflow onto the page's own steps (the default), reflow to
96
+ // the zoom the fingers ask for, or magnify the printed page. The engine owns the policy;
97
+ // these props only say which one the app wants and which step to sit on.
98
+ var zoomModeProp: String? = null
99
+ var zoomStepProp: Int = -1
100
+ /** The zooms this page's steps land on, for an app drawing its own size control. */
101
+ val zoomSteps: FloatArray get() = page?.zoomSteps(inner.layoutSpec()) ?: FloatArray(0)
102
+
103
+ private fun commitZoom() {
104
+ val p = page ?: return
105
+ if (!p.isOpen) return
106
+ zoomModeProp?.let { name ->
107
+ val mode = when (name) {
108
+ "continuous" -> QvpZoomMode.CONTINUOUS
109
+ "magnify" -> QvpZoomMode.MAGNIFY
110
+ else -> QvpZoomMode.STEPPED
111
+ }
112
+ if (mode != inner.zoomMode) inner.zoomMode = mode
113
+ }
114
+ if (zoomStepProp >= 0 && zoomStepProp != inner.zoom.step) inner.zoomToStep(zoomStepProp)
115
+ }
93
116
  fun setFillHeightProp(v: Boolean) { inner.fillHeight = v; layoutDirty = true }
117
+ fun setSurahFramesProp(v: Boolean) { inner.surahFrames = v; layoutDirty = true }
94
118
 
95
119
  // ── bytes ──
96
120
  private fun readUri(uri: String): ByteArray {
@@ -114,6 +138,7 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
114
138
  applyInk(p); applyTheme(p); applyStyles(p); applyHighlights(p); applyMask(p); applyReveal(p)
115
139
  }
116
140
  if (layoutDirty) { layoutDirty = false; inner.relayout(); resetView() }
141
+ commitZoom()
117
142
  inner.invalidate()
118
143
  } catch (e: Exception) {
119
144
  Log.e("QvpRn", "commit failed", e)
@@ -125,7 +150,7 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
125
150
  val key = pageBase64?.let { "b64:" + it.hashCode() } ?: pageUri
126
151
  if (key == loadedKey) return
127
152
  // drop everything bound to the old page: handles die with it
128
- themeHandle = 0; appliedTheme = null; styleHandles.clear(); highlightHandles.clear(); appliedMask = null; appliedReveal = null; appliedRevealAt = null; appliedInk = null; revealSteps = 0
153
+ themeHandle = 0; appliedTheme = null; styleHandles.clear(); highlightHandles.clear(); appliedMask = null; appliedReveal = null; appliedRevealAt = null; appliedInk = null; revealStepCount = 0
129
154
  page?.close(); page = null; loadedKey = key
130
155
  if (key == null) return
131
156
  val bytes = pageBase64?.let { Base64.decode(it, Base64.DEFAULT) } ?: readUri(pageUri!!)
@@ -138,12 +163,12 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
138
163
  }
139
164
 
140
165
  private fun applyInk(p: QvpPage) {
141
- val ink = Marshal.color(defaultInkProp, 0x231f20ff.toInt())
142
- if (appliedInk != ink) { appliedInk = ink; p.setDefaultInk(ink) }
166
+ val ink = Marshal.color(defaultInkProp, QvpDefaults.INK)
167
+ if (appliedInk != ink) { appliedInk = ink; p.setDefaultColor(ink) }
143
168
  }
144
169
  private fun applyTheme(p: QvpPage) {
145
170
  if (themeProp == appliedTheme) return
146
- if (themeHandle != 0) { p.unstyle(themeHandle); themeHandle = 0 }
171
+ if (themeHandle != 0) { p.removeStyle(themeHandle); themeHandle = 0 }
147
172
  appliedTheme = themeProp
148
173
  themeProp?.let { themeHandle = p.theme(Marshal.theme(it)) }
149
174
  }
@@ -154,13 +179,13 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
154
179
  }
155
180
  private fun applyStyles(p: QvpPage) {
156
181
  val want = entries(stylesProp)
157
- for (id in styleHandles.keys.toList()) if (!want.containsKey(id)) { p.unstyle(styleHandles.remove(id)!!.handle) }
182
+ for (id in styleHandles.keys.toList()) if (!want.containsKey(id)) { p.removeStyle(styleHandles.remove(id)!!.handle) }
158
183
  for ((id, e) in want) {
159
184
  val old = styleHandles[id]
160
185
  if (old != null && old.entry == e) continue
161
186
  val sameShape = old != null && old.entry["selector"] == e["selector"] && old.entry["target"] == e["target"] && old.entry["layer"] == e["layer"] && old.entry["hide"] == e["hide"] && e["hide"] != true
162
- if (sameShape) { p.restyle(old!!.handle, Marshal.color(e["color"], 0), (e["ms"] as? Number)?.toInt() ?: 0); styleHandles[id] = Rule(old.handle, e); continue }
163
- if (old != null) p.unstyle(old.handle)
187
+ if (sameShape) { p.recolorStyle(old!!.handle, Marshal.color(e["color"], 0), (e["ms"] as? Number)?.toInt() ?: 0); styleHandles[id] = Rule(old.handle, e); continue }
188
+ if (old != null) p.removeStyle(old.handle)
164
189
  val ms = (e["ms"] as? Number)?.toInt() ?: 0
165
190
  val layer = (e["layer"] as? Number)?.toInt() ?: QvpLayer.BASE
166
191
  val color = Marshal.color(e["color"], 0)
@@ -175,14 +200,14 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
175
200
  }
176
201
  private fun applyHighlights(p: QvpPage) {
177
202
  val want = entries(highlightsProp)
178
- for (id in highlightHandles.keys.toList()) if (!want.containsKey(id)) { p.unhighlight(highlightHandles.remove(id)!!.handle) }
203
+ for (id in highlightHandles.keys.toList()) if (!want.containsKey(id)) { p.removeHighlight(highlightHandles.remove(id)!!.handle) }
179
204
  for ((id, e) in want) {
180
205
  val old = highlightHandles[id]
181
206
  if (old != null && old.entry == e) continue
182
207
  val style = Marshal.highlightStyle(e["style"])
183
208
  if (old == null) { val h = p.highlight(Marshal.target(e["target"], p), style); if (h != 0) highlightHandles[id] = Rule(h, e); continue }
184
209
  if (old.entry["style"] != e["style"]) p.restyleHighlight(old.handle, style)
185
- if (old.entry["target"] != e["target"]) p.rehighlight(old.handle, Marshal.target(e["target"], p))
210
+ if (old.entry["target"] != e["target"]) p.moveHighlight(old.handle, Marshal.target(e["target"], p))
186
211
  highlightHandles[id] = Rule(old.handle, e)
187
212
  }
188
213
  }
@@ -191,19 +216,20 @@ class QvpRnPageView(private val ctx: ThemedReactContext) : FrameLayout(ctx) {
191
216
  appliedMask = maskProp
192
217
  p.unmask()
193
218
  val m = maskProp ?: return
194
- val paper = Marshal.color(m["blockColor"], 0xd9d4c8ff.toInt())
195
- p.maskOptions(paper, (m["padX"] as? Number)?.toFloat() ?: 0.6f, (m["padY"] as? Number)?.toFloat() ?: 0.6f, (m["radius"] as? Number)?.toFloat() ?: 0.8f, m["reverse"] == true)
196
- p.mask(Marshal.target(m["target"], p), Marshal.maskMode(m["mode"]))
219
+ val paper = Marshal.color(m["blockColor"], QvpDefaults.MASK_BLOCK)
220
+ p.maskOptions(paper, (m["padX"] as? Number)?.toFloat() ?: QvpDefaults.MASK_PAD, (m["padY"] as? Number)?.toFloat() ?: QvpDefaults.MASK_PAD, (m["radius"] as? Number)?.toFloat() ?: QvpDefaults.MASK_RADIUS, m["reverse"] == true)
221
+ val from = m["from"] as? Number
222
+ if (from != null) p.maskFrom(from.toInt(), Marshal.maskMode(m["mode"])) else p.mask(Marshal.target(m["target"], p), Marshal.maskMode(m["mode"]))
197
223
  }
198
224
  private fun applyReveal(p: QvpPage) {
199
225
  val r = revealProp
200
226
  val cfg = r?.filterKeys { it != "at" }
201
227
  if (cfg != appliedReveal) {
202
228
  appliedReveal = cfg; appliedRevealAt = null
203
- if (cfg == null) { p.revealStop(); revealSteps = 0; emit("onRevealChanged", mapOf("steps" to 0, "at" to null)); return }
204
- revealSteps = p.revealStart((cfg["lit"] as? Number)?.toInt() ?: 1, cfg["byAyah"] == true, Marshal.color(cfg["grey"], 0xc9c4b8ff.toInt()), Marshal.color(cfg["ink"], appliedInk ?: 0x231f20ff.toInt()),
229
+ if (cfg == null) { p.revealStop(); revealStepCount = 0; emit("onRevealChanged", mapOf("steps" to 0, "at" to null)); return }
230
+ revealStepCount = p.revealStart((cfg["lit"] as? Number)?.toInt() ?: QvpDefaults.REVEAL_LIT, cfg["byAyah"] == true, Marshal.color(cfg["grey"], QvpDefaults.REVEAL_GREY), Marshal.color(cfg["ink"], appliedInk ?: QvpDefaults.INK),
205
231
  cfg["ayahMarks"] != false, (cfg["ms"] as? Number)?.toInt() ?: 0)
206
- emit("onRevealChanged", mapOf("steps" to revealSteps, "at" to -1))
232
+ emit("onRevealChanged", mapOf("steps" to revealStepCount, "at" to -1))
207
233
  }
208
234
  if (r == null) return
209
235
  val at = (r["at"] as? Number)?.toLong() ?: -1L
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quran.ws/qvp-react-native",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "description": "React Native wrapper for the QVP vector mushaf engine (Android; iOS TODO). Thin marshalling over the Kotlin library, which is JNI over qvp.h.",
5
5
  "main": "src/index.tsx",
6
6
  "types": "src/index.tsx",