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.
- package/LICENSE +21 -0
- package/README.md +277 -0
- package/dist/core/book.d.ts +75 -0
- package/dist/core/book.d.ts.map +1 -0
- package/dist/core/book.js +2 -0
- package/dist/core/book.js.map +1 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +29 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/format.d.ts +13 -0
- package/dist/core/format.d.ts.map +1 -0
- package/dist/core/format.js +2 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/graphemes.d.ts +36 -0
- package/dist/core/graphemes.d.ts.map +1 -0
- package/dist/core/graphemes.js +62 -0
- package/dist/core/graphemes.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lookup.d.ts +10 -0
- package/dist/core/lookup.d.ts.map +1 -0
- package/dist/core/lookup.js +16 -0
- package/dist/core/lookup.js.map +1 -0
- package/dist/core/open.d.ts +12 -0
- package/dist/core/open.d.ts.map +1 -0
- package/dist/core/open.js +23 -0
- package/dist/core/open.js.map +1 -0
- package/dist/core/position.d.ts +121 -0
- package/dist/core/position.d.ts.map +1 -0
- package/dist/core/position.js +327 -0
- package/dist/core/position.js.map +1 -0
- package/dist/core/reading-text.d.ts +77 -0
- package/dist/core/reading-text.d.ts.map +1 -0
- package/dist/core/reading-text.js +144 -0
- package/dist/core/reading-text.js.map +1 -0
- package/dist/core/source.d.ts +17 -0
- package/dist/core/source.d.ts.map +1 -0
- package/dist/core/source.js +39 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/storage.d.ts +7 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +2 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/text.d.ts +18 -0
- package/dist/core/text.d.ts.map +1 -0
- package/dist/core/text.js +32 -0
- package/dist/core/text.js.map +1 -0
- package/dist/formats/epub/href.d.ts +14 -0
- package/dist/formats/epub/href.d.ts.map +1 -0
- package/dist/formats/epub/href.js +52 -0
- package/dist/formats/epub/href.js.map +1 -0
- package/dist/formats/epub/index.d.ts +3 -0
- package/dist/formats/epub/index.d.ts.map +1 -0
- package/dist/formats/epub/index.js +223 -0
- package/dist/formats/epub/index.js.map +1 -0
- package/dist/formats/epub/opf.d.ts +27 -0
- package/dist/formats/epub/opf.d.ts.map +1 -0
- package/dist/formats/epub/opf.js +110 -0
- package/dist/formats/epub/opf.js.map +1 -0
- package/dist/formats/epub/toc.d.ts +6 -0
- package/dist/formats/epub/toc.d.ts.map +1 -0
- package/dist/formats/epub/toc.js +88 -0
- package/dist/formats/epub/toc.js.map +1 -0
- package/dist/formats/fb2/index.d.ts +13 -0
- package/dist/formats/fb2/index.d.ts.map +1 -0
- package/dist/formats/fb2/index.js +317 -0
- package/dist/formats/fb2/index.js.map +1 -0
- package/dist/formats/index.d.ts +4 -0
- package/dist/formats/index.d.ts.map +1 -0
- package/dist/formats/index.js +4 -0
- package/dist/formats/index.js.map +1 -0
- package/dist/formats/text/index.d.ts +11 -0
- package/dist/formats/text/index.d.ts.map +1 -0
- package/dist/formats/text/index.js +201 -0
- package/dist/formats/text/index.js.map +1 -0
- package/dist/formats/xml.d.ts +33 -0
- package/dist/formats/xml.d.ts.map +1 -0
- package/dist/formats/xml.js +321 -0
- package/dist/formats/xml.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/chunk.d.ts +51 -0
- package/dist/layout/chunk.d.ts.map +1 -0
- package/dist/layout/chunk.js +131 -0
- package/dist/layout/chunk.js.map +1 -0
- package/dist/layout/index.d.ts +238 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +476 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/reader/index.d.ts +231 -0
- package/dist/reader/index.d.ts.map +1 -0
- package/dist/reader/index.js +958 -0
- package/dist/reader/index.js.map +1 -0
- package/dist/reader/input.d.ts +69 -0
- package/dist/reader/input.d.ts.map +1 -0
- package/dist/reader/input.js +75 -0
- package/dist/reader/input.js.map +1 -0
- package/dist/search/extract.d.ts +51 -0
- package/dist/search/extract.d.ts.map +1 -0
- package/dist/search/extract.js +401 -0
- package/dist/search/extract.js.map +1 -0
- package/dist/search/index.d.ts +11 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +11 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/matcher.d.ts +56 -0
- package/dist/search/matcher.d.ts.map +1 -0
- package/dist/search/matcher.js +128 -0
- package/dist/search/matcher.js.map +1 -0
- package/dist/search/normalize.d.ts +48 -0
- package/dist/search/normalize.d.ts.map +1 -0
- package/dist/search/normalize.js +103 -0
- package/dist/search/normalize.js.map +1 -0
- package/dist/view/allowlist.d.ts +59 -0
- package/dist/view/allowlist.d.ts.map +1 -0
- package/dist/view/allowlist.js +198 -0
- package/dist/view/allowlist.js.map +1 -0
- package/dist/view/appearance.d.ts +137 -0
- package/dist/view/appearance.d.ts.map +1 -0
- package/dist/view/appearance.js +274 -0
- package/dist/view/appearance.js.map +1 -0
- package/dist/view/css.d.ts +24 -0
- package/dist/view/css.d.ts.map +1 -0
- package/dist/view/css.js +153 -0
- package/dist/view/css.js.map +1 -0
- package/dist/view/frame.d.ts +54 -0
- package/dist/view/frame.d.ts.map +1 -0
- package/dist/view/frame.js +980 -0
- package/dist/view/frame.js.map +1 -0
- package/dist/view/host.d.ts +160 -0
- package/dist/view/host.d.ts.map +1 -0
- package/dist/view/host.js +385 -0
- package/dist/view/host.js.map +1 -0
- package/dist/view/index.d.ts +12 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +7 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/protocol.d.ts +220 -0
- package/dist/view/protocol.d.ts.map +1 -0
- package/dist/view/protocol.js +246 -0
- package/dist/view/protocol.js.map +1 -0
- package/dist/view/reference.d.ts +18 -0
- package/dist/view/reference.d.ts.map +1 -0
- package/dist/view/reference.js +25 -0
- package/dist/view/reference.js.map +1 -0
- package/dist/view/resources.d.ts +51 -0
- package/dist/view/resources.d.ts.map +1 -0
- package/dist/view/resources.js +242 -0
- package/dist/view/resources.js.map +1 -0
- package/dist/view/sanitize.d.ts +32 -0
- package/dist/view/sanitize.d.ts.map +1 -0
- package/dist/view/sanitize.js +226 -0
- package/dist/view/sanitize.js.map +1 -0
- package/dist/zip/central.d.ts +12 -0
- package/dist/zip/central.d.ts.map +1 -0
- package/dist/zip/central.js +72 -0
- package/dist/zip/central.js.map +1 -0
- package/dist/zip/crc32.d.ts +3 -0
- package/dist/zip/crc32.d.ts.map +1 -0
- package/dist/zip/crc32.js +28 -0
- package/dist/zip/crc32.js.map +1 -0
- package/dist/zip/eocd.d.ts +9 -0
- package/dist/zip/eocd.d.ts.map +1 -0
- package/dist/zip/eocd.js +84 -0
- package/dist/zip/eocd.js.map +1 -0
- package/dist/zip/errors.d.ts +19 -0
- package/dist/zip/errors.d.ts.map +1 -0
- package/dist/zip/errors.js +37 -0
- package/dist/zip/errors.js.map +1 -0
- package/dist/zip/index.d.ts +18 -0
- package/dist/zip/index.d.ts.map +1 -0
- package/dist/zip/index.js +78 -0
- package/dist/zip/index.js.map +1 -0
- package/dist/zip/inflate.d.ts +2 -0
- package/dist/zip/inflate.d.ts.map +1 -0
- package/dist/zip/inflate.js +43 -0
- package/dist/zip/inflate.js.map +1 -0
- package/dist/zip/source.d.ts +15 -0
- package/dist/zip/source.d.ts.map +1 -0
- package/dist/zip/source.js +34 -0
- package/dist/zip/source.js.map +1 -0
- package/package.json +75 -0
- package/src/core/book.ts +80 -0
- package/src/core/errors.ts +31 -0
- package/src/core/format.ts +14 -0
- package/src/core/graphemes.ts +73 -0
- package/src/core/index.ts +27 -0
- package/src/core/lookup.ts +16 -0
- package/src/core/open.ts +31 -0
- package/src/core/position.ts +485 -0
- package/src/core/reading-text.ts +200 -0
- package/src/core/source.ts +61 -0
- package/src/core/storage.ts +6 -0
- package/src/core/text.ts +35 -0
- package/src/formats/epub/href.ts +54 -0
- package/src/formats/epub/index.ts +235 -0
- package/src/formats/epub/opf.ts +143 -0
- package/src/formats/epub/toc.ts +120 -0
- package/src/formats/fb2/index.ts +348 -0
- package/src/formats/index.ts +3 -0
- package/src/formats/text/index.ts +208 -0
- package/src/formats/xml.ts +326 -0
- package/src/index.ts +26 -0
- package/src/layout/chunk.ts +169 -0
- package/src/layout/index.ts +573 -0
- package/src/reader/index.ts +1206 -0
- package/src/reader/input.ts +122 -0
- package/src/search/extract.ts +403 -0
- package/src/search/index.ts +10 -0
- package/src/search/matcher.ts +187 -0
- package/src/search/normalize.ts +122 -0
- package/src/view/allowlist.ts +273 -0
- package/src/view/appearance.ts +380 -0
- package/src/view/css.ts +172 -0
- package/src/view/frame.ts +1018 -0
- package/src/view/host.ts +527 -0
- package/src/view/index.ts +24 -0
- package/src/view/protocol.ts +412 -0
- package/src/view/reference.ts +30 -0
- package/src/view/resources.ts +265 -0
- package/src/view/sanitize.ts +266 -0
- package/src/zip/central.ts +85 -0
- package/src/zip/crc32.ts +30 -0
- package/src/zip/eocd.ts +97 -0
- package/src/zip/errors.ts +41 -0
- package/src/zip/index.ts +114 -0
- package/src/zip/inflate.ts +41 -0
- 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
|
+
}
|