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,169 @@
1
+ /**
2
+ * Chunk-boundary rules for the paginator, derived from real EPUB markup (item8
3
+ * of gutenberg-pride-and-prejudice.epub: 571 top-level blocks, 552 `<p>`, max
4
+ * nesting depth 5), not from theory.
5
+ *
6
+ * A chunk boundary becomes a *forced page break*, because each chunk is laid out
7
+ * as its own multi-column formatting context (layout containment forbids a
8
+ * `content-visibility` box fragmenting across columns). So these rules answer
9
+ * "where is a forced page break acceptable", not merely "where is the DOM
10
+ * splittable".
11
+ *
12
+ * The rules run over a structural view of a top-level node list — `ChunkNode` —
13
+ * rather than the DOM directly, so they are pure and unit-testable under
14
+ * `node:test`. `chunkElement` adapts a real DOM element to that view.
15
+ */
16
+ import { escapeXmlText } from '../core/text.ts';
17
+
18
+ /** The minimal shape the boundary rules read from each top-level node. */
19
+ export interface ChunkNode {
20
+ /** Uppercase tag name for elements, `'#text'` for text nodes. */
21
+ readonly tag: string;
22
+ /** `textContent.length` of this node. */
23
+ readonly chars: number;
24
+ /** Serialized markup — `outerHTML` for elements, escaped text for text nodes. */
25
+ readonly html: string;
26
+ }
27
+
28
+ export interface Chunk {
29
+ readonly html: string;
30
+ /** Total characters of text this chunk holds. */
31
+ readonly chars: number;
32
+ /** Number of top-level nodes in this chunk. */
33
+ readonly nodes: number;
34
+ }
35
+
36
+ export interface ChunkStats {
37
+ readonly topLevelNodes: number;
38
+ readonly oversizedElements: number;
39
+ readonly largestElementChars: number;
40
+ readonly headingsDeferred: number;
41
+ readonly atomicOversized: number;
42
+ readonly bareTextNodes: number;
43
+ }
44
+
45
+ export interface ChunkResult {
46
+ readonly chunks: readonly Chunk[];
47
+ readonly stats: ChunkStats;
48
+ }
49
+
50
+ /** Default chunk budget in characters. The whole tradeoff dial; see layout.md. */
51
+ export const DEFAULT_CHUNK_CHARS = 8000;
52
+
53
+ // Splitting *between* these is fine; splitting *inside* one is not — each carries
54
+ // structure (row alignment, list numbering, preformatted whitespace) a second
55
+ // formatting context would restate from scratch.
56
+ const ATOMIC = new Set(['TABLE', 'PRE', 'OL', 'UL', 'DL', 'FIGURE', 'BLOCKQUOTE', 'SVG', 'MATH']);
57
+
58
+ // A chunk must never end on one of these: the boundary is a page break, and a
59
+ // heading (or rule) stranded at the foot of a page is the classic orphan.
60
+ const NEVER_LAST = new Set(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HR']);
61
+
62
+ /**
63
+ * Splits a top-level node list into chunks at flow boundaries, accumulating
64
+ * children until `targetChars` of text is reached.
65
+ *
66
+ * - Split only between top-level (depth-1) children; never descend.
67
+ * - An element larger than the budget is emitted whole as its own chunk (the
68
+ * budget is advisory, not a cap) — descending would put a page break
69
+ * mid-paragraph.
70
+ * - Never end a chunk on a heading or `<hr>`; defer it to the next chunk.
71
+ * - Bare text nodes are glued to the open chunk; whitespace-only ones are
72
+ * dropped by `chunkElement` before this runs.
73
+ */
74
+ export function chunkNodes(nodes: readonly ChunkNode[], targetChars = DEFAULT_CHUNK_CHARS): ChunkResult {
75
+ let oversizedElements = 0;
76
+ let largestElementChars = 0;
77
+ let headingsDeferred = 0;
78
+ let atomicOversized = 0;
79
+ let bareTextNodes = 0;
80
+
81
+ const chunks: Chunk[] = [];
82
+ let current: ChunkNode[] = [];
83
+ let currentChars = 0;
84
+
85
+ const flush = (): void => {
86
+ if (current.length === 0) return;
87
+ chunks.push({
88
+ html: current.map((node) => node.html).join(''),
89
+ chars: currentChars,
90
+ nodes: current.length,
91
+ });
92
+ current = [];
93
+ currentChars = 0;
94
+ };
95
+
96
+ for (const node of nodes) {
97
+ if (node.tag === '#text') bareTextNodes += 1;
98
+ if (node.chars > largestElementChars) largestElementChars = node.chars;
99
+
100
+ if (node.chars >= targetChars) {
101
+ flush();
102
+ oversizedElements += 1;
103
+ if (ATOMIC.has(node.tag)) atomicOversized += 1;
104
+ current = [node];
105
+ currentChars = node.chars;
106
+ flush();
107
+ continue;
108
+ }
109
+
110
+ if (currentChars > 0 && currentChars + node.chars > targetChars) {
111
+ const deferred: ChunkNode[] = [];
112
+ while (current.length > 1 && NEVER_LAST.has(current[current.length - 1]!.tag)) {
113
+ const moved = current.pop()!;
114
+ currentChars -= moved.chars;
115
+ deferred.unshift(moved);
116
+ headingsDeferred += 1;
117
+ }
118
+ flush();
119
+ for (const moved of deferred) {
120
+ current.push(moved);
121
+ currentChars += moved.chars;
122
+ }
123
+ }
124
+
125
+ current.push(node);
126
+ currentChars += node.chars;
127
+ }
128
+ flush();
129
+
130
+ return {
131
+ chunks,
132
+ stats: {
133
+ topLevelNodes: nodes.length,
134
+ oversizedElements,
135
+ largestElementChars,
136
+ headingsDeferred,
137
+ atomicOversized,
138
+ bareTextNodes,
139
+ },
140
+ };
141
+ }
142
+
143
+ const ELEMENT_NODE = 1;
144
+ const TEXT_NODE = 3;
145
+
146
+ /**
147
+ * Adapts a live DOM element (a section body) into the `ChunkNode` list the
148
+ * boundary rules read, then chunks it. Whitespace-only bare text nodes are
149
+ * dropped here — they carry no block box and would strand against a formatting
150
+ * context they did not come from.
151
+ */
152
+ export function chunkElement(body: Element, targetChars = DEFAULT_CHUNK_CHARS): ChunkResult {
153
+ const nodes: ChunkNode[] = [];
154
+ for (const node of Array.from(body.childNodes)) {
155
+ if (node.nodeType === ELEMENT_NODE) {
156
+ const element = node as Element;
157
+ nodes.push({
158
+ tag: element.tagName.toUpperCase(),
159
+ chars: element.textContent?.length ?? 0,
160
+ html: element.outerHTML,
161
+ });
162
+ } else if (node.nodeType === TEXT_NODE) {
163
+ const text = node.textContent ?? '';
164
+ if (text.trim().length === 0) continue;
165
+ nodes.push({ tag: '#text', chars: text.length, html: escapeXmlText(text) });
166
+ }
167
+ }
168
+ return chunkNodes(nodes, targetChars);
169
+ }