frond-js 0.4.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.en.md +247 -0
- package/README.md +211 -0
- package/dist/abort-BY8vBk0v.d.cts +99 -0
- package/dist/abort-BY8vBk0v.d.ts +99 -0
- package/dist/adapter-3G46J3CA.cjs +503 -0
- package/dist/adapter-3ONQWJVQ.js +501 -0
- package/dist/adapter-55QHWRSE.js +124 -0
- package/dist/adapter-DF34GBWJ.cjs +19 -0
- package/dist/adapter-EXTNILTC.cjs +126 -0
- package/dist/adapter-GOFC7TMC.js +284 -0
- package/dist/adapter-LRJTQ47I.cjs +286 -0
- package/dist/adapter-TWWZML4A.js +17 -0
- package/dist/adapter-ZM5FQTJT.js +1415 -0
- package/dist/adapter-ZRNSDQUV.cjs +1417 -0
- package/dist/chunk-2SUG7YFZ.cjs +108 -0
- package/dist/chunk-B2L2YVXD.js +89 -0
- package/dist/chunk-D4KWSEZD.js +393 -0
- package/dist/chunk-EZTIZO6R.cjs +430 -0
- package/dist/chunk-G7DLWGBW.cjs +103 -0
- package/dist/chunk-GTGLDLJD.cjs +479 -0
- package/dist/chunk-IIV6VIUJ.cjs +83 -0
- package/dist/chunk-JDTHZQUK.js +102 -0
- package/dist/chunk-JJVXT3AC.js +30 -0
- package/dist/chunk-LIXRYQL2.js +473 -0
- package/dist/chunk-NABYHI6X.cjs +400 -0
- package/dist/chunk-SJVOYNTF.js +425 -0
- package/dist/chunk-SLI2YL25.cjs +252 -0
- package/dist/chunk-U4264IQH.js +78 -0
- package/dist/chunk-UY2YRCFC.js +250 -0
- package/dist/chunk-WCZTTQ7Z.cjs +32 -0
- package/dist/core/index.cjs +162 -0
- package/dist/core/index.d.cts +321 -0
- package/dist/core/index.d.ts +321 -0
- package/dist/core/index.js +49 -0
- package/dist/default-DRLIJX73.js +1183 -0
- package/dist/default-UK52WOO5.cjs +1192 -0
- package/dist/formats/epub/index.cjs +29 -0
- package/dist/formats/epub/index.d.cts +286 -0
- package/dist/formats/epub/index.d.ts +286 -0
- package/dist/formats/epub/index.js +11 -0
- package/dist/index.cjs +655 -0
- package/dist/index.d.cts +335 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +600 -0
- package/dist/render/index.cjs +2 -0
- package/dist/render/index.d.cts +116 -0
- package/dist/render/index.d.ts +116 -0
- package/dist/render/index.js +1 -0
- package/dist/types-B76GOMxj.d.ts +129 -0
- package/dist/types-B7mslPBY.d.cts +166 -0
- package/dist/types-B7mslPBY.d.ts +166 -0
- package/dist/types-BH88rUYt.d.cts +129 -0
- package/dist/types-C-5eHSRH.d.ts +379 -0
- package/dist/types-CPUqTEPW.d.cts +379 -0
- package/dist/types-DQYmArgv.d.cts +17 -0
- package/dist/types-DQYmArgv.d.ts +17 -0
- package/package.json +115 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
import { classifyReference, resolveArchivePath, directoryOf } from './chunk-U4264IQH.js';
|
|
2
|
+
import { StateError, throwIfAborted, FormatError, onAbort, SecurityError, createAbortError } from './chunk-B2L2YVXD.js';
|
|
3
|
+
|
|
4
|
+
// src/render/default/cfi-locate.ts
|
|
5
|
+
var ELEMENT_NODE = 1;
|
|
6
|
+
var TEXT_NODE = 3;
|
|
7
|
+
var CDATA_NODE = 4;
|
|
8
|
+
function isElementNode(node) {
|
|
9
|
+
return node.nodeType === ELEMENT_NODE;
|
|
10
|
+
}
|
|
11
|
+
function isCharacterData(node) {
|
|
12
|
+
return node.nodeType === TEXT_NODE || node.nodeType === CDATA_NODE;
|
|
13
|
+
}
|
|
14
|
+
function indexChildNodes(node) {
|
|
15
|
+
const slots = [];
|
|
16
|
+
for (const child of Array.from(node.childNodes)) {
|
|
17
|
+
if (!isElementNode(child) && !isCharacterData(child)) continue;
|
|
18
|
+
if (isCharacterData(child)) {
|
|
19
|
+
const last = slots[slots.length - 1];
|
|
20
|
+
if (last !== void 0 && last.kind === "chunk") last.nodes.push(child);
|
|
21
|
+
else slots.push({ kind: "chunk", nodes: [child] });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (slots[slots.length - 1]?.kind === "element") slots.push({ kind: "gap" });
|
|
25
|
+
slots.push({ kind: "element", node: child });
|
|
26
|
+
}
|
|
27
|
+
const head = slots[0];
|
|
28
|
+
if (head !== void 0 && head.kind === "element") {
|
|
29
|
+
slots.unshift({ kind: "edge", node: head.node });
|
|
30
|
+
}
|
|
31
|
+
const tail = slots[slots.length - 1];
|
|
32
|
+
if (tail !== void 0 && tail.kind === "element") {
|
|
33
|
+
slots.push({ kind: "edge", node: tail.node });
|
|
34
|
+
}
|
|
35
|
+
slots.unshift({ kind: "before" });
|
|
36
|
+
slots.push({ kind: "after" });
|
|
37
|
+
return slots;
|
|
38
|
+
}
|
|
39
|
+
function locateInChunk(chunk, offset) {
|
|
40
|
+
let remaining = offset;
|
|
41
|
+
for (const node of chunk) {
|
|
42
|
+
const length = node.nodeValue?.length ?? 0;
|
|
43
|
+
if (remaining <= length) return { node, offset: remaining };
|
|
44
|
+
remaining -= length;
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function closestElement(node) {
|
|
49
|
+
let current = node;
|
|
50
|
+
while (current !== null) {
|
|
51
|
+
if (isElementNode(current)) return current;
|
|
52
|
+
current = current.parentNode;
|
|
53
|
+
}
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
function foldWhitespace(value) {
|
|
57
|
+
return value.replace(/\s+/g, " ").trim();
|
|
58
|
+
}
|
|
59
|
+
function checkIdAssertion(node, expected, stepIndex, tally) {
|
|
60
|
+
tally.checked += 1;
|
|
61
|
+
const actual = isElementNode(node) ? node.id : null;
|
|
62
|
+
if (actual !== expected) {
|
|
63
|
+
tally.failures.push({ step: stepIndex, kind: "id", expected, actual });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function locateCfi(root, cfi) {
|
|
67
|
+
const doc = root === void 0 || root === null ? void 0 : root.ownerDocument;
|
|
68
|
+
if (doc === void 0 || doc === null) {
|
|
69
|
+
throw new StateError("locateCfi() \u9700\u8981\u4E00\u4E2A\u542B ownerDocument \u7684 root\uFF08\u89C1 Element \u5951\u7EA6\uFF09");
|
|
70
|
+
}
|
|
71
|
+
const point = cfi.kind === "range" ? cfi.start : cfi;
|
|
72
|
+
const path = point.paths[point.paths.length - 1];
|
|
73
|
+
if (path === void 0) return void 0;
|
|
74
|
+
const steps = path.steps;
|
|
75
|
+
if (steps.length === 0) return void 0;
|
|
76
|
+
const tally = { checked: 0, failures: [] };
|
|
77
|
+
const lastStep = steps[steps.length - 1];
|
|
78
|
+
if (lastStep !== void 0 && lastStep.id !== void 0) {
|
|
79
|
+
const byId = doc.getElementById(lastStep.id);
|
|
80
|
+
if (byId !== null) {
|
|
81
|
+
tally.checked += 1;
|
|
82
|
+
return {
|
|
83
|
+
node: byId,
|
|
84
|
+
element: byId,
|
|
85
|
+
offset: 0,
|
|
86
|
+
viaIdAssertion: true,
|
|
87
|
+
assertions: { checked: tally.checked, matched: 1, failures: [] }
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let current = root;
|
|
92
|
+
let offset = 0;
|
|
93
|
+
steps: for (const [stepIndex, step] of steps.entries()) {
|
|
94
|
+
const slot = indexChildNodes(current)[step.index];
|
|
95
|
+
if (slot === void 0) return void 0;
|
|
96
|
+
switch (slot.kind) {
|
|
97
|
+
case "before":
|
|
98
|
+
case "after":
|
|
99
|
+
case "gap":
|
|
100
|
+
if (step.id !== void 0) checkIdAssertion(current, step.id, stepIndex, tally);
|
|
101
|
+
offset = 0;
|
|
102
|
+
break steps;
|
|
103
|
+
case "edge":
|
|
104
|
+
if (step.id !== void 0) checkIdAssertion(slot.node, step.id, stepIndex, tally);
|
|
105
|
+
current = slot.node;
|
|
106
|
+
offset = 0;
|
|
107
|
+
break steps;
|
|
108
|
+
case "chunk": {
|
|
109
|
+
if (stepIndex !== steps.length - 1) return void 0;
|
|
110
|
+
const located = locateInChunk(slot.nodes, point.offset ?? 0);
|
|
111
|
+
if (located === void 0) return void 0;
|
|
112
|
+
if (step.id !== void 0) checkIdAssertion(located.node, step.id, stepIndex, tally);
|
|
113
|
+
current = located.node;
|
|
114
|
+
offset = located.offset;
|
|
115
|
+
break steps;
|
|
116
|
+
}
|
|
117
|
+
case "element":
|
|
118
|
+
if (step.id !== void 0) checkIdAssertion(slot.node, step.id, stepIndex, tally);
|
|
119
|
+
current = slot.node;
|
|
120
|
+
offset = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const element = closestElement(current);
|
|
124
|
+
if (element === void 0) return void 0;
|
|
125
|
+
const value = isCharacterData(current) ? current.nodeValue ?? "" : "";
|
|
126
|
+
for (const [kind, expected] of [
|
|
127
|
+
["text", point.text],
|
|
128
|
+
["textAfter", point.textAfter]
|
|
129
|
+
]) {
|
|
130
|
+
if (expected === void 0) continue;
|
|
131
|
+
tally.checked += 1;
|
|
132
|
+
const actual = foldWhitespace(kind === "text" ? value.slice(0, offset) : value.slice(offset));
|
|
133
|
+
const wanted = foldWhitespace(expected);
|
|
134
|
+
const matched = kind === "text" ? actual.endsWith(wanted) : actual.startsWith(wanted);
|
|
135
|
+
if (!matched) tally.failures.push({ step: steps.length - 1, kind, expected, actual });
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
node: current,
|
|
139
|
+
element,
|
|
140
|
+
offset,
|
|
141
|
+
viaIdAssertion: false,
|
|
142
|
+
assertions: {
|
|
143
|
+
checked: tally.checked,
|
|
144
|
+
matched: tally.checked - tally.failures.length,
|
|
145
|
+
failures: tally.failures
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/render/default/sandbox.ts
|
|
151
|
+
var CONTENT_SANDBOX = "allow-same-origin";
|
|
152
|
+
var CONTENT_CSP_DIRECTIVES = [
|
|
153
|
+
"default-src 'none'",
|
|
154
|
+
"script-src 'none'",
|
|
155
|
+
"object-src 'none'",
|
|
156
|
+
"base-uri 'none'",
|
|
157
|
+
"form-action 'none'",
|
|
158
|
+
"connect-src 'none'",
|
|
159
|
+
"img-src blob: data:",
|
|
160
|
+
"media-src blob:",
|
|
161
|
+
"style-src blob: 'unsafe-inline'",
|
|
162
|
+
"font-src blob: data:"
|
|
163
|
+
];
|
|
164
|
+
var CSP_HTTP_EQUIV = "content-security-policy";
|
|
165
|
+
function decodeAttr(value) {
|
|
166
|
+
return value.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
167
|
+
}
|
|
168
|
+
function* startTags(html) {
|
|
169
|
+
let i = 0;
|
|
170
|
+
for (; ; ) {
|
|
171
|
+
const lt = html.indexOf("<", i);
|
|
172
|
+
if (lt < 0) return;
|
|
173
|
+
if (html.startsWith("<!--", lt)) {
|
|
174
|
+
const close = html.indexOf("-->", lt + 4);
|
|
175
|
+
if (close < 0) return;
|
|
176
|
+
i = close + 3;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const rawName = /^<([a-zA-Z][^\s/>]*)/.exec(html.slice(lt))?.[1];
|
|
180
|
+
if (rawName === void 0) {
|
|
181
|
+
i = lt + 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
185
|
+
let j = lt + rawName.length + 1;
|
|
186
|
+
let closed = false;
|
|
187
|
+
while (j < html.length) {
|
|
188
|
+
const char = html[j];
|
|
189
|
+
if (char === ">") {
|
|
190
|
+
closed = true;
|
|
191
|
+
j += 1;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (char === '"' || char === "'") {
|
|
195
|
+
const close = html.indexOf(char, j + 1);
|
|
196
|
+
if (close < 0) break;
|
|
197
|
+
j = close + 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const key = /^[a-zA-Z][a-zA-Z0-9-]*/.exec(html.slice(j))?.[0];
|
|
201
|
+
if (key === void 0) {
|
|
202
|
+
j += 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const name = key.toLowerCase();
|
|
206
|
+
let k = j + key.length;
|
|
207
|
+
while (k < html.length && /\s/.test(html.charAt(k))) k += 1;
|
|
208
|
+
if (html[k] !== "=") {
|
|
209
|
+
if (!attrs.has(name)) attrs.set(name, "");
|
|
210
|
+
j = k;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
k += 1;
|
|
214
|
+
while (k < html.length && /\s/.test(html.charAt(k))) k += 1;
|
|
215
|
+
const quote = html[k];
|
|
216
|
+
if (quote === '"' || quote === "'") {
|
|
217
|
+
const close = html.indexOf(quote, k + 1);
|
|
218
|
+
if (close < 0) break;
|
|
219
|
+
if (!attrs.has(name)) attrs.set(name, decodeAttr(html.slice(k + 1, close)));
|
|
220
|
+
j = close + 1;
|
|
221
|
+
} else {
|
|
222
|
+
const raw = /^[^\s>]*/.exec(html.slice(k))?.[0] ?? "";
|
|
223
|
+
if (!attrs.has(name)) attrs.set(name, decodeAttr(raw));
|
|
224
|
+
j = k + raw.length;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!closed) return;
|
|
228
|
+
yield { name: rawName.toLowerCase(), start: lt, end: j, attrs };
|
|
229
|
+
i = j;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function cspMetaTags(html) {
|
|
233
|
+
const found = [];
|
|
234
|
+
for (const tag of startTags(html)) {
|
|
235
|
+
if (tag.name === "meta" && (tag.attrs.get("http-equiv") ?? "").toLowerCase() === CSP_HTTP_EQUIV) {
|
|
236
|
+
found.push(tag);
|
|
237
|
+
}
|
|
238
|
+
if (tag.name === "body") break;
|
|
239
|
+
}
|
|
240
|
+
return found;
|
|
241
|
+
}
|
|
242
|
+
function headTag(html) {
|
|
243
|
+
for (const tag of startTags(html)) {
|
|
244
|
+
if (tag.name === "head") return tag;
|
|
245
|
+
if (tag.name === "body") return void 0;
|
|
246
|
+
}
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
var WELL_FORMED_DIRECTIVE = /^[a-zA-Z0-9-]+(\s[\s\S]*)?$/;
|
|
250
|
+
function mergeCsp(bookPolicy) {
|
|
251
|
+
if (bookPolicy.trim() === "") return buildContentCsp();
|
|
252
|
+
const ours = new Set(CONTENT_CSP_DIRECTIVES.map((d) => d.trim().split(/\s+/)[0]));
|
|
253
|
+
const kept = bookPolicy.split(";").map((d) => d.trim()).filter((d) => {
|
|
254
|
+
if (d === "" || !WELL_FORMED_DIRECTIVE.test(d)) return false;
|
|
255
|
+
const space = d.search(/\s/);
|
|
256
|
+
const name = space === -1 ? d : d.slice(0, space);
|
|
257
|
+
return !ours.has(name.toLowerCase());
|
|
258
|
+
});
|
|
259
|
+
return [buildContentCsp(), ...kept].join("; ");
|
|
260
|
+
}
|
|
261
|
+
function metaTagFor(policy) {
|
|
262
|
+
const content = policy.replace(/&/g, "&").replace(/"/g, """);
|
|
263
|
+
return `<meta http-equiv="Content-Security-Policy" content="${content}">`;
|
|
264
|
+
}
|
|
265
|
+
function buildContentCsp(directives = CONTENT_CSP_DIRECTIVES) {
|
|
266
|
+
if (directives.length === 0) {
|
|
267
|
+
throw new SecurityError("\u5185\u5BB9 CSP \u7684\u6307\u4EE4\u5217\u8868\u4E3A\u7A7A \u2014\u2014 \u7A7A CSP \u7B49\u4E8E\u6CA1\u6709 CSP");
|
|
268
|
+
}
|
|
269
|
+
for (const directive of directives) {
|
|
270
|
+
if (directive.trim() === "") {
|
|
271
|
+
throw new SecurityError("\u5185\u5BB9 CSP \u542B\u7A7A\u6307\u4EE4 \u2014\u2014 \u62FC\u63A5\u7ED3\u679C\u4F1A\u51FA\u73B0 `;;`\uFF0C\u89E3\u6790\u884C\u4E3A\u4E0D\u53EF\u4F9D\u8D56");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const scriptSrc = directives.find((directive) => directive.startsWith("script-src "));
|
|
275
|
+
if (scriptSrc === void 0) {
|
|
276
|
+
throw new SecurityError(
|
|
277
|
+
"\u5185\u5BB9 CSP \u7F3A\u5C11 script-src \u2014\u2014 \u300C\u6CA1\u5199\u300D\u4E0D\u7B49\u4E8E\u300C\u7981\u6B62\u300D\uFF0CH9 \u8981\u6C42\u663E\u5F0F 'none'"
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (scriptSrc.trim() !== "script-src 'none'") {
|
|
281
|
+
throw new SecurityError(
|
|
282
|
+
`\u5185\u5BB9 CSP \u7684 script-src \u5FC5\u987B\u662F 'none'\uFF0C\u5B9E\u9645\u4E3A ${JSON.stringify(scriptSrc.slice("script-src ".length))} \u2014\u2014 \u672C\u9879\u76EE\u5185\u5BB9\u4E0E\u5BBF\u4E3B\u540C\u6E90\uFF0C\u4EFB\u4F55\u653E\u884C\u90FD\u7B49\u4E8E\u653E\u5F03\u811A\u672C\u9632\u7EBF`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return directives.join("; ");
|
|
286
|
+
}
|
|
287
|
+
function allowsScripts(sandboxValue) {
|
|
288
|
+
if (sandboxValue === null) return false;
|
|
289
|
+
return sandboxValue.trim().split(/\s+/).filter((token) => token !== "").includes("allow-scripts");
|
|
290
|
+
}
|
|
291
|
+
function withContentCsp(html) {
|
|
292
|
+
const existing = cspMetaTags(html);
|
|
293
|
+
let stripped = "";
|
|
294
|
+
let cursor = 0;
|
|
295
|
+
for (const meta of existing) {
|
|
296
|
+
stripped += html.slice(cursor, meta.start);
|
|
297
|
+
cursor = meta.end;
|
|
298
|
+
}
|
|
299
|
+
stripped += html.slice(cursor);
|
|
300
|
+
const head = headTag(stripped);
|
|
301
|
+
if (head === void 0) {
|
|
302
|
+
throw new SecurityError(
|
|
303
|
+
'\u5185\u5BB9 HTML \u4E2D\u627E\u4E0D\u5230\u53EF\u7528\u7684 <head> \u2014\u2014 \u62D2\u7EDD\u9759\u9ED8\u8865\u4E00\u4E2A\uFF1AEPUB \u7AE0\u8282\u662F XHTML\uFF0C\u7F3A <head> \u8BF4\u660E\u4E0A\u6E38\u89E3\u6790\u94FE\u51FA\u4E86\u95EE\u9898\uFF0C\u8865 head \u4F1A\u8BA9 CSP \u56E0\u4F4D\u7F6E\u4E0D\u5BF9\u800C\u5931\u6548\uFF0C\u6BD4\u76F4\u63A5\u62A5\u9519\u5371\u9669\u5F97\u591A\u3002\u26A0\uFE0F \u53E6\u4E00\u79CD\u89E6\u53D1\uFF08FI-X19 \u2462\uFF09\uFF1A`<head data-a="x>` \u8FD9\u7C7B\u672A\u95ED\u5408\u5F15\u53F7\u4F1A\u628A\u5176\u540E\u6574\u4EFD\u6587\u6863\u5403\u6210\u5C5E\u6027\u503C\uFF0C\u6B64\u65F6\u6CA1\u6709\u4EFB\u4F55\u4F4D\u7F6E\u80FD\u8BA9 meta \u6210\u4E3A\u5143\u7D20 \u2014\u2014 \u6240\u4EE5\u8FD9\u91CC**\u629B\u9519**\u800C\u4E0D\u662F\u300C\u63D2\u4E86\u518D\u8BF4\u300D\u3002'
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const bookPolicy = existing.map((meta) => meta.attrs.get("content") ?? "").join("; ");
|
|
307
|
+
return stripped.slice(0, head.end) + metaTagFor(mergeCsp(bookPolicy)) + stripped.slice(head.end);
|
|
308
|
+
}
|
|
309
|
+
function withDocumentAttributes(html, attributes) {
|
|
310
|
+
const language = attributes?.language?.trim() ?? "";
|
|
311
|
+
const requested = attributes?.direction;
|
|
312
|
+
const direction = requested === "ltr" || requested === "rtl" ? requested : void 0;
|
|
313
|
+
if (language === "" && direction === void 0) return html;
|
|
314
|
+
const OPEN_TAG = /<html\b([^>]*)>/i;
|
|
315
|
+
const match = OPEN_TAG.exec(html);
|
|
316
|
+
if (match === null) {
|
|
317
|
+
throw new FormatError(
|
|
318
|
+
"\u5185\u5BB9 HTML \u4E2D\u627E\u4E0D\u5230 <html> \u5F00\u6807\u7B7E \u2014\u2014 \u62D2\u7EDD\u9759\u9ED8\u8DF3\u8FC7\u6587\u6863\u6807\u6CE8\uFF1A\u8DF3\u8FC7\u4F1A\u8BA9\u300Ca11y \u5DF2\u6807\u6CE8\u300D\u53D8\u6210\u4E00\u53E5\u6CA1\u6709\u8BC1\u636E\u7684\u8BDD\uFF08PROMPT \xA71\uFF1A\u663E\u5F0F\u5931\u8D25 > \u9759\u9ED8\u964D\u7EA7\uFF09"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const openTagAttributes = match[1] ?? "";
|
|
322
|
+
const insertions = [];
|
|
323
|
+
if (language !== "" && !/(?:^|\s)lang\s*=/i.test(openTagAttributes)) {
|
|
324
|
+
insertions.push(` lang="${language.replace(/&/g, "&").replace(/"/g, """)}"`);
|
|
325
|
+
}
|
|
326
|
+
if (direction !== void 0 && !/(?:^|\s)dir\s*=/i.test(openTagAttributes)) {
|
|
327
|
+
insertions.push(` dir="${direction}"`);
|
|
328
|
+
}
|
|
329
|
+
if (insertions.length === 0) return html;
|
|
330
|
+
const insertion = insertions.join("");
|
|
331
|
+
return html.replace(OPEN_TAG, (openTag) => {
|
|
332
|
+
return `${openTag.slice(0, -1)}${insertion}>`;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function applyContentSandbox(frame, html, content) {
|
|
336
|
+
const securedHtml = withDocumentAttributes(withContentCsp(html), content);
|
|
337
|
+
frame.setAttribute("sandbox", CONTENT_SANDBOX);
|
|
338
|
+
if (allowsScripts(frame.getAttribute("sandbox"))) {
|
|
339
|
+
throw new SecurityError(
|
|
340
|
+
"\u6C99\u7BB1\u5199\u5165\u70B9\u81EA\u68C0\u5931\u8D25\uFF1A\u5199\u5165\u540E sandbox \u5C5E\u6027\u5141\u8BB8\u811A\u672C\u6267\u884C\u3002\u8FD9\u662F ADR-0008 \u7684 A4/A7 \u5931\u6548\u6A21\u5F0F\uFF0C\u6E32\u67D3\u5FC5\u987B\u6574\u4F53\u5931\u8D25\u800C\u4E0D\u662F\u7EE7\u7EED"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const url = URL.createObjectURL(new Blob([securedHtml], { type: "text/html" }));
|
|
344
|
+
frame.src = url;
|
|
345
|
+
return url;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/render/default/frame.ts
|
|
349
|
+
var DEFAULT_IMAGE_TIMEOUT_MS = 3e3;
|
|
350
|
+
var WRAPPER_MARKER = "data-frond-frame-buffer";
|
|
351
|
+
function createFrameBuffer(host) {
|
|
352
|
+
const { element, ownerDocument } = host;
|
|
353
|
+
if (element === void 0 || ownerDocument === void 0) {
|
|
354
|
+
throw new StateError(
|
|
355
|
+
"createFrameBuffer() \u9700\u8981\u4E00\u4E2A\u542B element \u4E0E ownerDocument \u7684\u5BBF\u4E3B\uFF08\u89C1 RenderHost \u5951\u7EA6\uFF09"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const wrapper = ownerDocument.createElement("div");
|
|
359
|
+
wrapper.setAttribute(WRAPPER_MARKER, "");
|
|
360
|
+
wrapper.style.position = "relative";
|
|
361
|
+
wrapper.style.width = "100%";
|
|
362
|
+
wrapper.style.height = "100%";
|
|
363
|
+
const first = createFrame(ownerDocument, wrapper);
|
|
364
|
+
const second = createFrame(ownerDocument, wrapper);
|
|
365
|
+
let visibleIsFirst = true;
|
|
366
|
+
let disposed = false;
|
|
367
|
+
const pageUrls = /* @__PURE__ */ new Map();
|
|
368
|
+
function applyVisibility() {
|
|
369
|
+
first.style.visibility = visibleIsFirst ? "visible" : "hidden";
|
|
370
|
+
second.style.visibility = visibleIsFirst ? "hidden" : "visible";
|
|
371
|
+
}
|
|
372
|
+
function assertAlive(operation) {
|
|
373
|
+
if (disposed) {
|
|
374
|
+
throw new StateError(
|
|
375
|
+
`\u5E27\u7F13\u51B2\u533A\u5DF2\u91CA\u653E\uFF0C\u4E0D\u80FD\u518D\u8C03\u7528 ${operation}() \u2014\u2014 \u91CA\u653E\u540E\u9759\u9ED8\u64CD\u4F5C\u5DF2\u79FB\u9664\u7684\u5E27\u53EA\u4F1A\u5236\u9020\u96BE\u67E5\u7684\u5E7D\u7075\u6E32\u67D3`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function assertOwned(frame, operation) {
|
|
380
|
+
if (frame !== first && frame !== second) {
|
|
381
|
+
throw new StateError(
|
|
382
|
+
`${operation}() \u6536\u5230\u7684\u5E27\u4E0D\u5C5E\u4E8E\u672C\u7F13\u51B2\u533A \u2014\u2014 \u4F20\u522B\u7684\u5E27\u4F1A\u5BFC\u81F4\u300C\u7B49\u4E86\u522B\u4EBA\u7684\u5E27\u3001\u6D4B\u4E86\u81EA\u5DF1\u7684\u5E27\u300D`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function waitForLayoutOn(frame, options, operation) {
|
|
387
|
+
const { signal, imageTimeoutMs = DEFAULT_IMAGE_TIMEOUT_MS } = options;
|
|
388
|
+
throwIfAborted(signal);
|
|
389
|
+
assertAlive(operation);
|
|
390
|
+
assertOwned(frame, operation);
|
|
391
|
+
const doc = frame.contentDocument;
|
|
392
|
+
if (doc === null) {
|
|
393
|
+
throw new StateError(
|
|
394
|
+
`${operation}() \u62FF\u4E0D\u5230\u5E27\u7684 contentDocument \u2014\u2014 \u8BE5\u5E27\u5904\u4E8E opaque origin\u3002ADR-0008 \u5B9E\u6D4B\uFF1Asandbox \u672A\u5E26 allow-same-origin \u65F6\u5C31\u662F\u8FD9\u4E2A\u75C7\u72B6\uFF0C\u6B64\u65F6\u5206\u9875\u6D4B\u91CF\u4F1A\u6C38\u8FDC\u4E3A\u7A7A\uFF0C\u5FC5\u987B\u5F53\u573A\u5931\u8D25\u800C\u4E0D\u662F\u9759\u9ED8\u7B49\u5F85`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
await raceWithAbort(doc.fonts.ready, signal);
|
|
398
|
+
await waitForImages(doc, imageTimeoutMs, signal);
|
|
399
|
+
return doc;
|
|
400
|
+
}
|
|
401
|
+
applyVisibility();
|
|
402
|
+
element.appendChild(wrapper);
|
|
403
|
+
return {
|
|
404
|
+
get visible() {
|
|
405
|
+
return visibleIsFirst ? first : second;
|
|
406
|
+
},
|
|
407
|
+
get hidden() {
|
|
408
|
+
return visibleIsFirst ? second : first;
|
|
409
|
+
},
|
|
410
|
+
swap() {
|
|
411
|
+
assertAlive("swap");
|
|
412
|
+
visibleIsFirst = !visibleIsFirst;
|
|
413
|
+
applyVisibility();
|
|
414
|
+
},
|
|
415
|
+
async load(frame, html, options = {}) {
|
|
416
|
+
const { signal } = options;
|
|
417
|
+
throwIfAborted(signal);
|
|
418
|
+
assertAlive("load");
|
|
419
|
+
assertOwned(frame, "load");
|
|
420
|
+
const waiter = waitForFrameLoad(frame, signal);
|
|
421
|
+
const previous = pageUrls.get(frame);
|
|
422
|
+
let url;
|
|
423
|
+
try {
|
|
424
|
+
url = applyContentSandbox(frame, html, options);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
waiter.cancel();
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
pageUrls.set(frame, url);
|
|
430
|
+
try {
|
|
431
|
+
await waiter.promise;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (previous === void 0) pageUrls.delete(frame);
|
|
434
|
+
else pageUrls.set(frame, previous);
|
|
435
|
+
URL.revokeObjectURL(url);
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
if (previous !== void 0) URL.revokeObjectURL(previous);
|
|
439
|
+
return await waitForLayoutOn(frame, options, "load");
|
|
440
|
+
},
|
|
441
|
+
async waitForLayout(frame, options = {}) {
|
|
442
|
+
await waitForLayoutOn(frame, options, "waitForLayout");
|
|
443
|
+
},
|
|
444
|
+
dispose() {
|
|
445
|
+
if (disposed) return;
|
|
446
|
+
disposed = true;
|
|
447
|
+
wrapper.remove();
|
|
448
|
+
for (const pageUrl of pageUrls.values()) URL.revokeObjectURL(pageUrl);
|
|
449
|
+
pageUrls.clear();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function createFrame(ownerDocument, wrapper) {
|
|
454
|
+
const frame = ownerDocument.createElement("iframe");
|
|
455
|
+
frame.style.position = "absolute";
|
|
456
|
+
frame.style.top = "0";
|
|
457
|
+
frame.style.left = "0";
|
|
458
|
+
frame.style.width = "100%";
|
|
459
|
+
frame.style.height = "100%";
|
|
460
|
+
frame.style.border = "0";
|
|
461
|
+
wrapper.appendChild(frame);
|
|
462
|
+
return frame;
|
|
463
|
+
}
|
|
464
|
+
function waitForFrameLoad(frame, signal) {
|
|
465
|
+
let resolvePromise;
|
|
466
|
+
let rejectPromise;
|
|
467
|
+
let detachAbort;
|
|
468
|
+
function cleanup() {
|
|
469
|
+
frame.removeEventListener("load", onLoad);
|
|
470
|
+
detachAbort();
|
|
471
|
+
}
|
|
472
|
+
function settle(error) {
|
|
473
|
+
cleanup();
|
|
474
|
+
if (error === void 0) resolvePromise();
|
|
475
|
+
else rejectPromise(error);
|
|
476
|
+
}
|
|
477
|
+
function onLoad() {
|
|
478
|
+
settle();
|
|
479
|
+
}
|
|
480
|
+
function onAbortSignal() {
|
|
481
|
+
settle(createAbortError());
|
|
482
|
+
}
|
|
483
|
+
const promise = new Promise((resolve, reject) => {
|
|
484
|
+
resolvePromise = resolve;
|
|
485
|
+
rejectPromise = reject;
|
|
486
|
+
frame.addEventListener("load", onLoad);
|
|
487
|
+
detachAbort = onAbort(signal, onAbortSignal);
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
promise,
|
|
491
|
+
/**
|
|
492
|
+
* 放弃等待。**静默结束**(resolve 而非 reject):调用方此时已因别的错误抛出,
|
|
493
|
+
* 没有任何人会 `await` 这个 promise —— 若在这里 reject 就变成未处理的 rejection。
|
|
494
|
+
*/
|
|
495
|
+
cancel() {
|
|
496
|
+
settle();
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function raceWithAbort(task, signal) {
|
|
501
|
+
if (signal === void 0) return task;
|
|
502
|
+
let detach;
|
|
503
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
504
|
+
detach = onAbort(signal, () => {
|
|
505
|
+
reject(createAbortError());
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
return Promise.race([task, aborted]).finally(() => {
|
|
509
|
+
detach();
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function waitForImages(doc, timeoutMs, signal) {
|
|
513
|
+
const pending = [...doc.images].filter((image) => !image.complete);
|
|
514
|
+
if (pending.length === 0) return Promise.resolve();
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
let remaining = pending.length;
|
|
517
|
+
const cleanup = [];
|
|
518
|
+
const settle = (error) => {
|
|
519
|
+
for (const undo of cleanup) undo();
|
|
520
|
+
if (error === void 0) resolve();
|
|
521
|
+
else reject(error);
|
|
522
|
+
};
|
|
523
|
+
const onImageSettled = () => {
|
|
524
|
+
remaining -= 1;
|
|
525
|
+
if (remaining === 0) settle();
|
|
526
|
+
};
|
|
527
|
+
for (const image of pending) {
|
|
528
|
+
image.addEventListener("load", onImageSettled);
|
|
529
|
+
image.addEventListener("error", onImageSettled);
|
|
530
|
+
cleanup.push(() => {
|
|
531
|
+
image.removeEventListener("load", onImageSettled);
|
|
532
|
+
image.removeEventListener("error", onImageSettled);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
const timer = setTimeout(settle, timeoutMs);
|
|
536
|
+
cleanup.push(() => {
|
|
537
|
+
clearTimeout(timer);
|
|
538
|
+
});
|
|
539
|
+
cleanup.push(
|
|
540
|
+
onAbort(signal, () => {
|
|
541
|
+
settle(createAbortError());
|
|
542
|
+
})
|
|
543
|
+
);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/render/default/paginator.ts
|
|
548
|
+
var MIRRORED_WRITING_MODES = ["vertical-rl", "sideways-rl"];
|
|
549
|
+
var FORWARD_VERTICAL_WRITING_MODES = ["vertical-lr", "sideways-lr"];
|
|
550
|
+
function isMirroredAxis(direction, writingMode) {
|
|
551
|
+
if (MIRRORED_WRITING_MODES.includes(writingMode)) return true;
|
|
552
|
+
if (FORWARD_VERTICAL_WRITING_MODES.includes(writingMode)) return false;
|
|
553
|
+
return direction === "rtl";
|
|
554
|
+
}
|
|
555
|
+
function isVerticalWriting(writingMode) {
|
|
556
|
+
return MIRRORED_WRITING_MODES.includes(writingMode) || FORWARD_VERTICAL_WRITING_MODES.includes(writingMode);
|
|
557
|
+
}
|
|
558
|
+
function computePageCount(scrollWidth, pageWidth) {
|
|
559
|
+
return Math.ceil(scrollWidth / pageWidth);
|
|
560
|
+
}
|
|
561
|
+
function pageOf(metrics, rect) {
|
|
562
|
+
const offset = metrics.mirrored ? metrics.originRight - rect.right : rect.left - metrics.originLeft;
|
|
563
|
+
const page = Math.floor(offset / metrics.pageWidth);
|
|
564
|
+
return Math.min(Math.max(page, 0), metrics.pageCount - 1);
|
|
565
|
+
}
|
|
566
|
+
function pageOfElement(element, metrics) {
|
|
567
|
+
const rect = element.getBoundingClientRect();
|
|
568
|
+
return pageOf(metrics, { left: rect.left, right: rect.right });
|
|
569
|
+
}
|
|
570
|
+
function createPaginator(doc, options = {}) {
|
|
571
|
+
const root = doc.documentElement;
|
|
572
|
+
if (root === null) {
|
|
573
|
+
throw new StateError(
|
|
574
|
+
"createPaginator() \u9700\u8981\u4E00\u4E2A\u542B documentElement \u7684\u6587\u6863 \u2014\u2014 \u5206\u9875\u7684\u5168\u90E8\u6837\u5F0F\u4E0E\u51E0\u4F55\u90FD\u6302\u5728\u5185\u5BB9\u6839\u4E0A"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
const view = doc.defaultView;
|
|
578
|
+
if (view === null) {
|
|
579
|
+
throw new StateError(
|
|
580
|
+
"createPaginator() \u62FF\u4E0D\u5230 defaultView \u2014\u2014 \u8BE5\u6587\u6863\u6CA1\u6709\u6D4F\u89C8\u4E0A\u4E0B\u6587\uFF08\u5982 createHTMLDocument() \u7684\u4EA7\u7269\uFF09\uFF0C\u65E2\u8BFB\u4E0D\u5230\u4E66\u5199\u65B9\u5411\u4E5F\u65E0\u6CD5\u5206\u9875"
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const { columnGapPx = 0, minPageSizePx = 1 } = options;
|
|
584
|
+
const style = root.style;
|
|
585
|
+
const savedStyles = /* @__PURE__ */ new Map();
|
|
586
|
+
let mode = options.mode ?? "paginated";
|
|
587
|
+
let disposed = false;
|
|
588
|
+
function writeStyle(property, value) {
|
|
589
|
+
if (!savedStyles.has(property)) savedStyles.set(property, style.getPropertyValue(property));
|
|
590
|
+
style.setProperty(property, value);
|
|
591
|
+
}
|
|
592
|
+
function restoreAll() {
|
|
593
|
+
for (const [property, previous] of savedStyles) {
|
|
594
|
+
if (previous === "") style.removeProperty(property);
|
|
595
|
+
else style.setProperty(property, previous);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function assertAlive(operation) {
|
|
599
|
+
if (disposed) {
|
|
600
|
+
throw new StateError(
|
|
601
|
+
`\u5206\u9875\u5668\u5DF2\u91CA\u653E\uFF0C\u4E0D\u80FD\u518D\u8C03\u7528 ${operation}() \u2014\u2014 \u91CA\u653E\u65F6\u6587\u6863\u7684\u884C\u5185\u6837\u5F0F\u5DF2\u6062\u590D\uFF0C\u7EE7\u7EED\u65BD\u52A0\u4F1A\u7559\u4E0B\u4E00\u4EFD\u65E0\u4EBA\u8D1F\u8D23\u7684\u6392\u7248`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function applyPaginated() {
|
|
606
|
+
writeStyle("height", "100%");
|
|
607
|
+
writeStyle("overflow", "hidden");
|
|
608
|
+
writeStyle("column-gap", `${String(columnGapPx)}px`);
|
|
609
|
+
writeStyle("column-fill", "auto");
|
|
610
|
+
}
|
|
611
|
+
if (mode === "paginated") applyPaginated();
|
|
612
|
+
return {
|
|
613
|
+
get mode() {
|
|
614
|
+
return mode;
|
|
615
|
+
},
|
|
616
|
+
get verticalWriting() {
|
|
617
|
+
return isVerticalWriting(view.getComputedStyle(root).writingMode);
|
|
618
|
+
},
|
|
619
|
+
setMode(nextMode, setModeOptions = {}) {
|
|
620
|
+
assertAlive("setMode");
|
|
621
|
+
throwIfAborted(setModeOptions.signal);
|
|
622
|
+
if (nextMode === mode) return;
|
|
623
|
+
mode = nextMode;
|
|
624
|
+
if (nextMode === "paginated") applyPaginated();
|
|
625
|
+
else restoreAll();
|
|
626
|
+
},
|
|
627
|
+
measure(measureOptions = {}) {
|
|
628
|
+
assertAlive("measure");
|
|
629
|
+
throwIfAborted(measureOptions.signal);
|
|
630
|
+
if (mode !== "paginated") {
|
|
631
|
+
throw new StateError(
|
|
632
|
+
"measure() \u53EA\u5728\u5206\u9875\u6A21\u5F0F\u4E0B\u6709\u610F\u4E49 \u2014\u2014 \u6EDA\u52A8\u6A21\u5F0F\u6CA1\u6709\u300C\u9875\u300D\uFF0C\u8FD4\u56DE\u4EFB\u4F55\u9875\u6570\u90FD\u662F\u7F16\u9020"
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const pageWidth = root.clientWidth;
|
|
636
|
+
const pageHeight = root.clientHeight;
|
|
637
|
+
if (pageWidth < minPageSizePx || pageHeight < minPageSizePx) {
|
|
638
|
+
throw new StateError(
|
|
639
|
+
`\u5206\u9875\u9700\u8981\u786E\u5B9A\u7684\u5BBF\u4E3B\u5C3A\u5BF8\uFF0C\u5B9E\u6D4B ${String(pageWidth)}\xD7${String(pageHeight)}px\uFF08\u4E0B\u9650 ${String(minPageSizePx)}px\uFF09\u2014\u2014 \u9AD8\u5EA6 auto / 0 \u4F1A\u8BA9\u9875\u6570\u9759\u9ED8\u81A8\u80C0\u6210\u8352\u8C2C\u503C`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
writeStyle("column-width", `${String(pageWidth)}px`);
|
|
643
|
+
const scrollWidth = root.scrollWidth;
|
|
644
|
+
const computed = view.getComputedStyle(root);
|
|
645
|
+
const rect = root.getBoundingClientRect();
|
|
646
|
+
return {
|
|
647
|
+
mode,
|
|
648
|
+
pageCount: computePageCount(scrollWidth, pageWidth),
|
|
649
|
+
pageWidth,
|
|
650
|
+
pageHeight,
|
|
651
|
+
originLeft: rect.left,
|
|
652
|
+
originRight: rect.right,
|
|
653
|
+
mirrored: isMirroredAxis(computed.direction, computed.writingMode)
|
|
654
|
+
};
|
|
655
|
+
},
|
|
656
|
+
dispose() {
|
|
657
|
+
if (disposed) return;
|
|
658
|
+
disposed = true;
|
|
659
|
+
restoreAll();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/render/default/resources.ts
|
|
665
|
+
function resolveArchiveReference(rawValue, sourcePath) {
|
|
666
|
+
if (classifyReference(rawValue) !== "archive") return void 0;
|
|
667
|
+
try {
|
|
668
|
+
return resolveArchivePath(directoryOf(sourcePath), rawValue.trim());
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (error instanceof FormatError) return void 0;
|
|
671
|
+
throw error;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
var CSS_KEY_PREFIX = "\0css:";
|
|
675
|
+
function cssKey(cssPath) {
|
|
676
|
+
return `${CSS_KEY_PREFIX}${cssPath}`;
|
|
677
|
+
}
|
|
678
|
+
function createResourceRegistry(loader) {
|
|
679
|
+
const urls = /* @__PURE__ */ new Map();
|
|
680
|
+
const pending = /* @__PURE__ */ new Map();
|
|
681
|
+
const refs = /* @__PURE__ */ new Map();
|
|
682
|
+
const open = /* @__PURE__ */ new Set();
|
|
683
|
+
let nextGeneration = 1;
|
|
684
|
+
let epoch = 0;
|
|
685
|
+
function retain(archivePath) {
|
|
686
|
+
if (open.size === 0) return false;
|
|
687
|
+
const holders = refs.get(archivePath) ?? /* @__PURE__ */ new Set();
|
|
688
|
+
for (const id of open) holders.add(id);
|
|
689
|
+
refs.set(archivePath, holders);
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
function release(archivePath) {
|
|
693
|
+
const url = urls.get(archivePath);
|
|
694
|
+
if (url !== void 0) {
|
|
695
|
+
URL.revokeObjectURL(url);
|
|
696
|
+
urls.delete(archivePath);
|
|
697
|
+
}
|
|
698
|
+
refs.delete(archivePath);
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
get size() {
|
|
702
|
+
return urls.size;
|
|
703
|
+
},
|
|
704
|
+
read(archivePath, options) {
|
|
705
|
+
return loader(archivePath, options);
|
|
706
|
+
},
|
|
707
|
+
async resolve(archivePath, options) {
|
|
708
|
+
const managed = retain(archivePath);
|
|
709
|
+
const cached = urls.get(archivePath);
|
|
710
|
+
if (cached !== void 0) return cached;
|
|
711
|
+
const inFlight = pending.get(archivePath);
|
|
712
|
+
if (inFlight !== void 0) return inFlight;
|
|
713
|
+
const myEpoch = epoch;
|
|
714
|
+
const task = (async () => {
|
|
715
|
+
const bytes = await loader(archivePath, options);
|
|
716
|
+
if (bytes === void 0) {
|
|
717
|
+
if (managed) refs.delete(archivePath);
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
const url = URL.createObjectURL(new Blob([toBlobPart(bytes)]));
|
|
721
|
+
if (myEpoch !== epoch) {
|
|
722
|
+
URL.revokeObjectURL(url);
|
|
723
|
+
return void 0;
|
|
724
|
+
}
|
|
725
|
+
if (managed && (refs.get(archivePath)?.size ?? 0) === 0) {
|
|
726
|
+
URL.revokeObjectURL(url);
|
|
727
|
+
return void 0;
|
|
728
|
+
}
|
|
729
|
+
urls.set(archivePath, url);
|
|
730
|
+
return url;
|
|
731
|
+
})().finally(() => {
|
|
732
|
+
pending.delete(archivePath);
|
|
733
|
+
});
|
|
734
|
+
pending.set(archivePath, task);
|
|
735
|
+
return task;
|
|
736
|
+
},
|
|
737
|
+
resolveCss(cssPath, cssText) {
|
|
738
|
+
const key = cssKey(cssPath);
|
|
739
|
+
retain(key);
|
|
740
|
+
const cached = urls.get(key);
|
|
741
|
+
if (cached !== void 0) return cached;
|
|
742
|
+
const url = URL.createObjectURL(new Blob([cssText], { type: "text/css" }));
|
|
743
|
+
urls.set(key, url);
|
|
744
|
+
return url;
|
|
745
|
+
},
|
|
746
|
+
beginGeneration() {
|
|
747
|
+
const id = nextGeneration;
|
|
748
|
+
nextGeneration += 1;
|
|
749
|
+
open.add(id);
|
|
750
|
+
return id;
|
|
751
|
+
},
|
|
752
|
+
endGeneration(generation) {
|
|
753
|
+
if (!open.delete(generation)) return;
|
|
754
|
+
for (const [archivePath, holders] of refs) {
|
|
755
|
+
if (!holders.delete(generation)) continue;
|
|
756
|
+
if (holders.size === 0) release(archivePath);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
dispose() {
|
|
760
|
+
epoch += 1;
|
|
761
|
+
open.clear();
|
|
762
|
+
refs.clear();
|
|
763
|
+
for (const url of urls.values()) {
|
|
764
|
+
URL.revokeObjectURL(url);
|
|
765
|
+
}
|
|
766
|
+
urls.clear();
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function toBlobPart(bytes) {
|
|
771
|
+
return bytes.buffer instanceof ArrayBuffer ? new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength) : new Uint8Array(bytes);
|
|
772
|
+
}
|
|
773
|
+
var DOCUMENT_URL_TARGETS = [
|
|
774
|
+
{ selector: "img[src]", attribute: "src" },
|
|
775
|
+
{ selector: "link[href]", attribute: "href" },
|
|
776
|
+
{ selector: "video[poster]", attribute: "poster" },
|
|
777
|
+
{ selector: "video[src]", attribute: "src" },
|
|
778
|
+
{ selector: "audio[src]", attribute: "src" },
|
|
779
|
+
{ selector: "source[src]", attribute: "src" },
|
|
780
|
+
{ selector: "track[src]", attribute: "src" }
|
|
781
|
+
];
|
|
782
|
+
var SRCSET_TARGETS = ["img[srcset]", "source[srcset]"];
|
|
783
|
+
async function rewriteSrcset(raw, registry, options) {
|
|
784
|
+
const candidates = raw.split(",").map((candidate) => candidate.trim()).filter((candidate) => candidate !== "");
|
|
785
|
+
const out = [];
|
|
786
|
+
const missing = [];
|
|
787
|
+
let rewritten = 0;
|
|
788
|
+
for (const candidate of candidates) {
|
|
789
|
+
const spaceAt = candidate.search(/\s/);
|
|
790
|
+
const rawUrl = spaceAt === -1 ? candidate : candidate.slice(0, spaceAt);
|
|
791
|
+
const descriptor = spaceAt === -1 ? "" : candidate.slice(spaceAt);
|
|
792
|
+
const archivePath = resolveArchiveReference(rawUrl, options.sourcePath);
|
|
793
|
+
if (archivePath === void 0) {
|
|
794
|
+
out.push(candidate);
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
const url = await registry.resolve(archivePath, options);
|
|
798
|
+
if (url === void 0) {
|
|
799
|
+
missing.push(archivePath);
|
|
800
|
+
out.push(candidate);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
out.push(`${url}${descriptor}`);
|
|
804
|
+
rewritten += 1;
|
|
805
|
+
}
|
|
806
|
+
return { value: out.join(", "), changed: rewritten > 0, rewritten, missing };
|
|
807
|
+
}
|
|
808
|
+
async function rewriteDocumentUrls(doc, registry, options) {
|
|
809
|
+
let rewritten = 0;
|
|
810
|
+
const missing = [];
|
|
811
|
+
for (const { selector, attribute } of DOCUMENT_URL_TARGETS) {
|
|
812
|
+
for (const element of doc.querySelectorAll(selector)) {
|
|
813
|
+
const raw = element.getAttribute(attribute);
|
|
814
|
+
if (raw === null) continue;
|
|
815
|
+
const archivePath = resolveArchiveReference(raw, options.sourcePath);
|
|
816
|
+
if (archivePath === void 0) continue;
|
|
817
|
+
const url = await registry.resolve(archivePath, options);
|
|
818
|
+
if (url === void 0) {
|
|
819
|
+
missing.push(archivePath);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
element.setAttribute(attribute, url);
|
|
823
|
+
rewritten += 1;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
for (const selector of SRCSET_TARGETS) {
|
|
827
|
+
for (const element of doc.querySelectorAll(selector)) {
|
|
828
|
+
const raw = element.getAttribute("srcset");
|
|
829
|
+
if (raw === null) continue;
|
|
830
|
+
const result = await rewriteSrcset(raw, registry, options);
|
|
831
|
+
if (result.changed) element.setAttribute("srcset", result.value);
|
|
832
|
+
rewritten += result.rewritten;
|
|
833
|
+
missing.push(...result.missing);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return { rewritten, missing };
|
|
837
|
+
}
|
|
838
|
+
async function rewriteStylesheetLinks(doc, registry, options) {
|
|
839
|
+
let rewritten = 0;
|
|
840
|
+
const missing = [];
|
|
841
|
+
for (const element of doc.querySelectorAll(STYLESHEET_LINK_SELECTOR)) {
|
|
842
|
+
if (!hasStylesheetRel(element)) continue;
|
|
843
|
+
const raw = element.getAttribute("href");
|
|
844
|
+
if (raw === null) continue;
|
|
845
|
+
const archivePath = resolveArchiveReference(raw, options.sourcePath);
|
|
846
|
+
if (archivePath === void 0) continue;
|
|
847
|
+
const url = await stylesheetUrl(archivePath, registry, options, /* @__PURE__ */ new Set(), missing);
|
|
848
|
+
if (url === void 0) {
|
|
849
|
+
missing.push(archivePath);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
element.setAttribute("href", url);
|
|
853
|
+
rewritten += 1;
|
|
854
|
+
}
|
|
855
|
+
return { rewritten, missing };
|
|
856
|
+
}
|
|
857
|
+
var STYLESHEET_LINK_SELECTOR = "link[href]";
|
|
858
|
+
function hasStylesheetRel(element) {
|
|
859
|
+
const rel = element.getAttribute("rel");
|
|
860
|
+
if (rel === null) return false;
|
|
861
|
+
return rel.toLowerCase().split(/\s+/).includes("stylesheet");
|
|
862
|
+
}
|
|
863
|
+
function decodeCss(bytes) {
|
|
864
|
+
return new TextDecoder().decode(bytes);
|
|
865
|
+
}
|
|
866
|
+
async function stylesheetUrl(archivePath, registry, options, seen, missing) {
|
|
867
|
+
if (seen.has(archivePath)) return void 0;
|
|
868
|
+
seen.add(archivePath);
|
|
869
|
+
const bytes = await registry.read(archivePath, options);
|
|
870
|
+
if (bytes === void 0) return void 0;
|
|
871
|
+
const nested = await rewriteCss(
|
|
872
|
+
decodeCss(bytes),
|
|
873
|
+
registry,
|
|
874
|
+
{ ...options, sourcePath: archivePath },
|
|
875
|
+
seen
|
|
876
|
+
);
|
|
877
|
+
missing.push(...nested.report.missing);
|
|
878
|
+
return registry.resolveCss(archivePath, nested.css);
|
|
879
|
+
}
|
|
880
|
+
function collectCssReferences(css) {
|
|
881
|
+
const references = [];
|
|
882
|
+
const claimed = [];
|
|
883
|
+
const importStringPattern = /@import\s+("[^"]*"|'[^']*')/gi;
|
|
884
|
+
css.replace(importStringPattern, (whole, inner, offset) => {
|
|
885
|
+
const quote = inner.slice(0, 1);
|
|
886
|
+
references.push({
|
|
887
|
+
kind: "import",
|
|
888
|
+
start: offset,
|
|
889
|
+
end: offset + whole.length,
|
|
890
|
+
value: inner.slice(1, -1),
|
|
891
|
+
render: (url) => `@import ${quote}${url}${quote}`
|
|
892
|
+
});
|
|
893
|
+
claimed.push({ start: offset, end: offset + whole.length });
|
|
894
|
+
return whole;
|
|
895
|
+
});
|
|
896
|
+
const importUrlPattern = /@import\s+url\(\s*("[^"]*"|'[^']*'|[^)'"\s][^)'"]*)\s*\)/gi;
|
|
897
|
+
css.replace(importUrlPattern, (whole, inner, offset) => {
|
|
898
|
+
const quote = inner.startsWith('"') || inner.startsWith("'") ? inner.slice(0, 1) : "";
|
|
899
|
+
references.push({
|
|
900
|
+
kind: "import",
|
|
901
|
+
start: offset,
|
|
902
|
+
end: offset + whole.length,
|
|
903
|
+
value: quote === "" ? inner : inner.slice(1, -1),
|
|
904
|
+
render: (url) => `@import url(${quote}${url}${quote})`
|
|
905
|
+
});
|
|
906
|
+
claimed.push({ start: offset, end: offset + whole.length });
|
|
907
|
+
return whole;
|
|
908
|
+
});
|
|
909
|
+
const urlPattern = /url\(\s*("[^"]*"|'[^']*'|[^)'"\s][^)'"]*)\s*\)/gi;
|
|
910
|
+
css.replace(urlPattern, (whole, inner, offset) => {
|
|
911
|
+
if (claimed.some((range) => offset >= range.start && offset < range.end)) return whole;
|
|
912
|
+
const quote = inner.startsWith('"') || inner.startsWith("'") ? inner.slice(0, 1) : "";
|
|
913
|
+
references.push({
|
|
914
|
+
kind: "url",
|
|
915
|
+
start: offset,
|
|
916
|
+
end: offset + whole.length,
|
|
917
|
+
value: quote === "" ? inner : inner.slice(1, -1),
|
|
918
|
+
render: (url) => `url(${quote}${url}${quote})`
|
|
919
|
+
});
|
|
920
|
+
return whole;
|
|
921
|
+
});
|
|
922
|
+
return references.sort((a, b) => a.start - b.start);
|
|
923
|
+
}
|
|
924
|
+
async function rewriteCss(css, registry, options, seen) {
|
|
925
|
+
const references = collectCssReferences(css);
|
|
926
|
+
const edits = [];
|
|
927
|
+
const missing = [];
|
|
928
|
+
for (const reference of references) {
|
|
929
|
+
const archivePath = resolveArchiveReference(reference.value, options.sourcePath);
|
|
930
|
+
if (archivePath === void 0) continue;
|
|
931
|
+
const url = reference.kind === "import" ? await stylesheetUrl(archivePath, registry, options, seen, missing) : await registry.resolve(archivePath, options);
|
|
932
|
+
if (url === void 0) {
|
|
933
|
+
missing.push(archivePath);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
edits.push({ start: reference.start, end: reference.end, text: reference.render(url) });
|
|
937
|
+
}
|
|
938
|
+
let result = css;
|
|
939
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
940
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
941
|
+
}
|
|
942
|
+
return { css: result, report: { rewritten: edits.length, missing } };
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/render/default/index.ts
|
|
946
|
+
var THEMES = {
|
|
947
|
+
light: { color: "#1a1a1a", background: "#ffffff" },
|
|
948
|
+
dark: { color: "#e6e6e6", background: "#121212" }
|
|
949
|
+
};
|
|
950
|
+
var DEFAULT_FONT_SIZE_PX = 16;
|
|
951
|
+
var DEFAULT_LINE_HEIGHT = 1.5;
|
|
952
|
+
var DEFAULT_THEME = "light";
|
|
953
|
+
function resolveAppearance(appearance = {}) {
|
|
954
|
+
const theme = appearance.theme ?? DEFAULT_THEME;
|
|
955
|
+
const colors = THEMES[theme];
|
|
956
|
+
if (colors === void 0) {
|
|
957
|
+
throw new StateError(
|
|
958
|
+
`\u672A\u77E5\u4E3B\u9898\u540D ${String(theme)} \u2014\u2014 \u5185\u7F6E\u4E3B\u9898\u53EA\u6709 ${Object.keys(THEMES).join(" / ")}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
const fontSizePx = appearance.fontSizePx ?? DEFAULT_FONT_SIZE_PX;
|
|
962
|
+
if (!Number.isFinite(fontSizePx) || fontSizePx <= 0) {
|
|
963
|
+
throw new StateError(
|
|
964
|
+
`fontSizePx \u5FC5\u987B\u662F\u6709\u9650\u6B63\u6570\uFF0C\u6536\u5230 ${String(fontSizePx)} \u2014\u2014 0 \u6216\u8D1F\u5B57\u53F7\u8BA9\u5185\u5BB9\u4E0D\u53EF\u8BFB\uFF0CNaN / Infinity \u8BA9\u6574\u4EFD\u6392\u7248\u5931\u6548`
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
const lineHeight = appearance.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
968
|
+
if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
|
|
969
|
+
throw new StateError(
|
|
970
|
+
`lineHeight \u5FC5\u987B\u662F\u6709\u9650\u6B63\u6570\uFF0C\u6536\u5230 ${String(lineHeight)} \u2014\u2014 \u540C\u4E0A\uFF1A\u884C\u8DDD\u4E3A 0 \u4F1A\u8BA9\u884C\u53E0\u5728\u4E00\u8D77\uFF0CNaN \u4F1A\u8BA9\u6574\u4EFD\u6392\u7248\u5931\u6548`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
theme,
|
|
975
|
+
color: colors.color,
|
|
976
|
+
background: colors.background,
|
|
977
|
+
fontSizePx,
|
|
978
|
+
lineHeight
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function appearanceDeclarations(appearance) {
|
|
982
|
+
return [
|
|
983
|
+
["color", appearance.color],
|
|
984
|
+
["background-color", appearance.background],
|
|
985
|
+
["font-size", `${String(appearance.fontSizePx)}px`],
|
|
986
|
+
["line-height", String(appearance.lineHeight)]
|
|
987
|
+
];
|
|
988
|
+
}
|
|
989
|
+
function pageScrollLeft(page, metrics) {
|
|
990
|
+
const offset = page * metrics.pageWidth;
|
|
991
|
+
return metrics.mirrored ? -offset : offset;
|
|
992
|
+
}
|
|
993
|
+
function applyAppearance(doc, appearance) {
|
|
994
|
+
const style = doc.documentElement.style;
|
|
995
|
+
for (const [property, value] of appearanceDeclarations(appearance)) {
|
|
996
|
+
style.setProperty(property, value);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function elementOfTarget(doc, target) {
|
|
1000
|
+
if (target.cfi !== void 0) return locateCfi(doc.documentElement, target.cfi)?.element;
|
|
1001
|
+
if (target.fragment !== void 0) return doc.getElementById(target.fragment) ?? void 0;
|
|
1002
|
+
return void 0;
|
|
1003
|
+
}
|
|
1004
|
+
function pageOfTarget(doc, target, metrics) {
|
|
1005
|
+
const element = elementOfTarget(doc, target);
|
|
1006
|
+
if (element === void 0) return 0;
|
|
1007
|
+
return pageOfElement(element, metrics);
|
|
1008
|
+
}
|
|
1009
|
+
function withLivenessGate(inner, assertAlive) {
|
|
1010
|
+
return {
|
|
1011
|
+
get size() {
|
|
1012
|
+
return inner.size;
|
|
1013
|
+
},
|
|
1014
|
+
// ⚠️ 声明为 `async`:返回 Promise 的函数**同步抛出**会让 `await` 与 `.rejects`
|
|
1015
|
+
// 两种写法行为不一致(`mount()` 里同一处纪律,契约测试钉的是后者)。
|
|
1016
|
+
async read(archivePath, options) {
|
|
1017
|
+
assertAlive();
|
|
1018
|
+
return inner.read(archivePath, options);
|
|
1019
|
+
},
|
|
1020
|
+
async resolve(archivePath, options) {
|
|
1021
|
+
assertAlive();
|
|
1022
|
+
return inner.resolve(archivePath, options);
|
|
1023
|
+
},
|
|
1024
|
+
// 同步契约 ⇒ 同步抛出。
|
|
1025
|
+
resolveCss(cssPath, cssText) {
|
|
1026
|
+
assertAlive();
|
|
1027
|
+
return inner.resolveCss(cssPath, cssText);
|
|
1028
|
+
},
|
|
1029
|
+
// 代次与释放是**记账**,不创建资源;迟到的 `endGeneration` 只会更保守,不泄漏。
|
|
1030
|
+
beginGeneration() {
|
|
1031
|
+
return inner.beginGeneration();
|
|
1032
|
+
},
|
|
1033
|
+
endGeneration(generation) {
|
|
1034
|
+
inner.endGeneration(generation);
|
|
1035
|
+
},
|
|
1036
|
+
dispose() {
|
|
1037
|
+
inner.dispose();
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function applyPagedScroll(doc, page, metrics) {
|
|
1042
|
+
doc.documentElement.scrollLeft = pageScrollLeft(page, metrics);
|
|
1043
|
+
}
|
|
1044
|
+
function assertScrolledSupported(paginator, nextMode) {
|
|
1045
|
+
if (nextMode !== "scrolled") return;
|
|
1046
|
+
if (paginator === void 0 || !paginator.verticalWriting) return;
|
|
1047
|
+
throw new StateError(
|
|
1048
|
+
"\u6EDA\u52A8\u6A21\u5F0F\u5C1A\u4E0D\u652F\u6301\u7AD6\u6392\u5185\u5BB9 \u2014\u2014 \u7AD6\u6392\u7684\u6EDA\u52A8\u8F74\u4E0E\u671D\u5411\u672A\u53D6\u8BC1\uFF0C\u5B81\u53EF\u62D2\u7EDD\u4E5F\u4E0D\u9759\u9ED8\u6EDA\u5230\u9519\u8BEF\u7684\u65B9\u5411\uFF08FI-X23\uFF09"
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
function applyScrolled(doc, target) {
|
|
1052
|
+
const root = doc.documentElement;
|
|
1053
|
+
const element = elementOfTarget(doc, target);
|
|
1054
|
+
if (element === void 0) {
|
|
1055
|
+
root.scrollTop = 0;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
root.scrollTop = root.scrollTop + element.getBoundingClientRect().top - root.getBoundingClientRect().top;
|
|
1059
|
+
}
|
|
1060
|
+
function applyScroll(doc, target, metrics) {
|
|
1061
|
+
if (metrics === void 0) {
|
|
1062
|
+
applyScrolled(doc, target);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
applyPagedScroll(doc, pageOfTarget(doc, target, metrics), metrics);
|
|
1066
|
+
}
|
|
1067
|
+
function createDefaultRenderer(options) {
|
|
1068
|
+
const { loadChapter, loadResource, language, direction } = options;
|
|
1069
|
+
let mode = options.mode ?? "paginated";
|
|
1070
|
+
if (typeof loadChapter !== "function") {
|
|
1071
|
+
throw new StateError(
|
|
1072
|
+
"createDefaultRenderer() \u9700\u8981 loadChapter \u2014\u2014 \u6E32\u67D3\u5C42\u523B\u610F\u4E0D\u5185\u5EFA\u5F52\u6863\u8BFB\u53D6\uFF08\u90A3\u662F formats/ \u7684\u804C\u8D23\uFF0CARCHITECTURE \xA71 \u4F9D\u8D56\u65B9\u5411\u5355\u5411\uFF09"
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
let appearance = resolveAppearance(options.appearance);
|
|
1076
|
+
let buffer;
|
|
1077
|
+
let view;
|
|
1078
|
+
let teardowns = 0;
|
|
1079
|
+
let liveSince;
|
|
1080
|
+
const registry = loadResource === void 0 ? void 0 : withLivenessGate(createResourceRegistry(loadResource), () => {
|
|
1081
|
+
if (liveSince === void 0 || liveSince === teardowns) return;
|
|
1082
|
+
throw new StateError(
|
|
1083
|
+
"\u672C\u6B21\u6E32\u67D3\u6240\u5C5E\u7684\u89C6\u56FE\u5DF2\u91CA\u653E\uFF0C\u4E0D\u518D\u89E3\u6790\u8D44\u6E90 \u2014\u2014 \u6CE8\u518C\u8868\u5DF2\u7ECF dispose() \u8FC7\uFF0C\u6B64\u523B\u518D\u5EFA `blob:` URL \u5C31\u843D\u8FDB\u4E00\u5F20\u65E0\u4EBA\u8D1F\u8D23\u7684\u8868\u91CC\uFF08FI-X20\uFF1A\u672A revoke \u7684 ObjectURL \u81EA\u5E26\u5F3A\u5F15\u7528\uFF0CGC \u6551\u4E0D\u56DE\u6765\uFF09"
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
1086
|
+
function teardown() {
|
|
1087
|
+
view?.paginator.dispose();
|
|
1088
|
+
view = void 0;
|
|
1089
|
+
buffer?.dispose();
|
|
1090
|
+
buffer = void 0;
|
|
1091
|
+
registry?.dispose();
|
|
1092
|
+
teardowns += 1;
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
get metrics() {
|
|
1096
|
+
return view?.metrics;
|
|
1097
|
+
},
|
|
1098
|
+
get resourceCount() {
|
|
1099
|
+
return registry?.size ?? 0;
|
|
1100
|
+
},
|
|
1101
|
+
mount(host, mountOptions = {}) {
|
|
1102
|
+
return Promise.resolve().then(() => {
|
|
1103
|
+
throwIfAborted(mountOptions.signal);
|
|
1104
|
+
teardown();
|
|
1105
|
+
buffer = createFrameBuffer(host);
|
|
1106
|
+
});
|
|
1107
|
+
},
|
|
1108
|
+
async render(target, renderOptions = {}) {
|
|
1109
|
+
const active = buffer;
|
|
1110
|
+
if (active === void 0) {
|
|
1111
|
+
throw new StateError(
|
|
1112
|
+
"render() \u4E4B\u524D\u5FC5\u987B\u5148 mount() \u2014\u2014 \u6CA1\u6709\u5BBF\u4E3B\u5C31\u6CA1\u6709\u53EF\u6302\u8F7D\u7684\u89C6\u56FE\uFF0C\u9759\u9ED8\u4EC0\u4E48\u90FD\u4E0D\u505A\u4F1A\u8BA9\u8C03\u7528\u65B9\u4EE5\u4E3A\u5DF2\u7ECF\u6E32\u67D3\u6210\u529F"
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
const { signal } = renderOptions;
|
|
1116
|
+
throwIfAborted(signal);
|
|
1117
|
+
const html = await loadChapter(target);
|
|
1118
|
+
throwIfAborted(signal);
|
|
1119
|
+
const frame = active.hidden;
|
|
1120
|
+
const loadOptions = {
|
|
1121
|
+
...renderOptions,
|
|
1122
|
+
...language === void 0 ? {} : { language },
|
|
1123
|
+
...direction === void 0 ? {} : { direction }
|
|
1124
|
+
};
|
|
1125
|
+
const doc = await active.load(frame, html, loadOptions);
|
|
1126
|
+
throwIfAborted(signal);
|
|
1127
|
+
liveSince = teardowns;
|
|
1128
|
+
const generation = registry?.beginGeneration();
|
|
1129
|
+
try {
|
|
1130
|
+
if (registry !== void 0) {
|
|
1131
|
+
const rewriteOptions = { sourcePath: target.href, ...renderOptions };
|
|
1132
|
+
await rewriteStylesheetLinks(doc, registry, rewriteOptions);
|
|
1133
|
+
await rewriteDocumentUrls(doc, registry, rewriteOptions);
|
|
1134
|
+
await active.waitForLayout(frame, renderOptions);
|
|
1135
|
+
throwIfAborted(signal);
|
|
1136
|
+
}
|
|
1137
|
+
const paginator = createPaginator(doc, { mode });
|
|
1138
|
+
applyAppearance(doc, appearance);
|
|
1139
|
+
assertScrolledSupported(paginator, mode);
|
|
1140
|
+
const metrics = mode === "paginated" ? paginator.measure(renderOptions) : void 0;
|
|
1141
|
+
const previous = view;
|
|
1142
|
+
view = { doc, paginator, target, metrics, generation };
|
|
1143
|
+
applyScroll(doc, target, metrics);
|
|
1144
|
+
active.swap();
|
|
1145
|
+
if (previous !== void 0) {
|
|
1146
|
+
previous.paginator.dispose();
|
|
1147
|
+
if (previous.generation !== void 0) registry?.endGeneration(previous.generation);
|
|
1148
|
+
}
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
if (generation !== void 0 && view?.generation !== generation) {
|
|
1151
|
+
registry?.endGeneration(generation);
|
|
1152
|
+
}
|
|
1153
|
+
throw error;
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
setAppearance(nextAppearance) {
|
|
1157
|
+
const resolved = resolveAppearance(nextAppearance);
|
|
1158
|
+
appearance = resolved;
|
|
1159
|
+
const active = view;
|
|
1160
|
+
if (active === void 0) return;
|
|
1161
|
+
applyAppearance(active.doc, resolved);
|
|
1162
|
+
if (active.metrics !== void 0) {
|
|
1163
|
+
active.metrics = active.paginator.measure();
|
|
1164
|
+
}
|
|
1165
|
+
applyScroll(active.doc, active.target, active.metrics);
|
|
1166
|
+
},
|
|
1167
|
+
setMode(nextMode) {
|
|
1168
|
+
if (nextMode === mode) return;
|
|
1169
|
+
assertScrolledSupported(view?.paginator, nextMode);
|
|
1170
|
+
mode = nextMode;
|
|
1171
|
+
const active = view;
|
|
1172
|
+
if (active === void 0) return;
|
|
1173
|
+
active.paginator.setMode(nextMode);
|
|
1174
|
+
active.metrics = nextMode === "paginated" ? active.paginator.measure() : void 0;
|
|
1175
|
+
applyScroll(active.doc, active.target, active.metrics);
|
|
1176
|
+
},
|
|
1177
|
+
unmount() {
|
|
1178
|
+
teardown();
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
export { DEFAULT_FONT_SIZE_PX, DEFAULT_LINE_HEIGHT, DEFAULT_THEME, THEMES, appearanceDeclarations, createDefaultRenderer, pageScrollLeft, resolveAppearance };
|