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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +247 -0
  3. package/README.md +211 -0
  4. package/dist/abort-BY8vBk0v.d.cts +99 -0
  5. package/dist/abort-BY8vBk0v.d.ts +99 -0
  6. package/dist/adapter-3G46J3CA.cjs +503 -0
  7. package/dist/adapter-3ONQWJVQ.js +501 -0
  8. package/dist/adapter-55QHWRSE.js +124 -0
  9. package/dist/adapter-DF34GBWJ.cjs +19 -0
  10. package/dist/adapter-EXTNILTC.cjs +126 -0
  11. package/dist/adapter-GOFC7TMC.js +284 -0
  12. package/dist/adapter-LRJTQ47I.cjs +286 -0
  13. package/dist/adapter-TWWZML4A.js +17 -0
  14. package/dist/adapter-ZM5FQTJT.js +1415 -0
  15. package/dist/adapter-ZRNSDQUV.cjs +1417 -0
  16. package/dist/chunk-2SUG7YFZ.cjs +108 -0
  17. package/dist/chunk-B2L2YVXD.js +89 -0
  18. package/dist/chunk-D4KWSEZD.js +393 -0
  19. package/dist/chunk-EZTIZO6R.cjs +430 -0
  20. package/dist/chunk-G7DLWGBW.cjs +103 -0
  21. package/dist/chunk-GTGLDLJD.cjs +479 -0
  22. package/dist/chunk-IIV6VIUJ.cjs +83 -0
  23. package/dist/chunk-JDTHZQUK.js +102 -0
  24. package/dist/chunk-JJVXT3AC.js +30 -0
  25. package/dist/chunk-LIXRYQL2.js +473 -0
  26. package/dist/chunk-NABYHI6X.cjs +400 -0
  27. package/dist/chunk-SJVOYNTF.js +425 -0
  28. package/dist/chunk-SLI2YL25.cjs +252 -0
  29. package/dist/chunk-U4264IQH.js +78 -0
  30. package/dist/chunk-UY2YRCFC.js +250 -0
  31. package/dist/chunk-WCZTTQ7Z.cjs +32 -0
  32. package/dist/core/index.cjs +162 -0
  33. package/dist/core/index.d.cts +321 -0
  34. package/dist/core/index.d.ts +321 -0
  35. package/dist/core/index.js +49 -0
  36. package/dist/default-DRLIJX73.js +1183 -0
  37. package/dist/default-UK52WOO5.cjs +1192 -0
  38. package/dist/formats/epub/index.cjs +29 -0
  39. package/dist/formats/epub/index.d.cts +286 -0
  40. package/dist/formats/epub/index.d.ts +286 -0
  41. package/dist/formats/epub/index.js +11 -0
  42. package/dist/index.cjs +655 -0
  43. package/dist/index.d.cts +335 -0
  44. package/dist/index.d.ts +335 -0
  45. package/dist/index.js +600 -0
  46. package/dist/render/index.cjs +2 -0
  47. package/dist/render/index.d.cts +116 -0
  48. package/dist/render/index.d.ts +116 -0
  49. package/dist/render/index.js +1 -0
  50. package/dist/types-B76GOMxj.d.ts +129 -0
  51. package/dist/types-B7mslPBY.d.cts +166 -0
  52. package/dist/types-B7mslPBY.d.ts +166 -0
  53. package/dist/types-BH88rUYt.d.cts +129 -0
  54. package/dist/types-C-5eHSRH.d.ts +379 -0
  55. package/dist/types-CPUqTEPW.d.cts +379 -0
  56. package/dist/types-DQYmArgv.d.cts +17 -0
  57. package/dist/types-DQYmArgv.d.ts +17 -0
  58. package/package.json +115 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * frond-js 统一错误体系。
