wolfy-reader 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 (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -0
  3. package/dist/core/book.d.ts +75 -0
  4. package/dist/core/book.d.ts.map +1 -0
  5. package/dist/core/book.js +2 -0
  6. package/dist/core/book.js.map +1 -0
  7. package/dist/core/errors.d.ts +17 -0
  8. package/dist/core/errors.d.ts.map +1 -0
  9. package/dist/core/errors.js +29 -0
  10. package/dist/core/errors.js.map +1 -0
  11. package/dist/core/format.d.ts +13 -0
  12. package/dist/core/format.d.ts.map +1 -0
  13. package/dist/core/format.js +2 -0
  14. package/dist/core/format.js.map +1 -0
  15. package/dist/core/graphemes.d.ts +36 -0
  16. package/dist/core/graphemes.d.ts.map +1 -0
  17. package/dist/core/graphemes.js +62 -0
  18. package/dist/core/graphemes.js.map +1 -0
  19. package/dist/core/index.d.ts +11 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +6 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/lookup.d.ts +10 -0
  24. package/dist/core/lookup.d.ts.map +1 -0
  25. package/dist/core/lookup.js +16 -0
  26. package/dist/core/lookup.js.map +1 -0
  27. package/dist/core/open.d.ts +12 -0
  28. package/dist/core/open.d.ts.map +1 -0
  29. package/dist/core/open.js +23 -0
  30. package/dist/core/open.js.map +1 -0
  31. package/dist/core/position.d.ts +121 -0
  32. package/dist/core/position.d.ts.map +1 -0
  33. package/dist/core/position.js +327 -0
  34. package/dist/core/position.js.map +1 -0
  35. package/dist/core/reading-text.d.ts +77 -0
  36. package/dist/core/reading-text.d.ts.map +1 -0
  37. package/dist/core/reading-text.js +144 -0
  38. package/dist/core/reading-text.js.map +1 -0
  39. package/dist/core/source.d.ts +17 -0
  40. package/dist/core/source.d.ts.map +1 -0
  41. package/dist/core/source.js +39 -0
  42. package/dist/core/source.js.map +1 -0
  43. package/dist/core/storage.d.ts +7 -0
  44. package/dist/core/storage.d.ts.map +1 -0
  45. package/dist/core/storage.js +2 -0
  46. package/dist/core/storage.js.map +1 -0
  47. package/dist/core/text.d.ts +18 -0
  48. package/dist/core/text.d.ts.map +1 -0
  49. package/dist/core/text.js +32 -0
  50. package/dist/core/text.js.map +1 -0
  51. package/dist/formats/epub/href.d.ts +14 -0
  52. package/dist/formats/epub/href.d.ts.map +1 -0
  53. package/dist/formats/epub/href.js +52 -0
  54. package/dist/formats/epub/href.js.map +1 -0
  55. package/dist/formats/epub/index.d.ts +3 -0
  56. package/dist/formats/epub/index.d.ts.map +1 -0
  57. package/dist/formats/epub/index.js +223 -0
  58. package/dist/formats/epub/index.js.map +1 -0
  59. package/dist/formats/epub/opf.d.ts +27 -0
  60. package/dist/formats/epub/opf.d.ts.map +1 -0
  61. package/dist/formats/epub/opf.js +110 -0
  62. package/dist/formats/epub/opf.js.map +1 -0
  63. package/dist/formats/epub/toc.d.ts +6 -0
  64. package/dist/formats/epub/toc.d.ts.map +1 -0
  65. package/dist/formats/epub/toc.js +88 -0
  66. package/dist/formats/epub/toc.js.map +1 -0
  67. package/dist/formats/fb2/index.d.ts +13 -0
  68. package/dist/formats/fb2/index.d.ts.map +1 -0
  69. package/dist/formats/fb2/index.js +317 -0
  70. package/dist/formats/fb2/index.js.map +1 -0
  71. package/dist/formats/index.d.ts +4 -0
  72. package/dist/formats/index.d.ts.map +1 -0
  73. package/dist/formats/index.js +4 -0
  74. package/dist/formats/index.js.map +1 -0
  75. package/dist/formats/text/index.d.ts +11 -0
  76. package/dist/formats/text/index.d.ts.map +1 -0
  77. package/dist/formats/text/index.js +201 -0
  78. package/dist/formats/text/index.js.map +1 -0
  79. package/dist/formats/xml.d.ts +33 -0
  80. package/dist/formats/xml.d.ts.map +1 -0
  81. package/dist/formats/xml.js +321 -0
  82. package/dist/formats/xml.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +28 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/layout/chunk.d.ts +51 -0
  88. package/dist/layout/chunk.d.ts.map +1 -0
  89. package/dist/layout/chunk.js +131 -0
  90. package/dist/layout/chunk.js.map +1 -0
  91. package/dist/layout/index.d.ts +238 -0
  92. package/dist/layout/index.d.ts.map +1 -0
  93. package/dist/layout/index.js +476 -0
  94. package/dist/layout/index.js.map +1 -0
  95. package/dist/reader/index.d.ts +231 -0
  96. package/dist/reader/index.d.ts.map +1 -0
  97. package/dist/reader/index.js +958 -0
  98. package/dist/reader/index.js.map +1 -0
  99. package/dist/reader/input.d.ts +69 -0
  100. package/dist/reader/input.d.ts.map +1 -0
  101. package/dist/reader/input.js +75 -0
  102. package/dist/reader/input.js.map +1 -0
  103. package/dist/search/extract.d.ts +51 -0
  104. package/dist/search/extract.d.ts.map +1 -0
  105. package/dist/search/extract.js +401 -0
  106. package/dist/search/extract.js.map +1 -0
  107. package/dist/search/index.d.ts +11 -0
  108. package/dist/search/index.d.ts.map +1 -0
  109. package/dist/search/index.js +11 -0
  110. package/dist/search/index.js.map +1 -0
  111. package/dist/search/matcher.d.ts +56 -0
  112. package/dist/search/matcher.d.ts.map +1 -0
  113. package/dist/search/matcher.js +128 -0
  114. package/dist/search/matcher.js.map +1 -0
  115. package/dist/search/normalize.d.ts +48 -0
  116. package/dist/search/normalize.d.ts.map +1 -0
  117. package/dist/search/normalize.js +103 -0
  118. package/dist/search/normalize.js.map +1 -0
  119. package/dist/view/allowlist.d.ts +59 -0
  120. package/dist/view/allowlist.d.ts.map +1 -0
  121. package/dist/view/allowlist.js +198 -0
  122. package/dist/view/allowlist.js.map +1 -0
  123. package/dist/view/appearance.d.ts +137 -0
  124. package/dist/view/appearance.d.ts.map +1 -0
  125. package/dist/view/appearance.js +274 -0
  126. package/dist/view/appearance.js.map +1 -0
  127. package/dist/view/css.d.ts +24 -0
  128. package/dist/view/css.d.ts.map +1 -0
  129. package/dist/view/css.js +153 -0
  130. package/dist/view/css.js.map +1 -0
  131. package/dist/view/frame.d.ts +54 -0
  132. package/dist/view/frame.d.ts.map +1 -0
  133. package/dist/view/frame.js +980 -0
  134. package/dist/view/frame.js.map +1 -0
  135. package/dist/view/host.d.ts +160 -0
  136. package/dist/view/host.d.ts.map +1 -0
  137. package/dist/view/host.js +385 -0
  138. package/dist/view/host.js.map +1 -0
  139. package/dist/view/index.d.ts +12 -0
  140. package/dist/view/index.d.ts.map +1 -0
  141. package/dist/view/index.js +7 -0
  142. package/dist/view/index.js.map +1 -0
  143. package/dist/view/protocol.d.ts +220 -0
  144. package/dist/view/protocol.d.ts.map +1 -0
  145. package/dist/view/protocol.js +246 -0
  146. package/dist/view/protocol.js.map +1 -0
  147. package/dist/view/reference.d.ts +18 -0
  148. package/dist/view/reference.d.ts.map +1 -0
  149. package/dist/view/reference.js +25 -0
  150. package/dist/view/reference.js.map +1 -0
  151. package/dist/view/resources.d.ts +51 -0
  152. package/dist/view/resources.d.ts.map +1 -0
  153. package/dist/view/resources.js +242 -0
  154. package/dist/view/resources.js.map +1 -0
  155. package/dist/view/sanitize.d.ts +32 -0
  156. package/dist/view/sanitize.d.ts.map +1 -0
  157. package/dist/view/sanitize.js +226 -0
  158. package/dist/view/sanitize.js.map +1 -0
  159. package/dist/zip/central.d.ts +12 -0
  160. package/dist/zip/central.d.ts.map +1 -0
  161. package/dist/zip/central.js +72 -0
  162. package/dist/zip/central.js.map +1 -0
  163. package/dist/zip/crc32.d.ts +3 -0
  164. package/dist/zip/crc32.d.ts.map +1 -0
  165. package/dist/zip/crc32.js +28 -0
  166. package/dist/zip/crc32.js.map +1 -0
  167. package/dist/zip/eocd.d.ts +9 -0
  168. package/dist/zip/eocd.d.ts.map +1 -0
  169. package/dist/zip/eocd.js +84 -0
  170. package/dist/zip/eocd.js.map +1 -0
  171. package/dist/zip/errors.d.ts +19 -0
  172. package/dist/zip/errors.d.ts.map +1 -0
  173. package/dist/zip/errors.js +37 -0
  174. package/dist/zip/errors.js.map +1 -0
  175. package/dist/zip/index.d.ts +18 -0
  176. package/dist/zip/index.d.ts.map +1 -0
  177. package/dist/zip/index.js +78 -0
  178. package/dist/zip/index.js.map +1 -0
  179. package/dist/zip/inflate.d.ts +2 -0
  180. package/dist/zip/inflate.d.ts.map +1 -0
  181. package/dist/zip/inflate.js +43 -0
  182. package/dist/zip/inflate.js.map +1 -0
  183. package/dist/zip/source.d.ts +15 -0
  184. package/dist/zip/source.d.ts.map +1 -0
  185. package/dist/zip/source.js +34 -0
  186. package/dist/zip/source.js.map +1 -0
  187. package/package.json +75 -0
  188. package/src/core/book.ts +80 -0
  189. package/src/core/errors.ts +31 -0
  190. package/src/core/format.ts +14 -0
  191. package/src/core/graphemes.ts +73 -0
  192. package/src/core/index.ts +27 -0
  193. package/src/core/lookup.ts +16 -0
  194. package/src/core/open.ts +31 -0
  195. package/src/core/position.ts +485 -0
  196. package/src/core/reading-text.ts +200 -0
  197. package/src/core/source.ts +61 -0
  198. package/src/core/storage.ts +6 -0
  199. package/src/core/text.ts +35 -0
  200. package/src/formats/epub/href.ts +54 -0
  201. package/src/formats/epub/index.ts +235 -0
  202. package/src/formats/epub/opf.ts +143 -0
  203. package/src/formats/epub/toc.ts +120 -0
  204. package/src/formats/fb2/index.ts +348 -0
  205. package/src/formats/index.ts +3 -0
  206. package/src/formats/text/index.ts +208 -0
  207. package/src/formats/xml.ts +326 -0
  208. package/src/index.ts +26 -0
  209. package/src/layout/chunk.ts +169 -0
  210. package/src/layout/index.ts +573 -0
  211. package/src/reader/index.ts +1206 -0
  212. package/src/reader/input.ts +122 -0
  213. package/src/search/extract.ts +403 -0
  214. package/src/search/index.ts +10 -0
  215. package/src/search/matcher.ts +187 -0
  216. package/src/search/normalize.ts +122 -0
  217. package/src/view/allowlist.ts +273 -0
  218. package/src/view/appearance.ts +380 -0
  219. package/src/view/css.ts +172 -0
  220. package/src/view/frame.ts +1018 -0
  221. package/src/view/host.ts +527 -0
  222. package/src/view/index.ts +24 -0
  223. package/src/view/protocol.ts +412 -0
  224. package/src/view/reference.ts +30 -0
  225. package/src/view/resources.ts +265 -0
  226. package/src/view/sanitize.ts +266 -0
  227. package/src/zip/central.ts +85 -0
  228. package/src/zip/crc32.ts +30 -0
  229. package/src/zip/eocd.ts +97 -0
  230. package/src/zip/errors.ts +41 -0
  231. package/src/zip/index.ts +114 -0
  232. package/src/zip/inflate.ts +41 -0
  233. package/src/zip/source.ts +51 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../src/core/lookup.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAA4B;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAC;IACxC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Book } from './book.ts';
