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,401 @@
1
+ /**
2
+ * Per-section text extraction for search. Decodes a section's bytes and strips its
3
+ * markup to a single plain string — the **canonical reading text**, the same text
4
+ * the frame ultimately shows and measures, so a match's captured anchor (quote +
5
+ * context) exists verbatim in the text `goTo` resolves against.
6
+ *
7
+ * Headless by rule: this imports only `src/core` (the shared reading-text policy)
8
+ * and platform primitives (`TextDecoder`) and never touches `document`/`window`, so
9
+ * the whole search stack runs under `node:test`. It deliberately does **not** route
10
+ * through the frame's `Paginator.sectionText()` — that returns only the
11
+ * currently-paginated section and needs a browser. Search scans every section
12
+ * without painting it.
13
+ *
14
+ * Extraction is a small tag-stripping tokenizer rather than `DOMParser`: `DOMParser`
15
+ * is a browser primitive with no headless Node global, and search only needs text
16
+ * content. To line up with the frame it mirrors what the sanitize + resource
17
+ * pipeline does to a section's text, via the one shared policy in
18
+ * `src/core/reading-text.ts`:
19
+ *
20
+ * - Inline markup contributes no characters (`wo<em>rd</em>` is found as `word`) —
21
+ * `textContent` semantics, and unwrapped elements keep their text.
22
+ * - Elements the sanitizer **discards whole** (form controls, `template`, raw-text
23
+ * containers — `DISCARDED_HTML_ELEMENTS` / `DISCARDED_SVG_ELEMENTS`) contribute
24
+ * nothing, exactly as their text never reaches the frame.
25
+ * - An `<img>` contributes what the resource layer will show: nothing when it can
26
+ * be served (or is left for the CSP), its `alt` when the frame substitutes alt
27
+ * text (`imageReadingText`) — which is why extraction takes the section's
28
+ * `resolve` seam.
29
+ * - Whitespace-only text runs at the top level of the body are dropped, because
30
+ * the chunker drops those text nodes before the frame tiles the section text
31
+ * (`chunkElement` in `src/layout/chunk.ts`).
32
+ * - `head`, `script` and `style` content is never reading text (`<style>` survives
33
+ * sanitization, but its CSS is source, not prose — searching it would be noise;
34
+ * see the known-edges note in docs/domains/search.md).
35
+ */
36
+ import { DISCARDED_HTML_ELEMENTS, DISCARDED_SVG_ELEMENTS, imageReadingText, } from '../core/reading-text.js';
37
+ import { decodeText } from '../core/text.js';
38
+ /** Decodes section bytes honouring the byte-order marks EPUB allows. */
39
+ export { decodeText as decodeSectionBytes } from '../core/text.js';
40
+ /**
41
+ * Elements whose content the HTML parser reads as raw text (a `<` in `1<2` inside
42
+ * `<script>` is not a tag): skipping jumps to the first matching end tag rather
43
+ * than tracking nesting. All but `style` are also discarded by the sanitizer;
44
+ * `style` survives into the frame but its text is source, not prose.
45
+ */
46
+ const RAW_TEXT = new Set([
47
+ 'iframe',
48
+ 'noembed',
49
+ 'noframes',
50
+ 'plaintext',
51
+ 'script',
52
+ 'style',
53
+ 'textarea',
54
+ 'title',
55
+ 'xmp',
56
+ ]);
57
+ /** HTML void elements: no content, no end tag, never on the open-element stack. */
58
+ const VOID = new Set([
59
+ 'area',
60
+ 'base',
61
+ 'br',
62
+ 'col',
63
+ 'embed',
64
+ 'hr',
65
+ 'img',
66
+ 'input',
67
+ 'link',
68
+ 'meta',
69
+ 'param',
70
+ 'source',
71
+ 'track',
72
+ 'wbr',
73
+ ]);
74
+ /**
75
+ * Block-level tags whose start tag implies `</p>` in HTML parsing. Without this,
76
+ * the unclosed `<p>` real books ship would keep the stack forever nested and the
77
+ * top-level whitespace rule would never apply again.
78
+ */
79
+ const P_CLOSERS = new Set([
80
+ 'address',
81
+ 'article',
82
+ 'aside',
83
+ 'blockquote',
84
+ 'details',
85
+ 'div',
86
+ 'dl',
87
+ 'fieldset',
88
+ 'figcaption',
89
+ 'figure',
90
+ 'footer',
91
+ 'form',
92
+ 'h1',
93
+ 'h2',
94
+ 'h3',
95
+ 'h4',
96
+ 'h5',
97
+ 'h6',
98
+ 'header',
99
+ 'hgroup',
100
+ 'hr',
101
+ 'main',
102
+ 'menu',
103
+ 'nav',
104
+ 'ol',
105
+ 'p',
106
+ 'pre',
107
+ 'section',
108
+ 'table',
109
+ 'ul',
110
+ ]);
111
+ /**
112
+ * Extracts a section's reading text from its decoded markup — the headless mirror
113
+ * of what the sanitize + resource pipeline leaves for the frame to show (see the
114
+ * module comment for the exact rules). `resolve` is the section's own resolver
115
+ * seam (`Section.resolve`), consulted only to decide whether an `<img>` renders
116
+ * or falls back to its alt text; omit it and every in-book image degrades to alt,
117
+ * matching a section that carries no resolver.
118
+ */
119
+ export function extractText(markup, resolve) {
120
+ let out = '';
121
+ let index = 0;
122
+ /** Open non-void elements. `head` and skipped containers never land here. */
123
+ const stack = [];
124
+ let svgDepth = 0;
125
+ // The chunker drops whitespace-only text nodes at the top level of the body
126
+ // (they carry no block box), so the frame's tiled text has no inter-paragraph
127
+ // source whitespace. Mirror it: a whitespace-only run emitted while no element
128
+ // (or only html/body) is open contributes nothing.
129
+ const atTopLevel = () => {
130
+ const top = stack[stack.length - 1];
131
+ return top === undefined || top === 'html' || top === 'body';
132
+ };
133
+ const emit = (text) => {
134
+ if (text === '')
135
+ return;
136
+ if (atTopLevel() && text.trim().length === 0)
137
+ return;
138
+ out += text;
139
+ };
140
+ const popTo = (name) => {
141
+ const at = stack.lastIndexOf(name);
142
+ if (at === -1)
143
+ return;
144
+ for (let i = stack.length - 1; i >= at; i -= 1) {
145
+ if (stack[i] === 'svg')
146
+ svgDepth -= 1;
147
+ }
148
+ stack.length = at;
149
+ };
150
+ const length = markup.length;
151
+ while (index < length) {
152
+ const lt = markup.indexOf('<', index);
153
+ if (lt === -1) {
154
+ emit(decodeEntities(markup.slice(index)));
155
+ break;
156
+ }
157
+ if (lt > index)
158
+ emit(decodeEntities(markup.slice(index, lt)));
159
+ // Comment / CDATA / processing instruction / doctype: skip to its close.
160
+ if (markup.startsWith('<!--', lt)) {
161
+ const close = markup.indexOf('-->', lt + 4);
162
+ index = close === -1 ? length : close + 3;
163
+ continue;
164
+ }
165
+ if (markup.startsWith('<![CDATA[', lt)) {
166
+ const close = markup.indexOf(']]>', lt + 9);
167
+ emit(markup.slice(lt + 9, close === -1 ? length : close)); // literal text, no entities
168
+ index = close === -1 ? length : close + 3;
169
+ continue;
170
+ }
171
+ if (markup[lt + 1] === '!' || markup[lt + 1] === '?') {
172
+ const close = markup.indexOf('>', lt + 1);
173
+ index = close === -1 ? length : close + 1;
174
+ continue;
175
+ }
176
+ const gt = markup.indexOf('>', lt);
177
+ if (gt === -1) {
178
+ // A stray '<' with no closing '>': treat the rest as text.
179
+ emit(decodeEntities(markup.slice(lt)));
180
+ break;
181
+ }
182
+ const rawTag = markup.slice(lt + 1, gt);
183
+ index = gt + 1;
184
+ const isClose = rawTag[0] === '/';
185
+ const name = tagName(isClose ? rawTag.slice(1) : rawTag);
186
+ if (name === '')
187
+ continue;
188
+ if (isClose) {
189
+ popTo(name);
190
+ continue;
191
+ }
192
+ const selfClosing = rawTag.endsWith('/');
193
+ const inSvg = svgDepth > 0;
194
+ if (!inSvg) {
195
+ // HTML implied end tags, minimally: a block start tag closes an open <p>,
196
+ // and a list/definition item closes its open sibling. Real books rely on
197
+ // both, and the frame's HTML parse applies them before chunking.
198
+ const top = stack[stack.length - 1];
199
+ if (top === 'p' && P_CLOSERS.has(name))
200
+ stack.pop();
201
+ else if (name === 'li' && top === 'li')
202
+ stack.pop();
203
+ else if ((name === 'dd' || name === 'dt') && (top === 'dd' || top === 'dt'))
204
+ stack.pop();
205
+ }
206
+ // The head never reaches the frame body (only its <link>/<style> move, and
207
+ // those carry no reading text), so its whole subtree is skipped.
208
+ if (!inSvg && name === 'head' && !selfClosing) {
209
+ index = skipBalanced(markup, name, index);
210
+ continue;
211
+ }
212
+ // An <img> renders as an image (zero characters) or becomes its alt text —
213
+ // the one place extraction *adds* text, mirroring the resource layer.
214
+ if (!inSvg && name === 'img') {
215
+ emit(imageReadingText(attributeOf(rawTag, 'src'), attributeOf(rawTag, 'alt'), resolve));
216
+ continue;
217
+ }
218
+ // Discarded-whole elements contribute nothing, exactly like the sanitizer;
219
+ // raw-text containers jump to their first matching end tag, everything else
220
+ // to the end tag balancing same-name nesting.
221
+ const discarded = inSvg ? DISCARDED_SVG_ELEMENTS : DISCARDED_HTML_ELEMENTS;
222
+ if (discarded.has(name) || name === 'style') {
223
+ if (selfClosing || (!inSvg && VOID.has(name)))
224
+ continue;
225
+ index = RAW_TEXT.has(name) ? findEndTag(markup, name, index) : skipBalanced(markup, name, index);
226
+ continue;
227
+ }
228
+ if (selfClosing || (!inSvg && VOID.has(name)))
229
+ continue;
230
+ if (name === 'svg')
231
+ svgDepth += 1;
232
+ stack.push(name);
233
+ }
234
+ return out;
235
+ }
236
+ /** Decode + extract a section from its raw bytes. */
237
+ export function extractSectionText(bytes, resolve) {
238
+ return extractText(decodeText(bytes), resolve);
239
+ }
240
+ /**
241
+ * Index just past the matching `</name>` end tag at or after `from` (raw-text
242
+ * containers: the first close wins, nothing inside is a tag), or the markup
243
+ * length when the container is never closed. Case-insensitive on the tag name.
244
+ */
245
+ function findEndTag(markup, name, from) {
246
+ const lower = markup.toLowerCase();
247
+ const needle = `</${name}`;
248
+ let at = from;
249
+ for (;;) {
250
+ const found = lower.indexOf(needle, at);
251
+ if (found === -1)
252
+ return markup.length;
253
+ const after = markup[found + needle.length];
254
+ // The next char must end the tag name (whitespace or '>'), so </scripting>
255
+ // does not close <script>.
256
+ if (after === undefined || after === '>' || /\s/.test(after)) {
257
+ const gt = markup.indexOf('>', found);
258
+ return gt === -1 ? markup.length : gt + 1;
259
+ }
260
+ at = found + needle.length;
261
+ }
262
+ }
263
+ /**
264
+ * Index just past the end tag that balances an already-open `name` element,
265
+ * counting same-name nesting (a discarded `<select>` may hold another), or the
266
+ * markup length when it never closes.
267
+ */
268
+ function skipBalanced(markup, name, from) {
269
+ const lower = markup.toLowerCase();
270
+ const open = `<${name}`;
271
+ const close = `</${name}`;
272
+ let depth = 1;
273
+ let at = from;
274
+ while (depth > 0) {
275
+ const lt = lower.indexOf('<', at);
276
+ if (lt === -1)
277
+ return markup.length;
278
+ const gt = markup.indexOf('>', lt);
279
+ if (gt === -1)
280
+ return markup.length;
281
+ if (lower.startsWith(close, lt) && endsTagName(markup, lt + close.length)) {
282
+ depth -= 1;
283
+ }
284
+ else if (lower.startsWith(open, lt) && endsTagName(markup, lt + open.length)) {
285
+ if (markup[gt - 1] !== '/')
286
+ depth += 1; // a self-closing one opens nothing
287
+ }
288
+ at = gt + 1;
289
+ }
290
+ return at;
291
+ }
292
+ /** True when the character at `i` terminates a tag name (`>`, `/`, whitespace, EOF). */
293
+ function endsTagName(markup, i) {
294
+ const char = markup[i];
295
+ return char === undefined || char === '>' || char === '/' || /\s/.test(char);
296
+ }
297
+ /** The lowercased element name from a raw tag body ('em class="x"' → 'em'). */
298
+ function tagName(body) {
299
+ let end = 0;
300
+ while (end < body.length && !/[\s/>]/.test(body[end]))
301
+ end += 1;
302
+ return body.slice(0, end).toLowerCase();
303
+ }
304
+ /**
305
+ * The decoded value of `wanted` in a raw tag body, or `null` when absent.
306
+ * Handles single/double-quoted and unquoted values and a bare attribute name
307
+ * (which the DOM reads as the empty string). Entities in the value are decoded,
308
+ * as an attribute parse would.
309
+ */
310
+ function attributeOf(rawTag, wanted) {
311
+ let i = 0;
312
+ while (i < rawTag.length && !/[\s/]/.test(rawTag[i]))
313
+ i += 1; // skip the tag name
314
+ while (i < rawTag.length) {
315
+ while (i < rawTag.length && /[\s/]/.test(rawTag[i]))
316
+ i += 1;
317
+ if (i >= rawTag.length)
318
+ break;
319
+ let start = i;
320
+ while (i < rawTag.length && !/[\s=/]/.test(rawTag[i]))
321
+ i += 1;
322
+ const attribute = rawTag.slice(start, i).toLowerCase();
323
+ while (i < rawTag.length && /\s/.test(rawTag[i]))
324
+ i += 1;
325
+ let value = '';
326
+ if (rawTag[i] === '=') {
327
+ i += 1;
328
+ while (i < rawTag.length && /\s/.test(rawTag[i]))
329
+ i += 1;
330
+ const quote = rawTag[i];
331
+ if (quote === '"' || quote === "'") {
332
+ i += 1;
333
+ start = i;
334
+ while (i < rawTag.length && rawTag[i] !== quote)
335
+ i += 1;
336
+ value = rawTag.slice(start, i);
337
+ i += 1;
338
+ }
339
+ else {
340
+ start = i;
341
+ while (i < rawTag.length && !/\s/.test(rawTag[i]))
342
+ i += 1;
343
+ value = rawTag.slice(start, i);
344
+ }
345
+ }
346
+ if (attribute === wanted)
347
+ return decodeEntities(value);
348
+ }
349
+ return null;
350
+ }
351
+ const NAMED = new Map([
352
+ ['amp', '&'],
353
+ ['lt', '<'],
354
+ ['gt', '>'],
355
+ ['quot', '"'],
356
+ ['apos', "'"],
357
+ ['nbsp', ' '],
358
+ ['mdash', '—'],
359
+ ['ndash', '–'],
360
+ ['hellip', '…'],
361
+ ['lsquo', '‘'],
362
+ ['rsquo', '’'],
363
+ ['ldquo', '“'],
364
+ ['rdquo', '”'],
365
+ ['copy', '©'],
366
+ ['reg', '®'],
367
+ ['trade', '™'],
368
+ ['deg', '°'],
369
+ ['times', '×'],
370
+ ['eacute', 'é'],
371
+ ['egrave', 'è'],
372
+ ['agrave', 'à'],
373
+ ['ccedil', 'ç'],
374
+ ['uuml', 'ü'],
375
+ [' ', ' '],
376
+ ]);
377
+ /**
378
+ * Decodes numeric (`&#233;`, `&#xe9;`) and the common named HTML entities. An
379
+ * unknown entity is left verbatim — a book's literal `&` in prose stays `&`.
380
+ */
381
+ function decodeEntities(text) {
382
+ if (!text.includes('&'))
383
+ return text;
384
+ return text.replace(/&(#x?[0-9a-f]+|[a-z][a-z0-9]*);/gi, (whole, body) => {
385
+ if (body[0] === '#') {
386
+ const code = body[1] === 'x' || body[1] === 'X'
387
+ ? Number.parseInt(body.slice(2), 16)
388
+ : Number.parseInt(body.slice(1), 10);
389
+ if (!Number.isFinite(code) || code < 0 || code > 0x10ffff)
390
+ return whole;
391
+ try {
392
+ return String.fromCodePoint(code);
393
+ }
394
+ catch {
395
+ return whole;
396
+ }
397
+ }
398
+ return NAMED.get(body.toLowerCase()) ?? whole;
399
+ });
400
+ }
401
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/search/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Full-text search: a headless matcher over decoded section text, yielding jumpable
3
+ * hits. Imports only `src/core` + platform primitives (`DOMParser`, `Intl.Segmenter`,
4
+ * `TextDecoder`), so it runs under `node:test` with no browser and passes
5
+ * `npm run check:core`. The whole-book scan (`searchBook`) is a lazy async generator
6
+ * that never buffers the book whole and cleans up on early `break`.
7
+ */
8
+ export { searchBook, matchText, type SearchHit, type SearchOptions } from './matcher.ts';
9
+ export { extractText, extractSectionText, decodeSectionBytes, type ReadingResolver } from './extract.ts';
10
+ export { normalizeText, normalizeQuery, type NormalizedText } from './normalize.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Full-text search: a headless matcher over decoded section text, yielding jumpable
3
+ * hits. Imports only `src/core` + platform primitives (`DOMParser`, `Intl.Segmenter`,
4
+ * `TextDecoder`), so it runs under `node:test` with no browser and passes
5
+ * `npm run check:core`. The whole-book scan (`searchBook`) is a lazy async generator
6
+ * that never buffers the book whole and cleans up on early `break`.
7
+ */
8
+ export { searchBook, matchText } from './matcher.js';
9
+ export { extractText, extractSectionText, decodeSectionBytes } from './extract.js';
10
+ export { normalizeText, normalizeQuery } from './normalize.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAsC,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAwB,MAAM,cAAc,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAuB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The headless full-text matcher. Normalized literal substring search over a
3
+ * section's extracted text, yielding jumpable hits. Imports only `src/core`
4
+ * (`capturePosition`, `Position`, `Book`, `Section`) and platform primitives, so it
5
+ * runs under `node:test` with no browser.
6
+ *
7
+ * The whole-book scan is a lazy async generator: it decodes and scans one section at
8
+ * a time and yields hits as it goes, so the book is never buffered whole and a
9
+ * consumer that `break`s stops the scan (the generator's cleanup runs on return).
10
+ */
11
+ import { type Book, type Position } from '../core/index.ts';
12
+ /** One search hit: the matched text, a clean context window, and a jumpable position. */
13
+ export interface SearchHit {
14
+ /** The verbatim matched run, in the section's raw text. */
15
+ readonly text: string;
16
+ /**
17
+ * The match surrounded by context, trimmed to whole words at both ends via
18
+ * `Intl.Segmenter` — no leading/trailing partial word.
19
+ */
20
+ readonly context: string;
21
+ /**
22
+ * A content-addressed `Position` at the match, built with `capturePosition`. Pass
23
+ * it to `reader.goTo(hit.position)` to land on the hit's page.
24
+ */
25
+ readonly position: Position;
26
+ /** 0-based index of the section this hit falls in, in reading order. */
27
+ readonly sectionIndex: number;
28
+ }
29
+ export interface SearchOptions {
30
+ /**
31
+ * Characters of surrounding context to aim for on each side of the match before
32
+ * word-boundary trimming. Default 40.
33
+ */
34
+ readonly contextRadius?: number;
35
+ /** Locale for word segmentation of the context. Defaults to the runtime default. */
36
+ readonly locale?: string;
37
+ }
38
+ /**
39
+ * All hits for `query` within one section's already-extracted `rawText`. Pure over
40
+ * strings — no I/O — so it is the unit the section scan and the tests both drive.
41
+ * A blank (whitespace-only or empty) query yields nothing.
42
+ */
43
+ export declare function matchText(rawText: string, query: string, sectionId: string, sectionIndex: number, options?: SearchOptions): Generator<SearchHit>;
44
+ /**
45
+ * Lazily scans every section of `book` for `query`, one section at a time, yielding
46
+ * each hit as it is found. The book is never buffered whole: each section's bytes are
47
+ * loaded, extracted, matched and released before the next is touched. A consumer that
48
+ * stops early (`break`/`return`) halts the scan — no later section is loaded.
49
+ *
50
+ * `onSection` (test-only instrumentation seam) fires with each section's index the
51
+ * moment before it is decoded, so a test can prove laziness by counting decodes.
52
+ */
53
+ export declare function searchBook(book: Book, query: string, options?: SearchOptions & {
54
+ onSection?: (index: number) => void;
55
+ }): AsyncGenerator<SearchHit>;
56
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/search/matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAmB,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAgB,MAAM,kBAAkB,CAAC;AAM3F,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID;;;;GAIG;AACH,wBAAiB,SAAS,CACxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,aAAkB,GAC1B,SAAS,CAAC,SAAS,CAAC,CA+CtB;AAqDD;;;;;;;;GAQG;AACH,wBAAuB,UAAU,CAC/B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAa,GAAG;IAAE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GACpE,cAAc,CAAC,SAAS,CAAC,CAgB3B"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The headless full-text matcher. Normalized literal substring search over a
3
+ * section's extracted text, yielding jumpable hits. Imports only `src/core`
4
+ * (`capturePosition`, `Position`, `Book`, `Section`) and platform primitives, so it
5
+ * runs under `node:test` with no browser.
6
+ *
7
+ * The whole-book scan is a lazy async generator: it decodes and scans one section at
8
+ * a time and yields hits as it goes, so the book is never buffered whole and a
9
+ * consumer that `break`s stops the scan (the generator's cleanup runs on return).
10
+ */
11
+ import { capturePosition } from '../core/index.js';
12
+ import { collapseWhitespace as collapse } from '../core/text.js';
13
+ import { extractSectionText } from './extract.js';
14
+ import { normalizeQuery, normalizeText } from './normalize.js';
15
+ const DEFAULT_CONTEXT_RADIUS = 40;
16
+ /**
17
+ * All hits for `query` within one section's already-extracted `rawText`. Pure over
18
+ * strings — no I/O — so it is the unit the section scan and the tests both drive.
19
+ * A blank (whitespace-only or empty) query yields nothing.
20
+ */
21
+ export function* matchText(rawText, query, sectionId, sectionIndex, options = {}) {
22
+ const needle = normalizeQuery(query);
23
+ if (needle === '')
24
+ return;
25
+ const { text: haystack, map } = normalizeText(rawText);
26
+ const radius = options.contextRadius ?? DEFAULT_CONTEXT_RADIUS;
27
+ let from = 0;
28
+ for (;;) {
29
+ const at = haystack.indexOf(needle, from);
30
+ if (at === -1)
31
+ return;
32
+ const end = at + needle.length;
33
+ // Map the normalized match span back onto the raw text. `map[end]` points at the
34
+ // start of the character *after* the match, which — when that character is a
35
+ // collapsed space — lies past a run of raw whitespace; trim it so the matched
36
+ // text ends on real content. The anchor still starts at `rawStart`.
37
+ const rawStart = map[at];
38
+ const rawEnd = trimEnd(rawText, rawStart, map[end]);
39
+ const matched = rawText.slice(rawStart, rawEnd);
40
+ // Context window in normalized space, then mapped back and word-trimmed on the
41
+ // raw text so the shown context carries no partial word at either edge.
42
+ const ctxNormStart = Math.max(0, at - radius);
43
+ const ctxNormEnd = Math.min(haystack.length, end + radius);
44
+ const rawCtxStart = map[ctxNormStart];
45
+ const rawCtxEnd = map[ctxNormEnd];
46
+ const context = trimToWords(rawText, rawCtxStart, rawCtxEnd, rawStart, rawEnd, options.locale);
47
+ yield {
48
+ text: matched,
49
+ context,
50
+ position: capturePosition(rawText, rawStart, sectionId),
51
+ sectionIndex,
52
+ };
53
+ // Advance past this match; guard against a zero-length needle (impossible here,
54
+ // an empty needle returned above) so the loop always progresses.
55
+ from = end > at ? end : at + 1;
56
+ }
57
+ }
58
+ /**
59
+ * Trims a raw context window `[start, end)` inward to whole-word boundaries via
60
+ * `Intl.Segmenter`, never cutting into the match span `[matchStart, matchEnd)`. A
61
+ * word straddling the left edge is dropped forward to its end (the next word's
62
+ * start); a word straddling the right edge is dropped back to its start. The result
63
+ * is whitespace-collapsed for a compact one-line context.
64
+ */
65
+ function trimToWords(text, start, end, matchStart, matchEnd, locale) {
66
+ // Segment a slice wider than the window so a word straddling an edge is visible
67
+ // whole. Offsets are relative to the wide slice, so add `lo`.
68
+ const lo = Math.max(0, Math.min(start, matchStart) - 24);
69
+ const hi = Math.min(text.length, Math.max(end, matchEnd) + 24);
70
+ let head = start;
71
+ let tail = end;
72
+ for (const seg of wordSegmenter(locale).segment(text.slice(lo, hi))) {
73
+ if (!seg.isWordLike)
74
+ continue;
75
+ const segStart = lo + seg.index;
76
+ const segEnd = segStart + seg.segment.length;
77
+ // A word-like segment straddling the left edge: move head to its end (the next
78
+ // word's start), but never past the match start.
79
+ if (segStart < start && segEnd > start)
80
+ head = Math.min(matchStart, segEnd);
81
+ // A word-like segment straddling the right edge: move tail to its start, but
82
+ // never before the match end.
83
+ if (segStart < end && segEnd > end)
84
+ tail = Math.max(matchEnd, segStart);
85
+ }
86
+ return collapse(text.slice(head, tail));
87
+ }
88
+ /** Pull `end` back past any trailing whitespace, never before `start`. */
89
+ function trimEnd(text, start, end) {
90
+ let at = end;
91
+ while (at > start && /\s/.test(text[at - 1]))
92
+ at -= 1;
93
+ return at;
94
+ }
95
+ let cachedWordSegmenter;
96
+ function wordSegmenter(locale) {
97
+ if (cachedWordSegmenter === undefined || cachedWordSegmenter.locale !== locale) {
98
+ cachedWordSegmenter = { locale, segmenter: new Intl.Segmenter(locale, { granularity: 'word' }) };
99
+ }
100
+ return cachedWordSegmenter.segmenter;
101
+ }
102
+ /**
103
+ * Lazily scans every section of `book` for `query`, one section at a time, yielding
104
+ * each hit as it is found. The book is never buffered whole: each section's bytes are
105
+ * loaded, extracted, matched and released before the next is touched. A consumer that
106
+ * stops early (`break`/`return`) halts the scan — no later section is loaded.
107
+ *
108
+ * `onSection` (test-only instrumentation seam) fires with each section's index the
109
+ * moment before it is decoded, so a test can prove laziness by counting decodes.
110
+ */
111
+ export async function* searchBook(book, query, options = {}) {
112
+ const needle = normalizeQuery(query);
113
+ if (needle === '')
114
+ return;
115
+ const sections = book.sections;
116
+ for (let index = 0; index < sections.length; index += 1) {
117
+ const section = sections[index];
118
+ options.onSection?.(index);
119
+ const bytes = await section.load();
120
+ // The section's own resolver rides along so extraction shows an <img> the
121
+ // way the frame will: rendered (no text) or substituted by its alt.
122
+ const rawText = extractSectionText(bytes, section.resolve?.bind(section));
123
+ for (const hit of matchText(rawText, query, section.id, index, options)) {
124
+ yield hit;
125
+ }
126
+ }
127
+ }
128
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/search/matcher.ts"],"names":[],"mappings":"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"}