3
+ *
4
+ * 设计约束(ARCHITECTURE §6 / REQ-CORE-003):
5
+ * - 禁止抛出裸 `Error` 或字符串
6
+ * - 每个错误携带稳定的 `code`,下游可据此分支,不依赖 message 文案
7
+ * - `cause` 保留原始错误,不丢失调用栈
8
+ */
9
+ /** 稳定错误码。属于公共契约,变更需走 SemVer。 */
10
+ type FrondErrorCode = 'ERR_PARSE' | 'ERR_FORMAT' | 'ERR_NETWORK' | 'ERR_SECURITY' | 'ERR_ABORT' | 'ERR_STATE';
11
+ interface FrondErrorOptions {
12
+ /** 原始错误,用于保留调用栈。 */
13
+ readonly cause?: unknown;
14
+ }
15
+ /**
16
+ * 所有 frond-js 错误的基类。
17
+ *
18
+ * 不要直接实例化本类;请使用具体的子类。
19
+ */
20
+ declare class FrondError extends Error {
21
+ /** 稳定错误码,用于程序化分支。 */
22
+ readonly code: FrondErrorCode;
23
+ constructor(code: FrondErrorCode, message: string, options?: FrondErrorOptions);
24
+ }
25
+ /** 解析失败:ZIP 损坏、XML 非法、结构缺失。 */
26
+ declare class ParseError extends FrondError {
27
+ constructor(message: string, options?: FrondErrorOptions);
28
+ }
29
+ /** 格式不支持或不符合规范。 */
30
+ declare class FormatError extends FrondError {
31
+ constructor(message: string, options?: FrondErrorOptions);
32
+ }
33
+ /** 资源加载失败。 */
34
+ declare class NetworkError extends FrondError {
35
+ constructor(message: string, options?: FrondErrorOptions);
36
+ }
37
+ /** 触发安全策略(例如 CSP 拦截、脚本执行被拒)。 */
38
+ declare class SecurityError extends FrondError {
39
+ constructor(message: string, options?: FrondErrorOptions);
40
+ }
41
+ /**
42
+ * 操作被取消。
43
+ *
44
+ * `name` 恒为 `'AbortError'`,与 `DOMException` 的语义对齐(H7)。
45
+ */
46
+ declare class AbortError extends FrondError {
47
+ constructor(message?: string, options?: FrondErrorOptions);
48
+ }
49
+ /** 非法状态迁移(H5:禁止隐式标志位)。 */
50
+ declare class StateError extends FrondError {
51
+ constructor(message: string, options?: FrondErrorOptions);
52
+ }
53
+ /** 判断任意值是否为 frond-js 错误。 */
54
+ declare function isFrondError(value: unknown): value is FrondError;
55
+ /**
56
+ * 判断任意值是否为取消错误。
57
+ *
58
+ * 同时识别 frond-js 的 {@link AbortError} 与宿主环境的 `DOMException`
59
+ * (`name === 'AbortError'`),因为 `AbortSignal.reason` 可能来自外部。
60
+ */
61
+ declare function isAbortError(value: unknown): boolean;
62
+
63
+ /**
64
+ * 取消与资源清理工具(H6 / H7)。
65
+ *
66
+ * 约定:
67
+ * - 所有异步 API 接受 `AbortSignal`
68
+ * - abort 后抛出 {@link AbortError},**不得吞掉**
69
+ * - abort 必须释放资源(`revokeObjectURL` / `worker.terminate()` / iframe 清理 / 事件解绑)
70
+ */
71
+ /** 创建一个携带原始原因的 {@link AbortError}。 */
72
+ declare function createAbortError(reason?: unknown): AbortError;
73
+ /**
74
+ * 若 signal 已 abort,立即抛出 {@link AbortError}。
75
+ *
76
+ * 用作异步流程中的检查点,避免在已取消的操作上继续分配资源。
77
+ */
78
+ declare function throwIfAborted(signal?: AbortSignal): void;
79
+ /**
80
+ * 合并多个 signal:任一 abort 即整体 abort。
81
+ *
82
+ * 全部为 `undefined` 时返回 `undefined`,便于直接透传给下游。
83
+ */
84
+ declare function linkSignals(...signals: (AbortSignal | undefined)[]): AbortSignal | undefined;
85
+ /**
86
+ * 注册 abort 回调,返回解绑函数。
87
+ *
88
+ * 若 signal 已 abort,回调会被**同步立即执行**。
89
+ * 解绑函数幂等,可在资源清理路径中无条件调用。
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const detach = onAbort(signal, () => URL.revokeObjectURL(url));
94
+ * // ... 正常结束时同样要调用 detach(),避免回调悬挂
95
+ * ```
96
+ */
97
+ declare function onAbort(signal: AbortSignal | undefined, handler: () => void): () => void;
98
+
99
+ export { AbortError as A, FormatError as F, NetworkError as N, ParseError as P, SecurityError as S, FrondError as a, type FrondErrorCode as b, type FrondErrorOptions as c, StateError as d, createAbortError as e, isFrondError as f, isAbortError as i, linkSignals as l, onAbort as o, throwIfAborted as t };
@@ -0,0 +1,503 @@
1
+ 'use strict';
2
+
3
+ var chunkNABYHI6X_cjs = require('./chunk-NABYHI6X.cjs');
4
+ var chunkSLI2YL25_cjs = require('./chunk-SLI2YL25.cjs');
5
+ require('./chunk-IIV6VIUJ.cjs');
6
+ var chunkG7DLWGBW_cjs = require('./chunk-G7DLWGBW.cjs');
7
+
8
+ // src/formats/fb2/label.ts
9
+ function sourceSuffix(label) {
10
+ return label === void 0 ? "" : `\uFF08\u6E90 ${label}\uFF09`;
11
+ }
12
+
13
+ // src/formats/fb2/binaries.ts
14
+ var UNKNOWN_MEDIA_TYPE = "application/octet-stream";
15
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
16
+ var ACCUMULATOR_MASK = 16383;
17
+ function decodeFb2Binaries(root, source) {
18
+ const binaries = [];
19
+ const seen = /* @__PURE__ */ new Set();
20
+ for (const element of chunkNABYHI6X_cjs.findAll(root, "binary")) {
21
+ const name = chunkNABYHI6X_cjs.getAttribute(element, "id");
22
+ if (name === void 0 || name === "") {
23
+ throw new chunkG7DLWGBW_cjs.FormatError(`<binary> \u7F3A\u5C11 id\uFF08\u6CA1\u6709\u540D\u5B57\u5C31\u6CE8\u518C\u4E0D\u4E86\u6761\u76EE\uFF09${sourceSuffix(source)}`);
24
+ }
25
+ if (seen.has(name)) {
26
+ throw new chunkG7DLWGBW_cjs.FormatError(`<binary> \u7684 id "${name}" \u91CD\u590D${sourceSuffix(source)}`);
27
+ }
28
+ seen.add(name);
29
+ binaries.push({
30
+ name,
31
+ mediaType: chunkNABYHI6X_cjs.getAttribute(element, "content-type") ?? UNKNOWN_MEDIA_TYPE,
32
+ bytes: decodeBase64(name, chunkNABYHI6X_cjs.getText(element), source)
33
+ });
34
+ }
35
+ return binaries;
36
+ }
37
+ function decodeBase64(name, text, source) {
38
+ const cleaned = text.replace(/\s+/g, "");
39
+ const body = cleaned.includes("=") ? cleaned.slice(0, cleaned.indexOf("=")) : cleaned;
40
+ if (body.length % 4 === 1) {
41
+ throw new chunkG7DLWGBW_cjs.FormatError(
42
+ `<binary id="${name}"> \u7684 base64 \u957F\u5EA6\u4E0D\u5408\u6CD5\uFF08\u6709\u6548\u5B57\u7B26 ${String(body.length)} \u4E2A\uFF09${sourceSuffix(source)}`
43
+ );
44
+ }
45
+ const bytes = new Uint8Array(body.length * 6 >> 3);
46
+ let accumulator = 0;
47
+ let bits = 0;
48
+ let index = 0;
49
+ for (const char of body) {
50
+ const value = BASE64_ALPHABET.indexOf(char);
51
+ if (value < 0) {
52
+ throw new chunkG7DLWGBW_cjs.FormatError(
53
+ `<binary id="${name}"> \u7684 base64 \u542B\u975E\u6CD5\u5B57\u7B26 "${char}"${sourceSuffix(source)}`
54
+ );
55
+ }
56
+ accumulator = (accumulator << 6 | value) & ACCUMULATOR_MASK;
57
+ bits += 6;
58
+ if (bits >= 8) {
59
+ bits -= 8;
60
+ bytes[index] = accumulator >> bits & 255;
61
+ index += 1;
62
+ }
63
+ }
64
+ return bytes;
65
+ }
66
+
67
+ // src/formats/fb2/elements.ts
68
+ function directChildren(element, localName) {
69
+ const matched = [];
70
+ for (const child of element.children) {
71
+ if (child.type !== "element") continue;
72
+ if (child.localName === localName) matched.push(child);
73
+ }
74
+ return matched;
75
+ }
76
+ function firstDirectChild(element, localName) {
77
+ for (const child of element.children) {
78
+ if (child.type === "element" && child.localName === localName) return child;
79
+ }
80
+ return void 0;
81
+ }
82
+ function hrefOf(element) {
83
+ const named = element.attributes.get("xlink:href") ?? element.attributes.get("l:href");
84
+ if (named !== void 0) return named;
85
+ for (const [name, value] of element.attributes) {
86
+ if (name.endsWith(":href")) return value;
87
+ }
88
+ return element.attributes.get("href");
89
+ }
90
+ function trimmedText(element) {
91
+ return chunkNABYHI6X_cjs.getText(element).trim();
92
+ }
93
+
94
+ // src/formats/fb2/xhtml.ts
95
+ var MAPPING = /* @__PURE__ */ new Map([
96
+ ["p", { tag: "p" }],
97
+ ["title", { tag: "header" }],
98
+ ["subtitle", { tag: "h2" }],
99
+ // `empty-line` 与 `v` 都表达「这里换行」:无包裹元素、无 class,只补一个 `<br/>`。
100
+ ["empty-line", { trailing: "<br/>" }],
101
+ ["emphasis", { tag: "em" }],
102
+ ["strong", { tag: "strong" }],
103
+ ["strikethrough", { tag: "s" }],
104
+ ["sub", { tag: "sub" }],
105
+ ["sup", { tag: "sup" }],
106
+ ["code", { tag: "code" }],
107
+ ["style", { tag: "span" }],
108
+ ["a", { tag: "a" }],
109
+ ["image", { tag: "img", selfClosing: true }],
110
+ ["section", { tag: "section" }],
111
+ ["epigraph", { tag: "blockquote" }],
112
+ ["cite", { tag: "blockquote" }],
113
+ ["annotation", { tag: "aside" }],
114
+ ["poem", { tag: "blockquote" }],
115
+ ["stanza", { tag: "div" }],
116
+ ["v", { trailing: "<br/>" }],
117
+ ["text-author", { tag: "p" }],
118
+ ["date", { tag: "p" }],
119
+ ["table", { tag: "table" }],
120
+ ["tr", { tag: "tr" }],
121
+ ["th", { tag: "th" }],
122
+ ["td", { tag: "td" }]
123
+ ]);
124
+ var CELL_ATTRIBUTES = ["colspan", "rowspan", "align", "valign"];
125
+ function idFor(element, context) {
126
+ return chunkNABYHI6X_cjs.getAttribute(element, "id") ?? context.syntheticIds.get(element);
127
+ }
128
+ function convertFb2Element(element, context) {
129
+ return convertNode(element, context, void 0);
130
+ }
131
+ function writtenIds(element, context) {
132
+ const ids = [];
133
+ collectWrittenIds(element, context, ids);
134
+ return ids;
135
+ }
136
+ function collectWrittenIds(element, context, ids) {
137
+ const mapping = MAPPING.get(element.localName);
138
+ if (mapping !== void 0 && mapping.tag !== void 0) {
139
+ const id = idFor(element, context);
140
+ if (id !== void 0) ids.push(id);
141
+ }
142
+ for (const child of element.children) {
143
+ if (child.type === "element") collectWrittenIds(child, context, ids);
144
+ }
145
+ }
146
+ function convertNode(element, context, parentLocalName) {
147
+ const mapping = MAPPING.get(element.localName);
148
+ if (mapping === void 0) return convertChildren(element, context);
149
+ const children = convertChildren(element, context);
150
+ const trailing = mapping.trailing ?? "";
151
+ if (mapping.tag === void 0) return children + trailing;
152
+ const tag = isTitleParagraph(element, parentLocalName) ? "h1" : mapping.tag;
153
+ const attributes = attributeString(element, context);
154
+ return mapping.selfClosing === true ? `<${tag} ${attributes}/>` : `<${tag} ${attributes}>${children}</${tag}>${trailing}`;
155
+ }
156
+ function isTitleParagraph(element, parentLocalName) {
157
+ return element.localName === "p" && parentLocalName === "title";
158
+ }
159
+ function convertChildren(element, context) {
160
+ let output = "";
161
+ for (const child of element.children) {
162
+ output += child.type === "text" ? escapeText(child.value) : convertNode(child, context, element.localName);
163
+ }
164
+ return output;
165
+ }
166
+ function attributeString(element, context) {
167
+ const parts = [`class="${element.localName}"`];
168
+ const id = idFor(element, context);
169
+ if (id !== void 0) parts.push(`id="${escapeAttribute(id)}"`);
170
+ for (const attribute of ownAttributes(element, context)) parts.push(attribute);
171
+ return parts.join(" ");
172
+ }
173
+ function ownAttributes(element, context) {
174
+ switch (element.localName) {
175
+ case "a":
176
+ return linkAttributes(element);
177
+ case "image":
178
+ return imageAttributes(element, context);
179
+ case "td":
180
+ case "th":
181
+ return cellAttributes(element);
182
+ default:
183
+ return [];
184
+ }
185
+ }
186
+ function linkAttributes(element) {
187
+ const attributes = [`href="${escapeAttribute(hrefOf(element) ?? "")}"`];
188
+ if (chunkNABYHI6X_cjs.getAttribute(element, "type") === "note") attributes.push('epub:type="noteref"');
189
+ return attributes;
190
+ }
191
+ function imageAttributes(element, context) {
192
+ const attributes = [`src="${escapeAttribute(imageSource(element, context))}"`];
193
+ const alt = chunkNABYHI6X_cjs.getAttribute(element, "alt");
194
+ if (alt !== void 0) attributes.push(`alt="${escapeAttribute(alt)}"`);
195
+ const title = chunkNABYHI6X_cjs.getAttribute(element, "title");
196
+ if (title !== void 0) attributes.push(`title="${escapeAttribute(title)}"`);
197
+ return attributes;
198
+ }
199
+ function imageSource(element, context) {
200
+ const href = hrefOf(element);
201
+ if (href === void 0) return "";
202
+ if (!href.startsWith("#")) return href;
203
+ const name = href.slice(1);
204
+ return context.binaries.has(name) ? `/${name}` : href;
205
+ }
206
+ function cellAttributes(element) {
207
+ const attributes = [];
208
+ for (const name of CELL_ATTRIBUTES) {
209
+ const value = chunkNABYHI6X_cjs.getAttribute(element, name);
210
+ if (value !== void 0) attributes.push(`${name}="${escapeAttribute(value)}"`);
211
+ }
212
+ return attributes;
213
+ }
214
+ function escapeText(value) {
215
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
216
+ }
217
+ function escapeAttribute(value) {
218
+ return escapeText(value).replace(/"/g, "&quot;");
219
+ }
220
+
221
+ // src/formats/fb2/chapters.ts
222
+ var FB2_CHAPTER_MEDIA_TYPE = "application/xhtml+xml";
223
+ var DOCUMENT_HEAD = '<?xml version="1.0" encoding="utf-8"?><html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"><head></head><body>';
224
+ var DOCUMENT_TAIL = "</body></html>";
225
+ var NAME_WIDTH = 4;
226
+ function buildFb2Chapters(bodies, context, source) {
227
+ const chapters = [];
228
+ for (const [bodyIndex, body] of bodies.entries()) {
229
+ const linear = bodyIndex === 0;
230
+ for (const block of body.children) {
231
+ if (block.type !== "element") continue;
232
+ const href = chapterName(chapters.length);
233
+ chapters.push({
234
+ href,
235
+ item: { href, mediaType: FB2_CHAPTER_MEDIA_TYPE, linear },
236
+ block,
237
+ document: `${DOCUMENT_HEAD}${convertFb2Element(block, context)}${DOCUMENT_TAIL}`,
238
+ ids: writtenIds(block, context)
239
+ });
240
+ }
241
+ }
242
+ if (chapters.length === 0) {
243
+ throw new chunkG7DLWGBW_cjs.FormatError(`\u6CA1\u6709\u53EF\u8BFB\u7684\u6B63\u6587\uFF08body \u7684\u9876\u5C42\u5757\u4E00\u5757\u90FD\u6CA1\u6709\uFF09${sourceSuffix(source)}`);
244
+ }
245
+ return chapters;
246
+ }
247
+ function chapterName(index) {
248
+ return `text/${String(index).padStart(NAME_WIDTH, "0")}.xhtml`;
249
+ }
250
+
251
+ // src/formats/fb2/description.ts
252
+ function parseFb2Description(root, source) {
253
+ const description = firstDirectChild(root, "description") ?? root;
254
+ const titleInfo = firstDirectChild(description, "title-info") ?? description;
255
+ const documentInfo = firstDirectChild(description, "document-info") ?? description;
256
+ const publishInfo = firstDirectChild(description, "publish-info") ?? description;
257
+ const title = requiredText(titleInfo, "book-title", "\u7F3A\u5C11\u4E66\u540D\uFF08title-info/book-title\uFF09", source);
258
+ const language = requiredText(titleInfo, "lang", "\u7F3A\u5C11\u8BED\u8A00\uFF08title-info/lang\uFF09", source);
259
+ const identifier = identifierOf(documentInfo, publishInfo, source);
260
+ const creators = creatorsOf(titleInfo);
261
+ const modified = modifiedOf(documentInfo);
262
+ return modified === void 0 ? { title, creators, language, identifier } : { title, creators, language, identifier, modified };
263
+ }
264
+ function requiredText(parent, localName, describe, source) {
265
+ const text = textOf(parent, localName);
266
+ if (text === "") {
267
+ throw new chunkG7DLWGBW_cjs.FormatError(`${describe}${sourceSuffix(source)}`);
268
+ }
269
+ return text;
270
+ }
271
+ function identifierOf(documentInfo, publishInfo, source) {
272
+ const fromId = textOf(documentInfo, "id");
273
+ if (fromId !== "") return fromId;
274
+ const fromIsbn = textOf(publishInfo, "isbn");
275
+ if (fromIsbn !== "") return fromIsbn;
276
+ throw new chunkG7DLWGBW_cjs.FormatError(
277
+ `\u7F3A\u5C11\u6807\u8BC6\u7B26\uFF08document-info/id \u4E0E publish-info/isbn \u90FD\u4E3A\u7A7A\uFF09${sourceSuffix(source)}`
278
+ );
279
+ }
280
+ function creatorsOf(titleInfo) {
281
+ const creators = [];
282
+ for (const author of directChildren(titleInfo, "author")) {
283
+ const name = ["first-name", "middle-name", "last-name"].map((part) => textOf(author, part)).filter((part) => part !== "").join(" ");
284
+ const creator = name === "" ? textOf(author, "nickname") : name;
285
+ if (creator === "") continue;
286
+ creators.push(creator);
287
+ }
288
+ return creators;
289
+ }
290
+ function modifiedOf(documentInfo) {
291
+ const element = firstDirectChild(documentInfo, "date");
292
+ if (element === void 0) return void 0;
293
+ const text = chunkNABYHI6X_cjs.getAttribute(element, "value") ?? trimmedText(element);
294
+ return text === "" ? void 0 : text;
295
+ }
296
+ function textOf(parent, localName) {
297
+ const element = firstDirectChild(parent, localName);
298
+ return element === void 0 ? "" : trimmedText(element);
299
+ }
300
+
301
+ // src/formats/fb2/encoding.ts
302
+ var DECLARATION_SCAN_BYTES = 256;
303
+ var XML_DECLARATION = /^<\?xml\b[^>]*\bencoding\s*=\s*(?:"([^"]*)"|'([^']*)')/;
304
+ var DEFAULT_ENCODING = "utf-8";
305
+ var UTF16_LE_BOM = 65534;
306
+ var UTF16_BE_BOM = 65279;
307
+ function decodeFb2Source(bytes, source) {
308
+ assertNotUtf16(bytes, source);
309
+ const label = declaredEncoding(bytes) ?? DEFAULT_ENCODING;
310
+ try {
311
+ return new TextDecoder(label).decode(bytes);
312
+ } catch (cause) {
313
+ throw new chunkG7DLWGBW_cjs.FormatError(`\u65E0\u6CD5\u8BC6\u522B\u7684\u7F16\u7801\u58F0\u660E "${label}"${sourceSuffix(source)}`, { cause });
314
+ }
315
+ }
316
+ function assertNotUtf16(bytes, source) {
317
+ const bom = (bytes[0] ?? 0) << 8 | (bytes[1] ?? 0);
318
+ if (bom !== UTF16_LE_BOM && bom !== UTF16_BE_BOM) return;
319
+ throw new chunkG7DLWGBW_cjs.FormatError(
320
+ `\u6E90\u4EE5 UTF-16 BOM \u5F00\u5934\uFF0C\u672C\u5305\u4E0D\u505A UTF-16 \u89E3\u7801\uFF08FB2 \u7684\u73B0\u5B9E\u5F62\u6001\u662F\u5355 / \u53CC\u5B57\u8282\u9057\u7559\u7F16\u7801\uFF09${sourceSuffix(source)}`
321
+ );
322
+ }
323
+ function declaredEncoding(bytes) {
324
+ const head = asciiHead(bytes);
325
+ const matched = XML_DECLARATION.exec(head);
326
+ if (matched === null) return void 0;
327
+ const value = matched[1] ?? matched[2];
328
+ return value === void 0 || value === "" ? void 0 : value;
329
+ }
330
+ function asciiHead(bytes) {
331
+ const limit = Math.min(bytes.length, DECLARATION_SCAN_BYTES);
332
+ let head = "";
333
+ for (const byte of bytes.subarray(0, limit)) {
334
+ head += String.fromCharCode(byte);
335
+ }
336
+ return head;
337
+ }
338
+
339
+ // src/formats/fb2/toc.ts
340
+ var SYNTHETIC_ID_PREFIX = "fb2-toc-";
341
+ function planFb2Toc(bodies) {
342
+ const state = { taken: /* @__PURE__ */ new Set(), syntheticIds: /* @__PURE__ */ new Map(), counter: 0 };
343
+ for (const body of bodies) collectIds(body, state.taken);
344
+ const entries = [];
345
+ for (const body of bodies) {
346
+ for (const section of directChildren(body, "section")) {
347
+ entries.push(...entriesFor(section, section, state));
348
+ }
349
+ }
350
+ return { entries, syntheticIds: state.syntheticIds };
351
+ }
352
+ function entriesFor(section, block, state) {
353
+ const title = firstDirectChild(section, "title");
354
+ if (title === void 0) {
355
+ return directChildren(section, "section").flatMap((child) => entriesFor(child, block, state));
356
+ }
357
+ const fragment = section === block ? void 0 : idForSection(section, state);
358
+ const children = directChildren(section, "section").flatMap(
359
+ (child) => entriesFor(child, block, state)
360
+ );
361
+ return [
362
+ {
363
+ label: trimmedText(title),
364
+ block,
365
+ ...fragment === void 0 ? {} : { fragment },
366
+ children
367
+ }
368
+ ];
369
+ }
370
+ function idForSection(section, state) {
371
+ const own = chunkNABYHI6X_cjs.getAttribute(section, "id");
372
+ if (own !== void 0) return own;
373
+ let candidate;
374
+ do {
375
+ state.counter += 1;
376
+ candidate = `${SYNTHETIC_ID_PREFIX}${String(state.counter)}`;
377
+ } while (state.taken.has(candidate));
378
+ state.taken.add(candidate);
379
+ state.syntheticIds.set(section, candidate);
380
+ return candidate;
381
+ }
382
+ function collectIds(root, ids) {
383
+ const own = chunkNABYHI6X_cjs.getAttribute(root, "id");
384
+ if (own !== void 0) ids.add(own);
385
+ for (const child of root.children) {
386
+ if (child.type === "element") collectIds(child, ids);
387
+ }
388
+ }
389
+
390
+ // src/formats/fb2/open.ts
391
+ var ZIP_MAGIC = [80, 75, 3, 4];
392
+ var FB2_EXTENSION = ".fb2";
393
+ var OUTPUT_ENCODING = "utf-8";
394
+ async function openFb2(bytes, options) {
395
+ const source = options?.source;
396
+ chunkG7DLWGBW_cjs.throwIfAborted(options?.signal);
397
+ const document = await readDocument(bytes, source);
398
+ return assemble(chunkNABYHI6X_cjs.parseXml(document, source === void 0 ? void 0 : { source }), source);
399
+ }
400
+ async function readDocument(bytes, source) {
401
+ if (!isZip(bytes)) return decodeFb2Source(bytes, source);
402
+ const archive = chunkSLI2YL25_cjs.parseZip(bytes, source === void 0 ? void 0 : { source });
403
+ const names = archive.entries.filter((entry) => entry.name.toLowerCase().endsWith(FB2_EXTENSION)).map((entry) => entry.name);
404
+ const name = names[0];
405
+ if (name === void 0 || names.length !== 1) {
406
+ throw new chunkG7DLWGBW_cjs.FormatError(
407
+ `\`.fb2.zip\` \u91CC\u5E94\u5F53\u6070\u597D\u6709\u4E00\u6761 \`${FB2_EXTENSION}\`\uFF0C\u5B9E\u9645 ${String(names.length)} \u6761${sourceSuffix(source)}`
408
+ );
409
+ }
410
+ return decodeFb2Source(await archive.read(name), source);
411
+ }
412
+ function isZip(bytes) {
413
+ return ZIP_MAGIC.every((byte, index) => bytes[index] === byte);
414
+ }
415
+ function assemble(root, source) {
416
+ const metadata = parseFb2Description(root, source);
417
+ const binaries = decodeFb2Binaries(root, source);
418
+ const bodies = directChildren(root, "body");
419
+ const plan = planFb2Toc(bodies);
420
+ const context = {
421
+ syntheticIds: plan.syntheticIds,
422
+ binaries: new Map(binaries.map((binary) => [binary.name, binary.mediaType]))
423
+ };
424
+ const chapters = buildFb2Chapters(bodies, context, source);
425
+ return {
426
+ content: createContent(buildEntries(chapters, binaries, source)),
427
+ book: buildBook(metadata, binaries, chapters),
428
+ toc: toTocItems(plan.entries, blockHrefs(chapters), source)
429
+ };
430
+ }
431
+ function buildEntries(chapters, binaries, source) {
432
+ const encoder = new TextEncoder();
433
+ const entries = /* @__PURE__ */ new Map();
434
+ for (const chapter of chapters) {
435
+ entries.set(chapter.href, encoder.encode(chapter.document));
436
+ }
437
+ for (const binary of binaries) {
438
+ if (entries.has(binary.name)) {
439
+ throw new chunkG7DLWGBW_cjs.FormatError(
440
+ `<binary> \u7684 id "${binary.name}" \u4E0E\u7AE0\u8282\u6587\u6863\u91CD\u540D${sourceSuffix(source)}`
441
+ );
442
+ }
443
+ entries.set(binary.name, binary.bytes);
444
+ }
445
+ return entries;
446
+ }
447
+ function blockHrefs(chapters) {
448
+ return new Map(chapters.map((chapter) => [chapter.block, chapter.href]));
449
+ }
450
+ function toTocItems(entries, hrefs, source) {
451
+ return entries.map((entry) => {
452
+ const href = hrefs.get(entry.block);
453
+ if (href === void 0) {
454
+ throw new chunkG7DLWGBW_cjs.FormatError(`\u76EE\u5F55\u6761\u76EE\u7684\u76EE\u6807\u5757\u6CA1\u6709\u5BF9\u5E94\u7684\u7AE0\u8282${sourceSuffix(source)}`);
455
+ }
456
+ return {
457
+ label: entry.label,
458
+ href,
459
+ ...entry.fragment === void 0 ? {} : { fragment: entry.fragment },
460
+ children: toTocItems(entry.children, hrefs, source)
461
+ };
462
+ });
463
+ }
464
+ function buildBook(metadata, binaries, chapters) {
465
+ const resources = binaries.map((binary) => ({
466
+ href: binary.name,
467
+ mediaType: binary.mediaType
468
+ }));
469
+ return {
470
+ format: "fb2",
471
+ metadata,
472
+ resources,
473
+ chapters: chapters.map((chapter) => chapter.item),
474
+ manifest: [],
475
+ spine: [],
476
+ manifestIndex: /* @__PURE__ */ new Map()
477
+ };
478
+ }
479
+ function createContent(entries) {
480
+ const read = (name, readOptions) => (
481
+ // 同步体推进微任务队列 ⇒ 抛出的错误自然成为拒绝(不依赖调用方套 `try`)。
482
+ Promise.resolve().then(() => {
483
+ chunkG7DLWGBW_cjs.throwIfAborted(readOptions?.signal);
484
+ const bytes = entries.get(name);
485
+ if (bytes === void 0) {
486
+ throw new chunkG7DLWGBW_cjs.FormatError(`FB2 \u5185\u5BB9\u6E90\u91CC\u6CA1\u6709 "${name}"`);
487
+ }
488
+ return bytes;
489
+ })
490
+ );
491
+ const readText = async (name, readOptions) => new TextDecoder(OUTPUT_ENCODING).decode(await read(name, readOptions));
492
+ return { read, readText };
493
+ }
494
+
495
+ // src/formats/fb2/adapter.ts
496
+ var fb2Adapter = {
497
+ format: "fb2",
498
+ async open(bytes, options) {
499
+ return openFb2(bytes, options);
500
+ }
501
+ };
502
+
503
+ exports.fb2Adapter = fb2Adapter;