2
+ import type { BookFormat } from './format.ts';
3
+ import type { BookInput } from './source.ts';
4
+ import type { StorageAdapter } from './storage.ts';
5
+ export interface OpenOptions {
6
+ /** Tried in order; the first format whose sniff claims the input decodes it. */
7
+ readonly formats: readonly BookFormat[];
8
+ readonly storage?: StorageAdapter;
9
+ }
10
+ /** Bytes in, Book out: runs the registered sniffers in order and decodes with the first claimant. */
11
+ export declare function open(input: BookInput, options: OpenOptions): Promise<Book>;
12
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/core/open.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;CACnC;AAED,qGAAqG;AACrG,wBAAsB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhF"}
@@ -0,0 +1,23 @@
1
+ import { BookError, CorruptContainerError, UnrecognizedFormatError } from './errors.js';
2
+ import { toByteSource } from './source.js';
3
+ /** Bytes in, Book out: runs the registered sniffers in order and decodes with the first claimant. */
4
+ export async function open(input, options) {
5
+ const source = toByteSource(input);
6
+ const context = options.storage === undefined ? {} : { storage: options.storage };
7
+ for (const format of options.formats) {
8
+ if (!(await format.sniff(source)))
9
+ continue;
10
+ try {
11
+ return await format.decode(source, context);
12
+ }
13
+ catch (error) {
14
+ if (error instanceof BookError)
15
+ throw error;
16
+ throw new CorruptContainerError(`${format.name} failed to decode the input`, {
17
+ cause: error,
18
+ });
19
+ }
20
+ }
21
+ throw new UnrecognizedFormatError('no registered format recognizes this input');
22
+ }
23
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/core/open.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ3C,qGAAqG;AACrG,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,KAAgB,EAAE,OAAoB;IAC/D,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GACX,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;IACpE,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAAE,SAAS;QAC5C,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS;gBAAE,MAAM,KAAK,CAAC;YAC5C,MAAM,IAAI,qBAAqB,CAAC,GAAG,MAAM,CAAC,IAAI,6BAA6B,EAAE;gBAC3E,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,IAAI,uBAAuB,CAAC,4CAA4C,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * A content-addressed anchor into a section's text, Hypothesis-style. A match is
3
+ * found by content (the exact quote plus surrounding context), never by offset:
4
+ * `offset` exists only to break ties between identical quotes and is never
5
+ * load-bearing on its own.
6
+ */
7
+ export interface TextAnchor {
8
+ /** The verbatim run of text the position points at. */
9
+ readonly exact: string;
10
+ /** Text immediately before `exact`, bounded length; boundary-snapped. */
11
+ readonly prefix: string;
12
+ /** Text immediately after `exact`, bounded length; boundary-snapped. */
13
+ readonly suffix: string;
14
+ /**
15
+ * Grapheme-cluster offset of `exact` within the section text. A tiebreak only —
16
+ * used to pick among duplicate quotes, never to locate a match alone.
17
+ */
18
+ readonly offset: number;
19
+ }
20
+ /**
21
+ * A reading position within a book. Its `serialized` form is an opaque string a
22
+ * host persists verbatim and never parses; `progress` rides alongside for host
23
+ * UI (scrollbars, "42%") without decoding the string. Backed by a content anchor
24
+ * so it survives reflow, font-size, and layout changes.
25
+ */
26
+ export interface Position {
27
+ /** The section this position falls in. */
28
+ readonly sectionId: string;
29
+ /** Fraction through the section, 0..1. For host progress UI only. */
30
+ readonly progress: number;
31
+ /** Opaque, versioned, round-trippable interchange string. Hosts store it as-is. */
32
+ readonly serialized: string;
33
+ /** The content anchor. Resolution matches on this, not on `progress`. */
34
+ readonly anchor: TextAnchor;
35
+ }
36
+ /**
37
+ * Where a `Position` resolved to in a piece of target text. Offsets are grapheme
38
+ * indices into that text, matching `TextAnchor.offset`. A resolution *miss* is not
39
+ * this type — it is `undefined`, returned by `resolvePosition`.
40
+ */
41
+ export interface ResolvedPosition {
42
+ readonly sectionId: string;
43
+ /** Grapheme index where the matched quote starts in the target text. */
44
+ readonly offset: number;
45
+ /** Length of the matched quote in graphemes. */
46
+ readonly length: number;
47
+ }
48
+ export interface CapturePositionOptions {
49
+ /**
50
+ * Locale for `Intl.Segmenter` word snapping. Defaults to the runtime default
51
+ * locale. Grapheme snapping is locale-independent.
52
+ */
53
+ readonly locale?: string;
54
+ /** Maximum length (in graphemes) of the captured quote. */
55
+ readonly quoteLength?: number;
56
+ /** Maximum length (in graphemes) of each context window. */
57
+ readonly contextLength?: number;
58
+ }
59
+ /**
60
+ * Captures a `Position` at `offset` in a section's plain text. Core is headless:
61
+ * the caller supplies already-extracted text and a UTF-16 code-unit offset into
62
+ * it; no DOM, HTML, or Range is touched here. The offset is snapped to grapheme
63
+ * and word boundaries with `Intl.Segmenter` so an anchor never splits a surrogate
64
+ * pair, combining sequence, or emoji cluster.
65
+ */
66
+ export declare function capturePosition(text: string, offset: number, sectionId: string, options?: CapturePositionOptions): Position;
67
+ /** Serializes a position to its opaque, versioned interchange string. */
68
+ export declare function serializePosition(position: Position): string;
69
+ /** One sentence of a section, with a `Position` anchored over its whole span. */
70
+ export interface SentenceRange {
71
+ /** The sentence text, trailing whitespace trimmed. */
72
+ readonly text: string;
73
+ /**
74
+ * A `Position` whose anchored quote is the whole sentence, so `resolvePosition`
75
+ * returns its full span — a host can `goTo` it (jump) and `decorate` it
76
+ * (highlight the entire sentence, not just its start).
77
+ */
78
+ readonly position: Position;
79
+ }
80
+ /**
81
+ * Segments a section's plain text into sentences with `Intl.Segmenter`, each
82
+ * carrying a `Position` anchored over the whole sentence. This is the TTS *enabler*
83
+ * (PLAN M3-6): the library exposes sentence ranges and reuses `decorate` as the
84
+ * highlight primitive so a host can build text-to-speech on top — it speaks nothing
85
+ * and stores nothing. Whitespace-only segments are dropped. Headless: the caller
86
+ * supplies already-extracted text, exactly like `capturePosition`.
87
+ *
88
+ * `Intl.Segmenter` follows UAX #29, which ends a "sentence" at every hard line
89
+ * break — but the reading text is section `textContent`, whose source newlines the
90
+ * rendered page collapses via `white-space:normal`. Segmenting it verbatim would
91
+ * split one visual sentence at every wrapped source line (a heading or short line
92
+ * becoming a two-word "sentence"). So adjacent Intl segments are merged until the
93
+ * run actually ends on sentence-terminal punctuation; the anchor still spans the
94
+ * raw text (newlines included) so it content-matches the frame's section text,
95
+ * while the returned `text` collapses whitespace runs to single spaces for a clean
96
+ * TTS string. Abbreviations (`J. R. R.`) still split — that break is Intl's, and a
97
+ * period genuinely can end a sentence, so we do not second-guess it.
98
+ */
99
+ export declare function segmentSentences(text: string, sectionId: string, options?: CapturePositionOptions): SentenceRange[];
100
+ /**
101
+ * Parses a serialized position back into a `Position`. Round-trips losslessly
102
+ * with `serializePosition`. Throws `CorruptContainerError` when the string is
103
+ * malformed or carries an unknown version — a version this reader cannot honor is
104
+ * a structural failure the host catches by class, distinct from a resolution miss
105
+ * (which the resolver returns as a value, never an error).
106
+ */
107
+ export declare function parsePosition(serialized: string): Position;
108
+ /**
109
+ * Resolves a `Position` against a piece of section text — which may have changed
110
+ * since capture — returning where its anchor now lands, or `undefined` when the
111
+ * quote is gone. A miss is a value, never a throw: matching live content is an
112
+ * expected outcome the caller degrades on (skip the restore, drop the decoration),
113
+ * mirroring `Section.resolve()` returning `undefined`.
114
+ *
115
+ * The match is by content. Every occurrence of the exact quote is found, then
116
+ * scored by how much of the stored prefix/suffix context still surrounds it; the
117
+ * best-scoring occurrence wins. The stored offset breaks a tie only when context
118
+ * cannot — it never selects a location the content contradicts.
119
+ */
120
+ export declare function resolvePosition(position: Position, text: string): ResolvedPosition | undefined;
121
+ //# sourceMappingURL=position.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../src/core/position.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAmBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,sBAA2B,GACnC,QAAQ,CAEV;AAwDD,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE5D;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,sBAA2B,GACnC,aAAa,EAAE,CAwCjB;AAyBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CA8B1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,GACX,gBAAgB,GAAG,SAAS,CA8B9B"}
@@ -0,0 +1,327 @@
1
+ import { CorruptContainerError } from './errors.js';
2
+ import { codeUnitOffsetToGraphemeIndex, countGraphemes, segmentGraphemes, } from './graphemes.js';
3
+ const DEFAULT_QUOTE_LENGTH = 32;
4
+ const DEFAULT_CONTEXT_LENGTH = 32;
5
+ /** Version tag that leads every serialized position. Version-first, non-negotiable. */
6
+ const SERIAL_PREFIX = 'wr1:';
7
+ const SERIAL_VERSION = 1;
8
+ /**
9
+ * Captures a `Position` at `offset` in a section's plain text. Core is headless:
10
+ * the caller supplies already-extracted text and a UTF-16 code-unit offset into
11
+ * it; no DOM, HTML, or Range is touched here. The offset is snapped to grapheme
12
+ * and word boundaries with `Intl.Segmenter` so an anchor never splits a surrogate
13
+ * pair, combining sequence, or emoji cluster.
14
+ */
15
+ export function capturePosition(text, offset, sectionId, options = {}) {
16
+ return captureSegmented(segmentText(text), offset, sectionId, options);
17
+ }
18
+ function segmentText(text) {
19
+ return { text, graphemes: segmentGraphemes(text), wordStarts: null };
20
+ }
21
+ /** `capturePosition` over an already-segmented text; behavior is identical. */
22
+ function captureSegmented(segmented, offset, sectionId, options) {
23
+ const { graphemes } = segmented;
24
+ const total = graphemes.length;
25
+ const rawIndex = codeUnitOffsetToGraphemeIndex(graphemes, offset);
26
+ const start = snapToWordStart(segmented, rawIndex, options.locale);
27
+ const quoteLength = options.quoteLength ?? DEFAULT_QUOTE_LENGTH;
28
+ const contextLength = options.contextLength ?? DEFAULT_CONTEXT_LENGTH;
29
+ const quoteEnd = Math.min(start + quoteLength, total);
30
+ const prefixStart = Math.max(start - contextLength, 0);
31
+ const suffixEnd = Math.min(quoteEnd + contextLength, total);
32
+ const anchor = {
33
+ exact: join(graphemes, start, quoteEnd),
34
+ prefix: join(graphemes, prefixStart, start),
35
+ suffix: join(graphemes, quoteEnd, suffixEnd),
36
+ offset: start,
37
+ };
38
+ const progress = total === 0 ? 0 : start / total;
39
+ return {
40
+ sectionId,
41
+ progress,
42
+ anchor,
43
+ serialized: serializeParts(sectionId, progress, anchor),
44
+ };
45
+ }
46
+ /** Serializes a position to its opaque, versioned interchange string. */
47
+ export function serializePosition(position) {
48
+ return serializeParts(position.sectionId, position.progress, position.anchor);
49
+ }
50
+ /**
51
+ * Segments a section's plain text into sentences with `Intl.Segmenter`, each
52
+ * carrying a `Position` anchored over the whole sentence. This is the TTS *enabler*
53
+ * (PLAN M3-6): the library exposes sentence ranges and reuses `decorate` as the
54
+ * highlight primitive so a host can build text-to-speech on top — it speaks nothing
55
+ * and stores nothing. Whitespace-only segments are dropped. Headless: the caller
56
+ * supplies already-extracted text, exactly like `capturePosition`.
57
+ *
58
+ * `Intl.Segmenter` follows UAX #29, which ends a "sentence" at every hard line
59
+ * break — but the reading text is section `textContent`, whose source newlines the
60
+ * rendered page collapses via `white-space:normal`. Segmenting it verbatim would
61
+ * split one visual sentence at every wrapped source line (a heading or short line
62
+ * becoming a two-word "sentence"). So adjacent Intl segments are merged until the
63
+ * run actually ends on sentence-terminal punctuation; the anchor still spans the
64
+ * raw text (newlines included) so it content-matches the frame's section text,
65
+ * while the returned `text` collapses whitespace runs to single spaces for a clean
66
+ * TTS string. Abbreviations (`J. R. R.`) still split — that break is Intl's, and a
67
+ * period genuinely can end a sentence, so we do not second-guess it.
68
+ */
69
+ export function segmentSentences(text, sectionId, options = {}) {
70
+ const out = [];
71
+ // Segment graphemes (and, lazily, words) once for the whole text; every
72
+ // sentence capture reuses the same arrays instead of re-segmenting — this is
73
+ // what keeps a long chapter linear instead of quadratic.
74
+ const segmented = segmentText(text);
75
+ // Accumulate a run of Intl segments and its code-unit start until the run ends on
76
+ // sentence-terminal punctuation, then anchor over the raw span. A run start of -1
77
+ // means no run is open (all leading/inter-sentence whitespace is skipped).
78
+ let runStart = -1;
79
+ let runText = '';
80
+ const flush = () => {
81
+ const trimmed = runText.replace(/\s+$/u, '');
82
+ if (trimmed.trim().length !== 0) {
83
+ const quoteLength = countGraphemes(trimmed);
84
+ // Anchor over the whole (raw, newline-bearing) run so decorate highlights all
85
+ // of it against the frame's section text.
86
+ const position = captureSegmented(segmented, runStart, sectionId, {
87
+ ...options,
88
+ quoteLength,
89
+ });
90
+ out.push({ text: collapseWhitespace(trimmed), position });
91
+ }
92
+ runStart = -1;
93
+ runText = '';
94
+ };
95
+ for (const segment of sentenceSegmenter(options.locale).segment(text)) {
96
+ if (segment.segment.trim().length === 0) {
97
+ // Whitespace-only Intl segment: part of an open run, else inter-sentence space.
98
+ if (runStart >= 0)
99
+ runText += segment.segment;
100
+ continue;
101
+ }
102
+ if (runStart < 0)
103
+ runStart = segment.index;
104
+ runText += segment.segment;
105
+ if (endsOnSentenceTerminal(runText))
106
+ flush();
107
+ }
108
+ if (runStart >= 0)
109
+ flush();
110
+ return out;
111
+ }
112
+ /** Sentence-final punctuation, incl. CJK and other-script terminators. */
113
+ const SENTENCE_TERMINAL = /[.!?…。!?؟।]/u;
114
+ /** Trailing closing punctuation a terminator may hide behind (quotes, brackets). */
115
+ const TRAILING_CLOSERS = /[)\]}"'”’»›]+$/u;
116
+ /**
117
+ * Whether a run, ignoring trailing whitespace and closing punctuation, ends on a
118
+ * sentence terminator — the seam that decides a real sentence break from a mere
119
+ * line wrap. `"the lazy dog."` and `"he said 'go.'"` end a sentence; `"The quick
120
+ * brown\n"` (a wrapped line) does not.
121
+ */
122
+ function endsOnSentenceTerminal(run) {
123
+ const core = run.replace(/\s+$/u, '').replace(TRAILING_CLOSERS, '');
124
+ const chars = [...core];
125
+ const last = chars[chars.length - 1];
126
+ return last !== undefined && SENTENCE_TERMINAL.test(last);
127
+ }
128
+ /** Collapses every run of whitespace to a single space and trims the ends. */
129
+ function collapseWhitespace(text) {
130
+ return text.replace(/\s+/gu, ' ').trim();
131
+ }
132
+ /**
133
+ * Parses a serialized position back into a `Position`. Round-trips losslessly
134
+ * with `serializePosition`. Throws `CorruptContainerError` when the string is
135
+ * malformed or carries an unknown version — a version this reader cannot honor is
136
+ * a structural failure the host catches by class, distinct from a resolution miss
137
+ * (which the resolver returns as a value, never an error).
138
+ */
139
+ export function parsePosition(serialized) {
140
+ if (!serialized.startsWith(SERIAL_PREFIX)) {
141
+ throw new CorruptContainerError(`not a wolfy-reader position: missing '${SERIAL_PREFIX}' version prefix`);
142
+ }
143
+ let payload;
144
+ try {
145
+ payload = JSON.parse(serialized.slice(SERIAL_PREFIX.length));
146
+ }
147
+ catch (cause) {
148
+ throw new CorruptContainerError('position payload is not valid JSON', { cause });
149
+ }
150
+ if (!isSerialPayload(payload)) {
151
+ throw new CorruptContainerError('position payload is missing required fields');
152
+ }
153
+ if (payload.v !== SERIAL_VERSION) {
154
+ throw new CorruptContainerError(`unsupported position version ${payload.v}; this reader understands version ${SERIAL_VERSION}`);
155
+ }
156
+ const anchor = {
157
+ exact: payload.e,
158
+ prefix: payload.pre,
159
+ suffix: payload.suf,
160
+ offset: payload.o,
161
+ };
162
+ return { sectionId: payload.s, progress: payload.p, anchor, serialized };
163
+ }
164
+ /**
165
+ * Resolves a `Position` against a piece of section text — which may have changed
166
+ * since capture — returning where its anchor now lands, or `undefined` when the
167
+ * quote is gone. A miss is a value, never a throw: matching live content is an
168
+ * expected outcome the caller degrades on (skip the restore, drop the decoration),
169
+ * mirroring `Section.resolve()` returning `undefined`.
170
+ *
171
+ * The match is by content. Every occurrence of the exact quote is found, then
172
+ * scored by how much of the stored prefix/suffix context still surrounds it; the
173
+ * best-scoring occurrence wins. The stored offset breaks a tie only when context
174
+ * cannot — it never selects a location the content contradicts.
175
+ */
176
+ export function resolvePosition(position, text) {
177
+ const { anchor, sectionId } = position;
178
+ const graphemes = segmentGraphemes(text);
179
+ if (anchor.exact === '') {
180
+ const offset = Math.min(anchor.offset, graphemes.length);
181
+ return { sectionId, offset, length: 0 };
182
+ }
183
+ const occurrences = findGraphemeOccurrences(graphemes, anchor.exact);
184
+ if (occurrences.length === 0)
185
+ return undefined;
186
+ const quoteLength = segmentGraphemes(anchor.exact).length;
187
+ // Segment the stored context once, outside the occurrence loop.
188
+ const prefix = segmentGraphemes(anchor.prefix).map((g) => g.segment);
189
+ const suffix = segmentGraphemes(anchor.suffix).map((g) => g.segment);
190
+ let best;
191
+ for (const offset of occurrences) {
192
+ const score = contextScore(graphemes, offset, quoteLength, prefix, suffix);
193
+ if (best === undefined ||
194
+ score > best.score ||
195
+ (score === best.score &&
196
+ Math.abs(offset - anchor.offset) < Math.abs(best.offset - anchor.offset))) {
197
+ best = { offset, score };
198
+ }
199
+ }
200
+ return { sectionId, offset: best.offset, length: quoteLength };
201
+ }
202
+ /** Grapheme start indices at which `quote` occurs in `graphemes`. */
203
+ function findGraphemeOccurrences(graphemes, quote) {
204
+ const quoteGraphemes = segmentGraphemes(quote).map((g) => g.segment);
205
+ const hits = [];
206
+ if (quoteGraphemes.length === 0)
207
+ return hits;
208
+ const last = graphemes.length - quoteGraphemes.length;
209
+ for (let start = 0; start <= last; start += 1) {
210
+ let matched = true;
211
+ for (let offset = 0; offset < quoteGraphemes.length; offset += 1) {
212
+ if (graphemes[start + offset].segment !== quoteGraphemes[offset]) {
213
+ matched = false;
214
+ break;
215
+ }
216
+ }
217
+ if (matched)
218
+ hits.push(start);
219
+ }
220
+ return hits;
221
+ }
222
+ /**
223
+ * Count of trailing prefix graphemes + leading suffix graphemes that still match.
224
+ * `prefix`/`suffix` are the anchor's context pre-segmented into grapheme strings —
225
+ * segmented once by the caller, never per occurrence.
226
+ */
227
+ function contextScore(graphemes, quoteStart, quoteLength, prefix, suffix) {
228
+ let score = 0;
229
+ for (let back = 1; back <= prefix.length; back += 1) {
230
+ const here = graphemes[quoteStart - back];
231
+ const want = prefix[prefix.length - back];
232
+ if (here === undefined || here.segment !== want)
233
+ break;
234
+ score += 1;
235
+ }
236
+ const afterStart = quoteStart + quoteLength;
237
+ for (let ahead = 0; ahead < suffix.length; ahead += 1) {
238
+ const here = graphemes[afterStart + ahead];
239
+ if (here === undefined || here.segment !== suffix[ahead])
240
+ break;
241
+ score += 1;
242
+ }
243
+ return score;
244
+ }
245
+ function serializeParts(sectionId, progress, anchor) {
246
+ const payload = {
247
+ v: SERIAL_VERSION,
248
+ s: sectionId,
249
+ p: progress,
250
+ e: anchor.exact,
251
+ pre: anchor.prefix,
252
+ suf: anchor.suffix,
253
+ o: anchor.offset,
254
+ };
255
+ return SERIAL_PREFIX + JSON.stringify(payload);
256
+ }
257
+ function isSerialPayload(value) {
258
+ if (typeof value !== 'object' || value === null)
259
+ return false;
260
+ const record = value;
261
+ return (typeof record.v === 'number' &&
262
+ typeof record.s === 'string' &&
263
+ typeof record.p === 'number' &&
264
+ typeof record.e === 'string' &&
265
+ typeof record.pre === 'string' &&
266
+ typeof record.suf === 'string' &&
267
+ typeof record.o === 'number');
268
+ }
269
+ const wordSegmenters = new Map();
270
+ const sentenceSegmenters = new Map();
271
+ function sentenceSegmenter(locale) {
272
+ const key = locale ?? '';
273
+ let segmenter = sentenceSegmenters.get(key);
274
+ if (segmenter === undefined) {
275
+ segmenter = new Intl.Segmenter(locale, { granularity: 'sentence' });
276
+ sentenceSegmenters.set(key, segmenter);
277
+ }
278
+ return segmenter;
279
+ }
280
+ function wordSegmenter(locale) {
281
+ const key = locale ?? '';
282
+ let segmenter = wordSegmenters.get(key);
283
+ if (segmenter === undefined) {
284
+ segmenter = new Intl.Segmenter(locale, { granularity: 'word' });
285
+ wordSegmenters.set(key, segmenter);
286
+ }
287
+ return segmenter;
288
+ }
289
+ function join(graphemes, start, end) {
290
+ let out = '';
291
+ for (let index = start; index < end; index += 1)
292
+ out += graphemes[index].segment;
293
+ return out;
294
+ }
295
+ /**
296
+ * Snaps a grapheme index to the start of the word segment it falls in, so a
297
+ * captured quote begins on a word boundary rather than mid-token. Word segments
298
+ * tile the text, so the containing segment is the one with the greatest start
299
+ * offset <= the grapheme's offset — a binary search over the word-start array,
300
+ * which is built once per `SegmentedText` and reused by every capture.
301
+ */
302
+ function snapToWordStart(segmented, index, locale) {
303
+ const { graphemes } = segmented;
304
+ if (index <= 0 || index >= graphemes.length)
305
+ return index;
306
+ if (segmented.wordStarts === null) {
307
+ const starts = [];
308
+ for (const word of wordSegmenter(locale).segment(segmented.text))
309
+ starts.push(word.index);
310
+ segmented.wordStarts = starts;
311
+ }
312
+ const starts = segmented.wordStarts;
313
+ const unitOffset = graphemes[index].index;
314
+ // Greatest word start <= unitOffset. starts[0] is 0 and unitOffset > 0 here,
315
+ // so the search never underflows.
316
+ let low = 0;
317
+ let high = starts.length - 1;
318
+ while (low < high) {
319
+ const mid = (low + high + 1) >>> 1;
320
+ if (starts[mid] <= unitOffset)
321
+ low = mid;
322
+ else
323
+ high = mid - 1;
324
+ }
325
+ return codeUnitOffsetToGraphemeIndex(graphemes, starts[low]);
326
+ }
327
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../../src/core/position.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The canonical "reading text" policy — the single definition of which parts of a
3
+ * section's markup become the text a reader actually sees in the frame.
4
+ *
5
+ * Two independent consumers must agree on it, and this module is how they stay one
6
+ * model rather than two:
7
+ *
8
+ * - `src/view` sanitizes and applies resources before rendering: discarded elements
9
+ * vanish with their children (`sanitize.ts`), and an `<img>` that cannot be served
10
+ * is replaced by its `alt` text (`resources.ts`). The frame's measured section
11
+ * text — what a `Position` resolves against — is the `textContent` of that output.
12
+ * - `src/search` captures hit anchors headlessly, without a DOM, over the same text.
13
+ * Its extractor (`src/search/extract.ts`) mirrors the pipeline using the tables
14
+ * and predicates here, so a captured quote/context window exists verbatim in the
15
+ * text the frame resolves against.
16
+ *
17
+ * Internal to the library: nothing here is re-exported from `src/core/index.ts`
18
+ * (same status as `graphemes.ts`), so it is shared implementation, not public
19
+ * surface. It is headless by rule — string tables and string predicates only —
20
+ * because `src/search` may import only `src/core` and platform primitives.
21
+ */
22
+ /**
23
+ * HTML elements the sanitizer removes with everything inside them, because their
24
+ * children are not prose: raw-text elements whose content is source rather than
25
+ * text, form controls, and void elements with no children to keep. Everything
26
+ * else absent from the allowlist is unwrapped instead — the element goes, its
27
+ * text stays. The unwrap-vs-discard rationale lives with the allowlist
28
+ * (`src/view/allowlist.ts`), which re-exports these under their frozen names.
29
+ */
30
+ export declare const DISCARDED_HTML_ELEMENTS: ReadonlySet<string>;
31
+ /**
32
+ * SVG elements discarded whole. SMIL animation is on this list, not merely absent
33
+ * from the allowlist, because `<animate attributeName="href" to="javascript:...">`
34
+ * turns an inert element into a live one after sanitization has already run.
35
+ */
36
+ export declare const DISCARDED_SVG_ELEMENTS: ReadonlySet<string>;
37
+ export type ReferenceKind = 'empty' | 'fragment' | 'relative' | 'scheme';
38
+ export interface ClassifiedReference {
39
+ readonly kind: ReferenceKind;
40
+ /** The reference with URL-insignificant whitespace removed. */
41
+ readonly value: string;
42
+ /** Lowercased scheme name, present only when kind is 'scheme'. */
43
+ readonly scheme?: string;
44
+ }
45
+ export declare function classifyReference(raw: string): ClassifiedReference;
46
+ /** Collapses `.` and `..` segments, keeping leading `..` that cannot be resolved. */
47
+ export declare function normalizeReference(reference: string): string;
48
+ /** The bare media type: parameters trimmed, lowercased. */
49
+ export declare function baseMediaType(mediaType: string): string;
50
+ /**
51
+ * Only these media types are minted a `data:` URL by the resource layer. A
52
+ * reference resolving is not evidence it should be served: the seam answers for
53
+ * the whole manifest, so a `text/javascript` entry resolves as readily as an
54
+ * image — and a URL is a capability, not a convenience.
55
+ */
56
+ export declare function isServableResource(mediaType: string): boolean;
57
+ /** The synchronous face of `Section.resolve` — all the image policy needs. */
58
+ export type ReadingResolver = (reference: string) => {
59
+ readonly mediaType: string;
60
+ } | undefined;
61
+ /**
62
+ * The text an `<img>` contributes to the reading text: `''` when the element
63
+ * survives as an image (a served resource, or a remote/`data:` reference left for
64
+ * the CSP to refuse), or its `alt` when the resource layer substitutes the alt
65
+ * text for an image it cannot serve (`applyResources` in `src/view/resources.ts`
66
+ * — Gutenberg drop caps are the motivating case). An empty/absent `alt` on an
67
+ * unservable image means decorative: the element is removed and contributes
68
+ * nothing, which `''` also expresses.
69
+ *
70
+ * This is the headless mirror of the frame-side decision; the one edge it cannot
71
+ * see is a resource whose bytes fail to *load* after resolving servable — the
72
+ * frame substitutes alt there too, and a search anchor over that rare case
73
+ * degrades to the ordinary soft miss. Change this and the `applyResources` image
74
+ * branch together.
75
+ */
76
+ export declare function imageReadingText(src: string | null, alt: string | null, resolve: ReadingResolver | undefined): string;
77
+ //# sourceMappingURL=reading-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reading-text.d.ts","sourceRoot":"","sources":["../../src/core/reading-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,qBA0BnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,qBAclC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEzE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAyBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAOlE;AAED,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAa5D;AAcD,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAEhG;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,OAAO,EAAE,eAAe,GAAG,SAAS,GACnC,MAAM,CAQR"}