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,379 @@
1
+ import { O as OperationOptions } from './types-DQYmArgv.cjs';
2
+
3
+ /**
4
+ * 可注入的字节源与资源加载抽象(REQ-CORE-005)。
5
+ *
6
+ * 通过实现这些接口,宿主可以接入任意存储后端(内存、OPFS、远端分片、加密容器),
7
+ * core 无需感知网络与文件系统差异 —— 测试里因此可以注入纯内存实现,
8
+ * **不触碰网络与文件系统**。
9
+ *
10
+ * 本模块属于 `frond-js/core`,**不得依赖 DOM,也不得依赖 render / reader**
11
+ * (ARCHITECTURE §1 依赖方向单向,H4)。
12
+ */
13
+
14
+ /**
15
+ * 可注入的字节源抽象。
16
+ *
17
+ * 实现者必须满足以下契约(`createMemoryBookSource` 是内置参考实现):
18
+ *
19
+ * 1. **区间语义**:`read({ start, end })` 返回覆盖**闭开区间** `[start, end)`
20
+ * 的字节,长度恒为 `end - start`;`[0, 0)` 是合法输入,返回零长度数组。
21
+ * 2. **取消(H6 / H7)**:`options.signal` 已 abort 时必须抛 `AbortError`,
22
+ * **且不得返回任何字节**。
23
+ * 3. **关闭后不可读**:`close()` 之后调用 `read` 必须抛 `StateError`
24
+ * —— 「已释放」与「读到空数据」是两回事,静默返回空会让缺陷延后暴露。
25
+ * 4. **区间越界即报错**:越界、起止倒置、非整数一律抛 `FormatError`,
26
+ * **不截断、不补齐**(对照 FI-005 的「不静默降级」)。
27
+ */
28
+ interface BookSource {
29
+ /** 总字节数;未知时为 `null`。 */
30
+ readonly size: number | null;
31
+ /**
32
+ * 按需读取指定字节区间。
33
+ *
34
+ * @param range 闭开区间 `[start, end)`
35
+ * @param options 取消信号
36
+ * @throws {AbortError} 操作被取消
37
+ * @throws {StateError} 源已 `close()`
38
+ * @throws {FormatError} 区间非法(越界 / 起止倒置 / 非整数)
39
+ */
40
+ read(range: {
41
+ readonly start: number;
42
+ readonly end: number;
43
+ }, options?: OperationOptions): Promise<Uint8Array>;
44
+ /** 释放底层资源。必须幂等(重复调用不抛错)。 */
45
+ close(): void;
46
+ }
47
+ /**
48
+ * 书内资源的加载抽象(图片、样式、字体等)。
49
+ *
50
+ * 与 {@link BookSource} 的分工:`BookSource` 面向**整本书的字节流**(可按偏移随机读),
51
+ * `ResourceIO` 面向**书内已定位的资源**(按 `href` 取整块)。
52
+ *
53
+ * ## 契约(自 T2.8 起生效)
54
+ *
55
+ * 1. **`href` 是归档路径**,不是相对路径、也不是 URL —— 相对引用的解析由渲染层
56
+ * 按「引用所在文档」完成(`core/archive-path`),实现方拿到的已经是可直接查表的形式。
57
+ * 2. **取不到时抛 `FormatError`**(不是返回空、也不是 `undefined`)—— 「归档里没有
58
+ * 这个条目」与「读到了零字节」是两回事,静默返回空会把缺陷延后到「图片不显示」才暴露。
59
+ * ⚠️ 与 `ResourceLoader` 的分工正在此处:后者用 `undefined` 表示「没有」,
60
+ * 适配层负责把 `FormatError` 翻译成 `undefined`(见 `src/reader/reader.ts`)。
61
+ * 3. **只对内置默认渲染器生效**:调用方自备 `renderer` 时,`Reader` **不**透传 `io`
62
+ * —— `Renderer` 契约里没有承载资源来源的位置,「谁渲染、谁负责取资源」。
63
+ * 4. **只管书内资源,不管章节**:章节是 XML 文本,走归档的 `readText`
64
+ * (自带 UTF-8 解码与 BOM 处理),不经过这里。
65
+ *
66
+ * 存在的意义是让宿主接远端分片或加密容器(见本模块头部)。
67
+ */
68
+ interface ResourceIO {
69
+ /**
70
+ * 读取指定资源对应的字节。
71
+ *
72
+ * @param href **归档路径**(如 `OPS/images/cover.png`)
73
+ * @param options 取消信号
74
+ * @throws {AbortError} 操作被取消(H6 / H7)
75
+ * @throws {FormatError} 归档里没有这个路径
76
+ */
77
+ load(href: string, options?: OperationOptions): Promise<Uint8Array>;
78
+ }
79
+ /** {@link createMemoryBookSource} 的选项。 */
80
+ interface MemoryBookSourceOptions {
81
+ /**
82
+ * 源标识,**仅用于错误 message**,便于定位是哪个源出的问题
83
+ * (与 `ParseZipOptions.source` 同口径)。缺省为 `'<anonymous>'`。
84
+ */
85
+ readonly source?: string;
86
+ }
87
+ /**
88
+ * 用一段已有字节造一个内存 {@link BookSource}(REQ-CORE-005 的内置参考实现)。
89
+ *
90
+ * 用途:让宿主(尤其**测试**)在不触碰网络与文件系统的前提下注入字节源 ——
91
+ * REQ-CORE-005 的验收标准「测试中可注入内存 IO,无需网络」正是由它满足。
92
+ *
93
+ * 两处刻意的实现选择:
94
+ *
95
+ * - **零复制**:`read` 返回 `subarray` 视图而非副本,与 T1.8「`archive` 不复制字节」
96
+ * 同一口径。代价是调用方修改返回的数组会穿透到底层字节 —— 需要隔离时请自行传入副本。
97
+ * - **`close()` 有实义**:关闭后 `read` 抛 `StateError`,而不是静默返回空数组。
98
+ * 「已释放」与「读到空数据」是两回事,静默返回空会让缺陷延后到很难定位的地方。
99
+ *
100
+ * @param bytes - 底层字节。**不被复制**,返回的源持有其视图。
101
+ * @param options - 仅 `source`(错误 message 用的标签)。
102
+ */
103
+ declare function createMemoryBookSource(bytes: Uint8Array, options?: MemoryBookSourceOptions): BookSource;
104
+
105
+ /**
106
+ * 通用书籍模型(`frond-js/core`)。
107
+ *
108
+ * 这是**跨格式契约**:EPUB、FB2、CBZ、MOBI 最终都产出同一个 `Book`,
109
+ * 渲染层与 Reader 只面向本模型编程,不感知格式差异
110
+ * (ARCHITECTURE §1 / §3.4 的数据流终点)。
111
+ *
112
+ * 设计约束:
113
+ * - **只读**:模型一旦产出即不可变,禁止渲染层反向修改
114
+ * - **零 DOM、零运行时依赖**(H4 / H12)
115
+ * - 本文件**只有类型**,不产生运行时代码,因此不计入覆盖率分母
116
+ */
117
+ /**
118
+ * 出版元数据。字段口径见 T1.5 决策 + **FI-X38 修订**(2026-09-22):三个字段**类型上恒为 `string`**
119
+ * (无来源就给空串,不改成可选);而「**缺了就拒开**」只适用于该格式既有判据本就要求它的场合 ——
120
+ * EPUB 的 `dc:identifier`、FB2 的 `document-info/id` 判红不变;MOBI 的 EXTH 104(ISBN)/ 113(ASIN)
121
+ * 是 **Kindle 产物**、规范不要求 ⇒ 缺失给 `""`(**不再拒开**);PDF / CBZ 本无标识符来源 ⇒ 恒 `""`。
122
+ * ⚠️ 三条不变:不拿 PalmDB 名 / 文件名顶替标识符(那是造假不是回退);`creators` 仍非必填;
123
+ * `title` 在 MOBI 侧仍要求「EXTH 503 与完整书名至少有一个」。
124
+ */
125
+ interface BookMetadata {
126
+ /** `dc:title`。EPUB 3 要求至少一个,取**第一个**。 */
127
+ readonly title: string;
128
+ /** 全部 `dc:creator`,按文档顺序。可为空数组(创作者非必填)。 */
129
+ readonly creators: readonly string[];
130
+ /** `dc:language`。EPUB 3 要求至少一个,取**第一个**。 */
131
+ readonly language: string;
132
+ /** `dc:identifier`(优先取 `unique-identifier` 指向的那条)。 */
133
+ readonly identifier: string;
134
+ /**
135
+ * `dcterms:modified`。
136
+ *
137
+ * ⚠️ EPUB 3 规范要求该字段,但真实文件常缺失。缺失**不报错**(T1.5 决策):
138
+ * 为合规而拒掉现实中能正常阅读的书,代价高于收益。
139
+ */
140
+ readonly modified?: string;
141
+ }
142
+ /** manifest 中的一个资源条目。 */
143
+ interface ManifestItem {
144
+ /** `id`。在同一个 OPF 内必须唯一。 */
145
+ readonly id: string;
146
+ /**
147
+ * `href`,**已相对 OPF 所在目录规范化**(不含前导 `/`,可直接交给归档读取层)。
148
+ *
149
+ * 有意处理:`#` 之后的 fragment 被剥离(manifest href 的 fragment 无意义,
150
+ * 保留会导致资源读取失败);**不做**百分号解码(ZIP 条目名本身不编码)。
151
+ */
152
+ readonly href: string;
153
+ /** `media-type`。 */
154
+ readonly mediaType: string;
155
+ /**
156
+ * `properties`,按空白拆分后的数组。
157
+ *
158
+ * EPUB 3 的 `nav`、`scripted`、`svg` 等标记在此。EPUB 2 无该属性 → 空数组。
159
+ */
160
+ readonly properties: readonly string[];
161
+ }
162
+ /** spine 中的一个阅读顺序项。 */
163
+ interface SpineItem {
164
+ /** `idref`,指向 manifest 条目。 */
165
+ readonly idref: string;
166
+ /** `linear`:`false` 表示 `linear="no"`(辅助内容,如封面、版权页)。默认 `true`。 */
167
+ readonly linear: boolean;
168
+ /** 在 spine 中的顺序,从 0 开始。 */
169
+ readonly index: number;
170
+ /** 已解析的 manifest 条目。**解析期已校验存在性**,调用方无需再查表。 */
171
+ readonly item: ManifestItem;
172
+ }
173
+ /**
174
+ * 目录中的一个条目。
175
+ *
176
+ * **跨格式契约**:EPUB(NAV / NCX)、FB2(`<section>` 嵌套标题)、
177
+ * MOBI(索引)最终都产出同一模型,渲染层与 Reader 只面向它编程。
178
+ *
179
+ * 与 {@link ManifestItem} 的关系:两者的 `href` **口径一致** —— 都**不含** fragment。
180
+ * 目录项多出来的 fragment 单独成字段,因此 `href` 可以直接交给归档读取层,
181
+ * 调用方不必再判断「这个 href 要不要拆」。这是 T1.6 的取舍:
182
+ * 拆解在**解析时做一次**,而不是让每个消费方各拆一次。
183
+ */
184
+ interface TocItem {
185
+ /** 显示文本。取源文档原文(仅折叠两侧空白)。 */
186
+ readonly label: string;
187
+ /**
188
+ * 目标文档在归档内的路径,**已规范化且不含 fragment**。
189
+ *
190
+ * 相对链接按**引用它的文档**(NAV / NCX 文件)所在目录解析。
191
+ * 该目录通常与 OPF 同目录,此时等价于 {@link Book.basePath}。
192
+ */
193
+ readonly href: string;
194
+ /**
195
+ * `#` 之后的定位信息,**不含 `#`**。无 fragment(或 fragment 为空串)时该字段不存在。
196
+ *
197
+ * 内容可能是元素 id(`"s2"`)、EPUB CFI(`"epubcfi(/6/4!/4/2)"`)—— 本模型**不解释**它,
198
+ * 交由 CFI 解析(`src/core/cfi/**`)与渲染层按需处理。
199
+ *
200
+ * ✅ **MOBI 的两个版本都已收编为元素 id**(MOBI6 见 T3.2b ④,KF8 见 T3.2b ③b):
201
+ *
202
+ * - **MOBI6**:正文是**单个** HTML 文档,目录靠 `guide` 的 `<reference type="toc" filepos=N>`
203
+ * 指向**目录页**,目录页里的锚点又是 `<a filepos=N>` —— `N` 是**字节**偏移(不是字符下标)。
204
+ * `openMobi` 先用 `shiftOffset` 把它换算到改写后的空间,再在目标开标签上**就地**
205
+ * 注入 `id="mobi-toc-N"`(`src/formats/mobi/anchors.ts`)⇒ 本字段拿到的是**元素 id**。
206
+ * 落点不是开标签时**保留原字节偏移字符串**(降级,不抛错)⇒ 那一条仍定位不到。
207
+ * - **KF8**:目录数据来自 **INDX 表**(不是正文超链接)。`parseKf8Toc` 把
208
+ * `insertpos + off − skelpos(章)` 换算成全局 `flow[0]` 偏移,再在**重写之前**的章节字节里
209
+ * 反搜最近的 `id=` / `name=` ⇒ 本字段拿到的是**元素 id**(`src/formats/mobi/kf8toc.ts`)。
210
+ * 反搜不到时该字段**不写**(而非写空串)。
211
+ *
212
+ * ⇒ **消费方不要把 `fragment` 无条件当元素 id 用**:取不到就只是「定位不到」,
213
+ * 而不是「文档有问题」。形态只剩**两种**(元素 id / CFI 字符串),
214
+ * 判据是**前缀**(见 `src/reader/navigation.ts` 的 `locationForTocItem`)。
215
+ */
216
+ readonly fragment?: string;
217
+ /** 子条目,按文档顺序。无子项时为空数组。 */
218
+ readonly children: readonly TocItem[];
219
+ }
220
+ /**
221
+ * 支持的书籍格式。**`Book` 的判别字段**(T3.0.2 / ADR-0014 新增)。
222
+ *
223
+ * 有了它,调用方不必靠「哪些字段是 `undefined`」反推拿到的是什么格式。
224
+ */
225
+ type BookFormat = 'epub' | 'fb2' | 'mobi' | 'cbz' | 'pdf';
226
+ /**
227
+ * 书级**阅读方向**(D-7 新增)—— {@link Book.direction} 的取值。
228
+ *
229
+ * ## 为什么不含 `'default'`
230
+ *
231
+ * EPUB 的 `spine@page-progression-direction` 合法值是 `ltr` / `rtl` / `default`,
232
+ * 而 `default` 的语义是「**没有声明方向**」。把它收进类型会让每个消费方都要处理
233
+ * 一个「第三种方向」,而它其实等价于「没有值」⇒ 解析时直接映射为 `undefined`,
234
+ * 本类型只留两个**真的方向**。
235
+ *
236
+ * ## 与 CSS `direction` 的关系
237
+ *
238
+ * 消费方把它写成内容文档的 `<html dir="…">`,浏览器据此映射出 CSS `direction`
239
+ * (`src/render/default/sandbox.ts`)。⚠️ 它是**书的元数据**,与 `paginator.ts`
240
+ * 从 `getComputedStyle()` 读到的**计算值**是两件事 —— 后者是前者的下游结果。
241
+ */
242
+ type ReadingDirection = 'ltr' | 'rtl';
243
+ /**
244
+ * 通用资源条目(跨格式)。
245
+ *
246
+ * **EPUB 的 {@link ManifestItem} 是它的超集**(多 `id` 与 `properties`)——
247
+ * 非 OPF 格式(CBZ 的图片序列、MOBI 的记录表)只需给出「归档内路径 + 媒体类型」。
248
+ */
249
+ interface ResourceItem {
250
+ /** 归档内路径,**已规范化**(口径同 {@link ManifestItem.href})。 */
251
+ readonly href: string;
252
+ /** 媒体类型。 */
253
+ readonly mediaType: string;
254
+ }
255
+ /**
256
+ * 通用阅读顺序条目(跨格式)。
257
+ *
258
+ * **EPUB 的 {@link SpineItem} 是它的超集**(多 `idref` / `index` / 已解析的 `item`)。
259
+ */
260
+ interface ChapterItem {
261
+ /** 归档内路径,**已规范化**(口径同 {@link ManifestItem.href})。 */
262
+ readonly href: string;
263
+ /** 媒体类型。 */
264
+ readonly mediaType: string;
265
+ /** 是否属主阅读顺序(EPUB 的 `linear="no"` 对应 `false`)。 */
266
+ readonly linear: boolean;
267
+ }
268
+ /**
269
+ * 一本已解析的书。
270
+ *
271
+ * 数据流(规格 §3):
272
+ * `BookInput/BookSource → ZIP → container.xml → OPF → metadata + manifest + spine
273
+ * → NAV or NCX → Book + CFI → Renderer → Reader`
274
+ *
275
+ * ## 字段分层(T3.0.2 / ADR-0014)
276
+ *
277
+ * `Book` 是**跨格式契约**,但字段里混着两套口径,现显式分层:
278
+ *
279
+ * | 层 | 字段 | 非 OPF 格式下的取值 |
280
+ * | ------------- | ---------------------------------------------------------------------- | ------------------------------- |
281
+ * | **通用** | `format` / `metadata` / `resources` / `chapters` | **必须有值** |
282
+ * | **通用(可选)** | `direction` | **不存在**(`undefined`) |
283
+ * | **EPUB 专属** | `opfPath` / `basePath` | **不存在**(`undefined`) |
284
+ * | **EPUB 专属** | `manifest` / `spine` / `manifestIndex` / `tocItem` / `ncxPath` | 空数组 / 空 `Map` / `undefined` |
285
+ *
286
+ * ⚠️ **「本格式没有这个概念」≠「解析失败」** —— 前者是**如实为空**,后者抛错。
287
+ *
288
+ * ### 可选化的判据:该字段的「没有」**有没有诚实的空值**(T3.2a 定稿)
289
+ *
290
+ * - `opfPath` / `basePath`:**没有**。填 `''` 会被读成「OPF 在归档根目录」,
291
+ * 那是**假信息** —— 非 OPF 格式根本没有 OPF。故改为可选:`undefined`
292
+ * 表示「本格式没有 OPF 这个概念」,与「解析失败」区分开。
293
+ * - `manifest` / `spine` / `manifestIndex`:**有**。`[]` / `new Map()` 直白地
294
+ * 表示「没有条目」,不指认任何具体位置,故保持必填、取空值。
295
+ *
296
+ * ⚠️ **`spine` 保持必填**另有一条独立理由:`reader/` 仍有一处读它 ——
297
+ * `spineIndexFromCfi`,因为 CFI 的 `/6/2n` 描述的就是 **EPUB package 文档内**的
298
+ * 位置,本就是 OPF 概念(非 OPF 格式收到 CFI 报 `FormatError` 是**诚实**的)。
299
+ * **导航面(定位 / 翻页)自 T3.2a 起读 `chapters`**(`reader/navigation.ts`),
300
+ * 非 OPF 格式因此可读 —— 这条由 `tests/reader/navigation.test.ts` 守住。
301
+ */
302
+ interface Book {
303
+ /**
304
+ * 本 `Book` 的来源格式(**判别字段**)。
305
+ *
306
+ * **必填**:EPUB 解析器产出 `'epub'`;后续 FB2 / CBZ / MOBI / PDF 适配器各自产出自己的值。
307
+ */
308
+ readonly format: BookFormat;
309
+ readonly metadata: BookMetadata;
310
+ /**
311
+ * 通用资源表,按文档顺序。
312
+ *
313
+ * EPUB 下由 {@link manifest} **派生** —— 由**同一个函数**产出(ADR-0014 的「单一事实来源」)。
314
+ */
315
+ readonly resources: readonly ResourceItem[];
316
+ /**
317
+ * 通用阅读顺序,按阅读顺序。
318
+ *
319
+ * EPUB 下由 {@link spine} **派生**:`chapters[i].href === spine[i].item.href`
320
+ * 由单测**逐项断言**守住 —— **不靠约定靠断言**(同 ADR-0008 的「唯一写入点」思路)。
321
+ */
322
+ readonly chapters: readonly ChapterItem[];
323
+ /**
324
+ * 书级阅读方向(`ltr` / `rtl`)。**未声明时为 `undefined`**(D-7 新增)。
325
+ *
326
+ * 唯一来源是 EPUB 的 `spine@page-progression-direction`(`formats/epub/opf.ts`):
327
+ * `ltr` / `rtl` 原样取值,`default` 与**任何非法值**都映射为 `undefined`
328
+ * —— 「非法方向 = 没有方向」,与 `spine@toc` 悬空时 `ncxPath` 置空同一口径
329
+ * (T1.5 对**可选线索**的处置:不因一个可选线索有瑕疵就拒掉整本能读的书)。
330
+ *
331
+ * ⚠️ **它是「书的元数据」,不是「内容文档的排版结果」**:消费方(渲染层)把它写成
332
+ * `<html dir="…">`,浏览器再据此得出 CSS `direction`。`paginator.ts` 读的是后者
333
+ * (计算值)—— 两者不在同一层,别把这里的值当成排版事实。
334
+ *
335
+ * ⚠️ **竖排(`writing-mode: vertical-rl`)不在本字段的表达范围内**:那是 CSS 的事,
336
+ * 由书籍自己的样式表声明,`Book` 不解析样式表(详见 `paginator.ts` 的镜像轴说明)。
337
+ */
338
+ readonly direction?: ReadingDirection;
339
+ /**
340
+ * package document(OPF)在归档内的路径。**非 OPF 格式下不存在**(T3.2a 可选化)。
341
+ *
342
+ * ⚠️ **与 `spine` 的取舍相反**(那里刻意保持必填):`spine` 的「没有」有**诚实的空值**
343
+ * (空数组,语义是「没有阅读顺序项」);而 `opfPath` 的「没有」**没有** ——
344
+ * 填 `''` 会被读成「OPF 在归档根目录」,那是**假信息**。
345
+ * 因此这里可选:`undefined` 表示「本格式没有 OPF 这个概念」,与「解析失败」区分开。
346
+ */
347
+ readonly opfPath?: string;
348
+ /**
349
+ * OPF 所在目录,用于解析书内其他相对引用。根目录时为 `''`。
350
+ *
351
+ * **非 OPF 格式下不存在**(理由同 {@link opfPath})。
352
+ */
353
+ readonly basePath?: string;
354
+ /** 全部 manifest 条目,按文档顺序。 */
355
+ readonly manifest: readonly ManifestItem[];
356
+ /** 全部 spine 项,按阅读顺序(`index` 递增)。 */
357
+ readonly spine: readonly SpineItem[];
358
+ /**
359
+ * 按 `id` 索引的 manifest,用于 O(1) 查找。
360
+ *
361
+ * 键与 {@link manifest} 中的条目一一对应。**重复 id 在解析期即抛错**,
362
+ * 因此这里不会出现覆盖。
363
+ */
364
+ readonly manifestIndex: ReadonlyMap<string, ManifestItem>;
365
+ /**
366
+ * EPUB 3 的导航文档条目(manifest 中 `properties` 含 `nav` 者)。
367
+ *
368
+ * 仅**识别**,不解析 —— 目录解析属 T1.6。
369
+ */
370
+ readonly tocItem?: ManifestItem;
371
+ /**
372
+ * EPUB 2 的 NCX 路径(已规范化,相对归档根),来自 `spine@toc`。
373
+ *
374
+ * 仅**识别**,不解析 —— 目录解析属 T1.6。
375
+ */
376
+ readonly ncxPath?: string;
377
+ }
378
+
379
+ export { type BookSource as B, type ChapterItem as C, type ManifestItem as M, type ResourceIO as R, type SpineItem as S, type TocItem as T, type Book as a, type BookFormat as b, type BookMetadata as c, type MemoryBookSourceOptions as d, type ReadingDirection as e, type ResourceItem as f, createMemoryBookSource as g };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * core 层的公共类型。
3
+ *
4
+ * 本模块属于 `frond-js/core`,**不得依赖 DOM,也不得依赖 render / reader**
5
+ * (ARCHITECTURE §1 依赖方向单向,H4)。
6
+ */
7
+ /**
8
+ * 所有异步 API 的统一选项(H6)。
9
+ *
10
+ * 约定(H7):signal abort 后,操作必须抛出 `AbortError` 并释放已占用的资源。
11
+ */
12
+ interface OperationOptions {
13
+ /** 取消信号。 */
14
+ readonly signal?: AbortSignal;
15
+ }
16
+
17
+ export type { OperationOptions as O };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * core 层的公共类型。
3
+ *
4
+ * 本模块属于 `frond-js/core`,**不得依赖 DOM,也不得依赖 render / reader**
5
+ * (ARCHITECTURE §1 依赖方向单向,H4)。
6
+ */
7
+ /**
8
+ * 所有异步 API 的统一选项(H6)。
9
+ *
10
+ * 约定(H7):signal abort 后,操作必须抛出 `AbortError` 并释放已占用的资源。
11
+ */
12
+ interface OperationOptions {
13
+ /** 取消信号。 */
14
+ readonly signal?: AbortSignal;
15
+ }
16
+
17
+ export type { OperationOptions as O };
package/package.json ADDED
@@ -0,0 +1,115 @@
1
+ {
2
+ "name": "frond-js",
3
+ "version": "0.4.0",
4
+ "description": "零 DOM 依赖的浏览器端电子书引擎:解析、建模、渲染、阅读",
5
+ "keywords": [
6
+ "epub",
7
+ "ebook",
8
+ "reader",
9
+ "epub3",
10
+ "cfi",
11
+ "pagination"
12
+ ],
13
+ "author": "frond-js contributors",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://gitee.com/eltongu/frond-js.git"
18
+ },
19
+ "homepage": "https://gitee.com/eltongu/frond-js",
20
+ "bugs": {
21
+ "url": "https://gitee.com/eltongu/frond-js/issues"
22
+ },
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "packageManager": "pnpm@11.25.0",
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.js",
41
+ "require": "./dist/index.cjs"
42
+ },
43
+ "./core": {
44
+ "types": "./dist/core/index.d.ts",
45
+ "import": "./dist/core/index.js",
46
+ "require": "./dist/core/index.cjs"
47
+ },
48
+ "./render": {
49
+ "types": "./dist/render/index.d.ts",
50
+ "import": "./dist/render/index.js",
51
+ "require": "./dist/render/index.cjs"
52
+ },
53
+ "./formats/epub": {
54
+ "types": "./dist/formats/epub/index.d.ts",
55
+ "import": "./dist/formats/epub/index.js",
56
+ "require": "./dist/formats/epub/index.cjs"
57
+ },
58
+ "./package.json": "./package.json"
59
+ },
60
+ "scripts": {
61
+ "build": "tsup",
62
+ "dev": "tsup --watch",
63
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.core.json",
64
+ "lint": "eslint .",
65
+ "lint:fix": "eslint . --fix",
66
+ "format": "prettier --write .",
67
+ "format:check": "prettier --check .",
68
+ "test": "vitest run --coverage",
69
+ "test:watch": "vitest",
70
+ "test:browser": "vitest run --config vitest.browser.config.ts",
71
+ "e2e": "node scripts/fixtures/build-large-epub.mjs && node scripts/fixtures/build-large-epub.mjs --chapters=11 --paragraphs-per-chapter=8000 --out=large-11x8000.epub && playwright test",
72
+ "size": "node scripts/check-size.mjs",
73
+ "api:check": "node scripts/check-api.mjs && node scripts/check-dom-surface.mjs && node scripts/check-dom-surface.controls.mjs && node scripts/check-dom-surface.mutation.mjs && node scripts/check-api-doc.mjs && node scripts/check-api-doc.controls.mjs && node scripts/check-api-doc.mutation.mjs",
74
+ "docs:check": "node scripts/check-doc-head-version.controls.mjs && node scripts/probe/table-shape-mutation.mjs && node scripts/probe/surface-map-mutation.mjs && node scripts/probe/roster-gate-mutation.mjs && node scripts/probe/heading-shapes.mjs && node scripts/check-doc-head-version.mjs",
75
+ "release:check": "node scripts/check-release.controls.mjs && node scripts/check-release.mutation.mjs && node scripts/check-release.mjs",
76
+ "release:verify-tag": "node scripts/verify-release-tag.controls.mjs && node scripts/verify-release-tag.mjs",
77
+ "release:diff-surface": "node scripts/diff-surface.controls.mjs && node scripts/diff-surface.mutation.mjs && node scripts/diff-surface.mjs",
78
+ "api:update": "node scripts/check-api.mjs --update",
79
+ "serve": "node scripts/serve.mjs",
80
+ "env:check": "node scripts/env-check.mjs",
81
+ "gate:local": "node scripts/gate-local.controls.mjs && node scripts/gate-local.mjs",
82
+ "gate:local:list": "node scripts/gate-local.mjs --list",
83
+ "verify": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm test:browser && pnpm build && pnpm size && pnpm release:check && pnpm api:check && pnpm docs:check && pnpm e2e",
84
+ "prepare": "simple-git-hooks"
85
+ },
86
+ "simple-git-hooks": {
87
+ "pre-commit": "pnpm lint"
88
+ },
89
+ "peerDependencies": {
90
+ "pdfjs-dist": ">=5.0.375 <7"
91
+ },
92
+ "peerDependenciesMeta": {
93
+ "pdfjs-dist": {
94
+ "optional": true
95
+ }
96
+ },
97
+ "devDependencies": {
98
+ "@eslint/js": "^10.0.1",
99
+ "@playwright/test": "^1.63.0",
100
+ "@types/node": "^22.20.2",
101
+ "@vitest/browser": "5.0.0",
102
+ "@vitest/browser-playwright": "5.0.0",
103
+ "@vitest/coverage-v8": "^5.0.0",
104
+ "eslint": "^10.10.0",
105
+ "eslint-config-prettier": "^10.1.8",
106
+ "globals": "^17.12.0",
107
+ "pdfjs-dist": "6.3.289",
108
+ "prettier": "^3.9.6",
109
+ "simple-git-hooks": "^2.14.0",
110
+ "tsup": "^8.5.1",
111
+ "typescript": "^5.9.3",
112
+ "typescript-eslint": "^8.70.0",
113
+ "vitest": "^5.0.0"
114
+ }
115
+ }