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,335 @@
1
+ import { C as Cfi } from './types-B7mslPBY.cjs';
2
+ import { B as BookSource, R as ResourceIO, T as TocItem } from './types-CPUqTEPW.cjs';
3
+ import { O as OperationOptions } from './types-DQYmArgv.cjs';
4
+ import { RenderHost, Renderer } from './render/index.cjs';
5
+ export { RenderLocation } from './render/index.cjs';
6
+ export { A as AbortError, F as FormatError, a as FrondError, b as FrondErrorCode, c as FrondErrorOptions, N as NetworkError, P as ParseError, S as SecurityError, d as StateError, e as createAbortError, i as isAbortError, f as isFrondError, l as linkSignals, o as onAbort, t as throwIfAborted } from './abort-BY8vBk0v.cjs';
7
+
8
+ /**
9
+ * Reader 生命周期状态(H5)。
10
+ *
11
+ * 禁止用布尔标志位表达状态;所有判断必须走状态机。
12
+ * 完整迁移规则见 `docs/ARCHITECTURE.md` §2.2。
13
+ */
14
+ type ReaderState = 'idle' | 'loading' | 'ready' | 'paginating' | 'error' | 'closed';
15
+ /** 全部合法状态。 */
16
+ declare const READER_STATES: readonly ReaderState[];
17
+ /** 判断 `from → to` 是否为合法迁移。 */
18
+ declare function canTransition(from: ReaderState, to: ReaderState): boolean;
19
+ type ReaderStateListener = (state: ReaderState) => void;
20
+
21
+ /**
22
+ * 格式适配层的**端口**(ADR-0013 / T3.0.3)。
23
+ *
24
+ * 本模块属于 `frond-js/core`,**只声明类型**:它是「谁能被注册为一种格式」的契约,
25
+ * 不含任何具体格式的实现,也不加载任何格式包。这样 `reader/` 可以只依赖本文件,
26
+ * 而不必静态 import 任何格式包 —— 后者会把格式解析器内联进根入口
27
+ * (ADR-0015 实测:EPUB 解析器曾被内联进 `dist/index.js`)。
28
+ *
29
+ * ⚠️ **为什么端口声明在 `core/` 而不是 `formats/`**:若声明在 `formats/`,
30
+ * `reader/` 就要 import `formats/` 这一层;而 `formats/` 内部一旦有了共享模块,
31
+ * 各格式包就会争相 import 它,「格式包之间不得互相 import」的边界随之失效。
32
+ * 声明在 `core/` 则依赖方向保持单向:`formats/* → core/`、`reader/ → core/`。
33
+ */
34
+
35
+ /**
36
+ * PDF 的**运行时资源地址**(FI-X37 / ADR-0016 修订 2)。
37
+ *
38
+ * 四个都是「一个目录的 URL」,**逐字**交给 PDF.js 的 `getDocument()`:
39
+ *
40
+ * | 键 | 解什么 |
41
+ * | -- | ------ |
42
+ * | `wasmUrl` | JPEG 2000 / JBIG2 / ICC 的 **wasm 解码器**(`openjpeg.wasm` / `jbig2.wasm` / `qcms_bg.wasm`)—— **扫描版书没有它就是整本空白** |
43
+ * | `cMapUrl` | CJK 等字体的 CMap |
44
+ * | `standardFontDataUrl` | 非嵌入的 14 标准字体 |
45
+ * | `iccUrl` | ICC 色彩空间 |
46
+ *
47
+ * ⚠️ **必须是字符串且以 `/` 结尾**(实测于批次 W-1,`pdfjs-dist@6.3.289`):
48
+ * PDF.js 的 `getFactoryUrlProp()` 对**非字符串静默返回 `null`**(不报错 ⇒ 表现为「传了没用」),
49
+ * 对**缺尾斜杠**才抛 `Invalid factory url: … must include trailing slash.`。
50
+ *
51
+ * ⚠️ **本库不归一化、也不猜默认值**(ADR-0013 禁「猜」):缺斜杠就让 PDF.js 抛,那是**能定位的错误**;
52
+ * 替宿主补一个 `/` 或指向 `pdfjs-dist/wasm/` 都会把「谁提供这些字节」这个责任抹掉 ——
53
+ * 打包器重定位 / CDN 部署下那种默认值会**静默 404**,回到本条要解决的同一个症状。
54
+ */
55
+ interface PdfAssetUrls {
56
+ readonly wasmUrl?: string;
57
+ readonly cMapUrl?: string;
58
+ readonly standardFontDataUrl?: string;
59
+ readonly iccUrl?: string;
60
+ }
61
+
62
+ /**
63
+ * 支持的书源输入。
64
+ *
65
+ * 五种形态的归一化口径(`Reader.open`):
66
+ *
67
+ * | 输入 | 处理 |
68
+ * | ------------- | ----------------------------------------------------------------- |
69
+ * | `ArrayBuffer` | 包成 `Uint8Array`(**不复制**) |
70
+ * | `Uint8Array` | 直接交给 `openBook`(**不复制**) |
71
+ * | `Blob` | `await blob.arrayBuffer()` |
72
+ * | `string` | 按 **URL** 处理:`fetch` → 非 2xx 抛 `NetworkError` |
73
+ * | `BookSource` | 直接交给 `openBook`(可注入的字节源,REQ-CORE-005) |
74
+ */
75
+ type BookInput = ArrayBuffer | Uint8Array | Blob | string | BookSource;
76
+ /** `Reader` 构造选项。 */
77
+ interface ReaderOptions {
78
+ /**
79
+ * 渲染宿主(`Renderer.mount` 的入参)。
80
+ *
81
+ * **可选,但 `open()` 时缺失即抛 `StateError`** —— 保留可选是因为
82
+ * `docs/API.md` §3 已登记 `constructor(options?)`,且「缺宿主」与「缺输入」
83
+ * 属同一类**调用方错误**(`StateError` 本就在 `open` 的抛出清单里),
84
+ * 不必为此改签名。宿主的存在性由 `open` 检查,而不是由构造期兜住。
85
+ */
86
+ readonly host?: RenderHost;
87
+ /** 自定义渲染器。不传则使用默认实现。 */
88
+ readonly renderer?: Renderer;
89
+ /**
90
+ * 自定义资源加载(图片 / 样式 / 字体 / 媒体)。
91
+ *
92
+ * **自 T2.8 起生效**:`Reader` 把它接到内置默认渲染器的 `loadResource` 上,
93
+ * 渲染章节时把指向归档的引用重写成 `blob:` URL。
94
+ *
95
+ * 两条边界,都是**如实登记**而不是静默忽略:
96
+ *
97
+ * - **只对内置默认渲染器生效**。自备 `renderer` 时 `Reader` 不透传 `io`
98
+ * —— `Renderer` 契约里没有承载资源来源的位置,取资源是渲染器自己的事。
99
+ * - **只管书内资源,不管章节**。章节文本仍从归档读(`readText`,自带解码与
100
+ * BOM 口径),`io` 面向的是「按 `href` 取整块字节」的已定位资源。
101
+ */
102
+ readonly io?: ResourceIO;
103
+ /**
104
+ * PDF 的运行时资源地址,**逐字**交给 PDF.js 的 `getDocument()`(W-3 / FI-X37)。
105
+ *
106
+ * 只有一个理由需要它:**扫描版 / 图像型 PDF**。这类书的页内容是 JPEG 2000 / JBIG2 图像,
107
+ * PDF.js 要 wasm 解码器才解得动;没给时**整本渲染成空白而不报任何错**
108
+ * (实测:真书上一张都没解出来,`state` 照样是 `ready`)。
109
+ *
110
+ * ⚠️ 每个地址必须是**以 `/` 结尾的字符串**(W-1 实测):PDF.js 对非字符串
111
+ * **静默按「没给」处理**,对缺尾斜杠才抛 `Invalid factory url: … must include trailing slash.`。
112
+ * 本库**不归一化、不猜默认值**(ADR-0013 禁「猜」,且打包器重定位 / CDN 下的默认路径会静默 404)。
113
+ *
114
+ * ⚠️ **只对 PDF 生效**:其余格式收到它也不读(刻意不摊平到顶层,见 `core/formats/types.ts` 的
115
+ * `FormatOpenOptions.pdf`)。`Reader.open()` 的四条分支(URL / Blob / ArrayBuffer / 字节与
116
+ * `BookSource`)都会带上它 —— 由 `tests/reader/pdf-assets.test.ts` 的源码扫描钉住。
117
+ */
118
+ readonly pdf?: PdfAssetUrls;
119
+ /**
120
+ * 初始位置。
121
+ *
122
+ * 传 `Cfi` 或 CFI 字符串时按**章内位置**定位;不传则从第一章章首开始。
123
+ * 形态解析见 `docs/API.md` §3。
124
+ */
125
+ readonly initialLocation?: Cfi | string;
126
+ }
127
+
128
+ /**
129
+ * `Reader` —— 阅读门面(REQ-READER-002)。
130
+ *
131
+ * ## 职责边界
132
+ *
133
+ * `Reader` **只做编排**:把「字节 → 书籍模型」(`core/formats` 的注册表 +
134
+ * 由 `./formats.js` 注入的格式包)、「位置 → 渲染」(`render` 的 `Renderer`)
135
+ * 与「生命周期」(`state.ts` 的迁移表)串成一条链,并保证**每一步都受迁移表
136
+ * 与取消语义约束**。
137
+ *
138
+ * 它**不**解析 EPUB、**不**操作 DOM、**不**测量排版 —— 那些分别属
139
+ * `formats/*` 与 `render/`(ARCHITECTURE §1 依赖方向单向:
140
+ * `reader/` 可依赖 `render` / `core`,反向不可)。
141
+ *
142
+ * ⚠️ **`reader/` 不得静态 import 任何具体格式包**(ADR-0013)。T3.0.3 之前这里
143
+ * 静态 `import` 着 `formats/epub`,EPUB 解析器因此被内联进根入口 —— 实测
144
+ * `dist/index.js` 命中 `META-INF/container.xml` 等 4 个 EPUB 专属串,
145
+ * 而 `dist/formats/epub/index.js` 是**另一份副本**。现在格式包一律经
146
+ * `./formats.js` 的**惰性 thunk** 触达,由 `pnpm size` 的根入口闭包数字守住。
147
+ *
148
+ * ## 生命周期
149
+ *
150
+ * 状态与全部合法迁移见 `docs/ARCHITECTURE.md` §2.2(**唯一事实来源**)。
151
+ * 本类**不使用任何布尔标志位**表达状态(H5),一切判断走 {@link ReaderStateMachine}。
152
+ *
153
+ * | 方法 | 允许的当前状态 |
154
+ * | ---- | -------------- |
155
+ * | `open` | `idle`、`error` |
156
+ * | `next` / `prev` / `goTo` | `ready` |
157
+ * | `close` | 任意非 `closed`(含 `idle`,第 12 条迁移) |
158
+ *
159
+ * 不满足前置条件时抛 `StateError`,**不静默忽略**。
160
+ *
161
+ * ## 检查顺序(本类定稿,T2.7)
162
+ *
163
+ * **`open`:状态 → 宿主 → `initialLocation` 语法 → 进入 `loading` → 取消。**
164
+ * **`next` / `prev` / `goTo`:状态(含会话)→ 取消 → 进入 `paginating`。**
165
+ *
166
+ * 与 `frame.ts` 的「已释放 → 已取消 → 模式 → 尺寸」同构:「已释放 / 状态不对」
167
+ * 与「配置缺失」都是**调用方错误**,必须排在**状态迁移之前** —— 否则一次失败
168
+ * 会留下一个卡在 `loading` 的实例;而且「已关闭的实例即便传了已取消的 `signal`
169
+ * 也应报 `StateError`」这条口径,正靠「状态先判」成立。
170
+ *
171
+ * 两条分岔的原因在**取消时的状态落点不同**:
172
+ *
173
+ * - `open` 的 abort 是**终态**(`closed`),而迁移表里从 `idle` / `error` 到
174
+ * `closed` 的边是给 `close()` 用的。所以先进入 `loading` 再检查取消,
175
+ * 走的正是文档里的 `idle → loading → closed`(#1 → #4)。
176
+ * - `next` / `prev` / `goTo` 的 abort 只是**回到原处**(`ready`)。取消检查前置
177
+ * 即可,不必为此绕一趟 `paginating`(那会在订阅者那里多广播两个状态)。
178
+ *
179
+ * 唯一**不能**前置的是「`initialLocation` 的 CFI 落在哪一章」:它需要书籍模型,
180
+ * 只能等解析完成。所以那类失败(`FormatError` / `RangeError`)落在 `error` 状态,
181
+ * 而「字符串根本不是 CFI」这种纯语法问题仍在前置阶段拦下(状态保持 `idle`)。
182
+ *
183
+ * ## abort 口径(由 `docs/API.md` §3.1 钉死,本类照做)
184
+ *
185
+ * - `open` 被 abort → 抛 `AbortError`,状态**转 `closed`**(迁移 #4)——
186
+ * 一次被取消的打开**不留可复用的实例**。
187
+ * - `next` / `prev` / `goTo` 被 abort → 抛 `AbortError`,状态**回 `ready`**(迁移 #7)——
188
+ * 双缓冲不残留,实例仍可继续翻页。
189
+ *
190
+ * ## 章级跳转与 `RenderLocation.cfi`
191
+ *
192
+ * `next` / `prev` / `goTo(章索引)` 是**章级跳转**,没有章内位置,
193
+ * 故 `RenderLocation.cfi` 传 `undefined`(该字段在 T2.7 收窄为可选,
194
+ * 理由见 `render/types.ts`)。只有 `initialLocation` / `goTo(CFI)` 才带 CFI。
195
+ *
196
+ * ## 导航面读 `chapters`,不读 `spine`(T3.2a 定稿)
197
+ *
198
+ * 本类**只经 `./navigation.js` 触达章节序列**,而那里读的是 `Book.chapters`
199
+ * (跨格式契约)—— 于是 MOBI / FB2 / CBZ 这类 `spine` 为空数组的格式**可读**。
200
+ * `spine` 只剩一处消费者:{@link spineIndexFromCfi},因为 CFI 的 `/6/2n`
201
+ * 描述的就是 **EPUB package 文档内**的位置,本就是 OPF 概念。
202
+ *
203
+ * ⚠️ **这条边界由 `tests/reader/navigation.test.ts` 守住,不是靠本注释** ——
204
+ * `reader.test.ts` 全程用 EPUB 夹具,而 EPUB 下 `chapters` 与 `spine` 逐项相等,
205
+ * 那上千行用例**无法区分**本类读的是哪一套(详见 `navigation.ts` 的模块注释)。
206
+ *
207
+ * ## 刻意不提供的接口
208
+ *
209
+ * `docs/API.md` §3 只登记了 `state` / `open` / `close` / `next` / `prev` /
210
+ * `goTo` / `subscribe`。**不额外暴露** `book` / `toc` / `currentLocation` 等 getter
211
+ * —— 未登记的能力不得擅自扩大公共面(H13);需要时先在 `API.md` 立项。
212
+ */
213
+ declare class Reader {
214
+ #private;
215
+ constructor(options?: ReaderOptions);
216
+ /** 当前状态。只读 —— 只能由内部状态机迁移(H5)。 */
217
+ get state(): ReaderState;
218
+ /**
219
+ * 当前书的目录树(按文档顺序)。无目录时为空数组。
220
+ *
221
+ * ## 为什么这个 getter 存在(T3.2b ③c 立项,ADR-0011 修订)
222
+ *
223
+ * 本节顶部的「刻意不提供的接口」原先写着**不暴露** `toc`。本批**改这条决定**,
224
+ * 理由是:**目录不可达就等于没有目录** —— `Reader` 的公开面原先**没有任何路径**
225
+ * 能让调用方拿到目录(`book` 也没暴露),于是「目录可跳转」这条需求在
226
+ * `Reader` 上**无法被调用方实现**(只能绕过门面直接用 `openBook` + 自备渲染器)。
227
+ * 现在有了 `toc` + {@link goTo} 的 `TocItem` 形态,这条链路才闭合。
228
+ *
229
+ * ⚠️ **只加这一个**:`book` / `currentLocation` 仍**不暴露** —— 它们各自都还没有
230
+ * 「调用方非拿到不可」的场景,而 `toc` 有。**未登记的能力不得擅自扩大公共面**(H13)。
231
+ *
232
+ * ## 前置条件:必须 `ready`
233
+ *
234
+ * 非 `ready` 时抛 `StateError`,**不返回空数组** —— 空数组与「这本书确实没有目录」
235
+ * 在类型上无法区分,返回它就是把**假信息**交给调用方(同 `locationAt` 的
236
+ * 「共 0 项」那条注释)。目录在 `open` 成功时一次性产出,`close` 后随会话一起释放。
237
+ *
238
+ * 返回的数组是**适配器产出的原对象**(不复制、不冻结):`TocItem` 是只读契约,
239
+ * 复制一份既无收益,又会让「同一个目录项对象」在两次读取间不是同一个引用。
240
+ *
241
+ * @throws {StateError} 当前状态不是 `ready`
242
+ */
243
+ get toc(): readonly TocItem[];
244
+ /**
245
+ * 打开一本书。`idle → loading → ready`,失败则 `loading → error`。
246
+ *
247
+ * 输入归一化(`BookInput` 五种形态):`ArrayBuffer` / `Uint8Array` / `BookSource`
248
+ * 直接交给格式注册表(不复制);`Blob` 走 `arrayBuffer()`;`string` 按 **URL**
249
+ * 走 `fetch`。格式的判别(探测)与适配器的加载都在注册表里完成 —— 本方法
250
+ * 对「这是什么格式」一无所知(ADR-0013)。
251
+ *
252
+ * 打开成功后**立即渲染落点**:默认落到第 0 章(`Book.chapters[0]`),传了
253
+ * `ReaderOptions.initialLocation` 则落到该 CFI 所指的章与章内位置。
254
+ *
255
+ * @throws {StateError} 当前状态不是 `idle` / `error`,或 `ReaderOptions.host` 缺失
256
+ * @throws {ParseError} container / OPF / 目录文档 XML 结构损坏
257
+ * @throws {FormatError} 认不出格式、该格式的适配器尚未实现、归档结构非法、
258
+ * OPF 语义违规,或 `initialLocation` 无法解释为书内位置
259
+ * @throws {NetworkError} `string` 输入的 URL 取字节失败(非 2xx / 网络错误)
260
+ * @throws {AbortError} 操作被取消 —— **状态转 `closed`**,已分配资源全部释放
261
+ */
262
+ open(input: BookInput, options?: OperationOptions): Promise<void>;
263
+ /**
264
+ * 关闭并释放全部资源。任意非 `closed` 状态 → `closed`;`closed` 为终态,不可复用。
265
+ *
266
+ * **幂等且不抛**(`docs/API.md` §3.1):重复调用无副作用。
267
+ *
268
+ * 关闭会**中止在飞操作**(内部 `AbortController`)—— 被中止的 `open` 以其
269
+ * `AbortError` 拒绝,被中止的 `next` / `prev` / `goTo` 同样;两者的状态落点
270
+ * 都由迁移表决定(`loading → closed` / `paginating → closed`)。
271
+ *
272
+ * 只有**已挂载过**的渲染器会收到 `unmount()` —— 从未打开过时(`idle` 下
273
+ * 直接 `close()`)没有要释放的东西,不会去打扰调用方传入的自定义渲染器。
274
+ */
275
+ close(): void;
276
+ /**
277
+ * 下一章。仅 `ready` 状态可调用。
278
+ *
279
+ * **「下一章」= 从当前章索引向后第一个 `linear === true` 的章**
280
+ * (`linear="no"` 是封面 / 版权页这类辅助内容,不进阅读顺序)。
281
+ * 后面没有线性项时是**合法的 no-op**:不迁移、不抛、不再渲染 ——
282
+ * `docs/API.md` §3.1 没有给 `next` 登记 `RangeError`,且迁移表里没有
283
+ * `ready → ready` 这条边,两条合起来只允许「什么都不做」。
284
+ *
285
+ * @throws {StateError} 当前状态不是 `ready`
286
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**,双缓冲不残留
287
+ */
288
+ next(options?: OperationOptions): Promise<void>;
289
+ /**
290
+ * 上一章。仅 `ready` 状态可调用。
291
+ *
292
+ * 与 {@link Reader.next} 对称:向前第一个 `linear === true` 的项;
293
+ * 前面没有线性项时是合法的 no-op。
294
+ *
295
+ * @throws {StateError} 当前状态不是 `ready`
296
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**
297
+ */
298
+ prev(options?: OperationOptions): Promise<void>;
299
+ /**
300
+ * 跳转到指定位置。
301
+ *
302
+ * 四种目标形态:
303
+ *
304
+ * | 目标 | 语义 | 失败时 |
305
+ * | ---- | ---- | ------ |
306
+ * | `number` | **章索引**(`Book.chapters` 的下标,`0` 起) | 非整数 / 越界 → `RangeError` |
307
+ * | `Cfi` / CFI 字符串 | 书内 CFI,**按它的 spine 步推出章**,并作为章内位置 | 字符串不是 CFI,或 CFI 推不出章 → `FormatError`;推出的章号越界 → `RangeError` |
308
+ * | `TocItem` | **目录项**(T3.2b ③c):`href` → 章,`fragment` → 章内位置 | `href` 不在阅读顺序里 → `FormatError`(见 {@link locationForTocItem}) |
309
+ *
310
+ * **CFI → 章**的推导口径见 {@link spineIndexFromCfi};**目录项 → 章内位置**
311
+ * 的口径见 {@link locationForTocItem}(那里也登记了 MOBI 目录项当前
312
+ * 「落到章首」这个已知缺口)。
313
+ *
314
+ * `number` 形态**不检查** `linear` —— 索引直达是显式意图,与「翻页跳过辅助内容」分开。
315
+ *
316
+ * 跳到当前所在章**照常重新渲染**(不做特判):调用方拿到的是同一套
317
+ * 「渲染完成」保证,不需要自己判断「这算不算真的跳了」。
318
+ *
319
+ * @throws {StateError} 当前状态不是 `ready`
320
+ * @throws {RangeError} 索引越界(含 CFI 推出的章号越界)
321
+ * @throws {FormatError} 字符串目标不是合法 CFI / CFI 无法映射到书内的章 /
322
+ * 目录项的 `href` 不在阅读顺序里
323
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**
324
+ */
325
+ goTo(target: Cfi | string | number | TocItem, options?: OperationOptions): Promise<void>;
326
+ /**
327
+ * 订阅状态变化,返回取消订阅函数。
328
+ *
329
+ * 取消函数**幂等**(重复调用无副作用)。监听器抛错**不**影响其他监听器
330
+ * —— 广播前会复制监听器集合(`state.ts` 已保证)。
331
+ */
332
+ subscribe(listener: ReaderStateListener): () => void;
333
+ }
334
+
335
+ export { type BookInput, BookSource, Cfi, OperationOptions, READER_STATES, Reader, type ReaderOptions, type ReaderState, type ReaderStateListener, RenderHost, Renderer, ResourceIO, canTransition };
@@ -0,0 +1,335 @@
1
+ import { C as Cfi } from './types-B7mslPBY.js';
2
+ import { B as BookSource, R as ResourceIO, T as TocItem } from './types-C-5eHSRH.js';
3
+ import { O as OperationOptions } from './types-DQYmArgv.js';
4
+ import { RenderHost, Renderer } from './render/index.js';
5
+ export { RenderLocation } from './render/index.js';
6
+ export { A as AbortError, F as FormatError, a as FrondError, b as FrondErrorCode, c as FrondErrorOptions, N as NetworkError, P as ParseError, S as SecurityError, d as StateError, e as createAbortError, i as isAbortError, f as isFrondError, l as linkSignals, o as onAbort, t as throwIfAborted } from './abort-BY8vBk0v.js';
7
+
8
+ /**
9
+ * Reader 生命周期状态(H5)。
10
+ *
11
+ * 禁止用布尔标志位表达状态;所有判断必须走状态机。
12
+ * 完整迁移规则见 `docs/ARCHITECTURE.md` §2.2。
13
+ */
14
+ type ReaderState = 'idle' | 'loading' | 'ready' | 'paginating' | 'error' | 'closed';
15
+ /** 全部合法状态。 */
16
+ declare const READER_STATES: readonly ReaderState[];
17
+ /** 判断 `from → to` 是否为合法迁移。 */
18
+ declare function canTransition(from: ReaderState, to: ReaderState): boolean;
19
+ type ReaderStateListener = (state: ReaderState) => void;
20
+
21
+ /**
22
+ * 格式适配层的**端口**(ADR-0013 / T3.0.3)。
23
+ *
24
+ * 本模块属于 `frond-js/core`,**只声明类型**:它是「谁能被注册为一种格式」的契约,
25
+ * 不含任何具体格式的实现,也不加载任何格式包。这样 `reader/` 可以只依赖本文件,
26
+ * 而不必静态 import 任何格式包 —— 后者会把格式解析器内联进根入口
27
+ * (ADR-0015 实测:EPUB 解析器曾被内联进 `dist/index.js`)。
28
+ *
29
+ * ⚠️ **为什么端口声明在 `core/` 而不是 `formats/`**:若声明在 `formats/`,
30
+ * `reader/` 就要 import `formats/` 这一层;而 `formats/` 内部一旦有了共享模块,
31
+ * 各格式包就会争相 import 它,「格式包之间不得互相 import」的边界随之失效。
32
+ * 声明在 `core/` 则依赖方向保持单向:`formats/* → core/`、`reader/ → core/`。
33
+ */
34
+
35
+ /**
36
+ * PDF 的**运行时资源地址**(FI-X37 / ADR-0016 修订 2)。
37
+ *
38
+ * 四个都是「一个目录的 URL」,**逐字**交给 PDF.js 的 `getDocument()`:
39
+ *
40
+ * | 键 | 解什么 |
41
+ * | -- | ------ |
42
+ * | `wasmUrl` | JPEG 2000 / JBIG2 / ICC 的 **wasm 解码器**(`openjpeg.wasm` / `jbig2.wasm` / `qcms_bg.wasm`)—— **扫描版书没有它就是整本空白** |
43
+ * | `cMapUrl` | CJK 等字体的 CMap |
44
+ * | `standardFontDataUrl` | 非嵌入的 14 标准字体 |
45
+ * | `iccUrl` | ICC 色彩空间 |
46
+ *
47
+ * ⚠️ **必须是字符串且以 `/` 结尾**(实测于批次 W-1,`pdfjs-dist@6.3.289`):
48
+ * PDF.js 的 `getFactoryUrlProp()` 对**非字符串静默返回 `null`**(不报错 ⇒ 表现为「传了没用」),
49
+ * 对**缺尾斜杠**才抛 `Invalid factory url: … must include trailing slash.`。
50
+ *
51
+ * ⚠️ **本库不归一化、也不猜默认值**(ADR-0013 禁「猜」):缺斜杠就让 PDF.js 抛,那是**能定位的错误**;
52
+ * 替宿主补一个 `/` 或指向 `pdfjs-dist/wasm/` 都会把「谁提供这些字节」这个责任抹掉 ——
53
+ * 打包器重定位 / CDN 部署下那种默认值会**静默 404**,回到本条要解决的同一个症状。
54
+ */
55
+ interface PdfAssetUrls {
56
+ readonly wasmUrl?: string;
57
+ readonly cMapUrl?: string;
58
+ readonly standardFontDataUrl?: string;
59
+ readonly iccUrl?: string;
60
+ }
61
+
62
+ /**
63
+ * 支持的书源输入。
64
+ *
65
+ * 五种形态的归一化口径(`Reader.open`):
66
+ *
67
+ * | 输入 | 处理 |
68
+ * | ------------- | ----------------------------------------------------------------- |
69
+ * | `ArrayBuffer` | 包成 `Uint8Array`(**不复制**) |
70
+ * | `Uint8Array` | 直接交给 `openBook`(**不复制**) |
71
+ * | `Blob` | `await blob.arrayBuffer()` |
72
+ * | `string` | 按 **URL** 处理:`fetch` → 非 2xx 抛 `NetworkError` |
73
+ * | `BookSource` | 直接交给 `openBook`(可注入的字节源,REQ-CORE-005) |
74
+ */
75
+ type BookInput = ArrayBuffer | Uint8Array | Blob | string | BookSource;
76
+ /** `Reader` 构造选项。 */
77
+ interface ReaderOptions {
78
+ /**
79
+ * 渲染宿主(`Renderer.mount` 的入参)。
80
+ *
81
+ * **可选,但 `open()` 时缺失即抛 `StateError`** —— 保留可选是因为
82
+ * `docs/API.md` §3 已登记 `constructor(options?)`,且「缺宿主」与「缺输入」
83
+ * 属同一类**调用方错误**(`StateError` 本就在 `open` 的抛出清单里),
84
+ * 不必为此改签名。宿主的存在性由 `open` 检查,而不是由构造期兜住。
85
+ */
86
+ readonly host?: RenderHost;
87
+ /** 自定义渲染器。不传则使用默认实现。 */
88
+ readonly renderer?: Renderer;
89
+ /**
90
+ * 自定义资源加载(图片 / 样式 / 字体 / 媒体)。
91
+ *
92
+ * **自 T2.8 起生效**:`Reader` 把它接到内置默认渲染器的 `loadResource` 上,
93
+ * 渲染章节时把指向归档的引用重写成 `blob:` URL。
94
+ *
95
+ * 两条边界,都是**如实登记**而不是静默忽略:
96
+ *
97
+ * - **只对内置默认渲染器生效**。自备 `renderer` 时 `Reader` 不透传 `io`
98
+ * —— `Renderer` 契约里没有承载资源来源的位置,取资源是渲染器自己的事。
99
+ * - **只管书内资源,不管章节**。章节文本仍从归档读(`readText`,自带解码与
100
+ * BOM 口径),`io` 面向的是「按 `href` 取整块字节」的已定位资源。
101
+ */
102
+ readonly io?: ResourceIO;
103
+ /**
104
+ * PDF 的运行时资源地址,**逐字**交给 PDF.js 的 `getDocument()`(W-3 / FI-X37)。
105
+ *
106
+ * 只有一个理由需要它:**扫描版 / 图像型 PDF**。这类书的页内容是 JPEG 2000 / JBIG2 图像,
107
+ * PDF.js 要 wasm 解码器才解得动;没给时**整本渲染成空白而不报任何错**
108
+ * (实测:真书上一张都没解出来,`state` 照样是 `ready`)。
109
+ *
110
+ * ⚠️ 每个地址必须是**以 `/` 结尾的字符串**(W-1 实测):PDF.js 对非字符串
111
+ * **静默按「没给」处理**,对缺尾斜杠才抛 `Invalid factory url: … must include trailing slash.`。
112
+ * 本库**不归一化、不猜默认值**(ADR-0013 禁「猜」,且打包器重定位 / CDN 下的默认路径会静默 404)。
113
+ *
114
+ * ⚠️ **只对 PDF 生效**:其余格式收到它也不读(刻意不摊平到顶层,见 `core/formats/types.ts` 的
115
+ * `FormatOpenOptions.pdf`)。`Reader.open()` 的四条分支(URL / Blob / ArrayBuffer / 字节与
116
+ * `BookSource`)都会带上它 —— 由 `tests/reader/pdf-assets.test.ts` 的源码扫描钉住。
117
+ */
118
+ readonly pdf?: PdfAssetUrls;
119
+ /**
120
+ * 初始位置。
121
+ *
122
+ * 传 `Cfi` 或 CFI 字符串时按**章内位置**定位;不传则从第一章章首开始。
123
+ * 形态解析见 `docs/API.md` §3。
124
+ */
125
+ readonly initialLocation?: Cfi | string;
126
+ }
127
+
128
+ /**
129
+ * `Reader` —— 阅读门面(REQ-READER-002)。
130
+ *
131
+ * ## 职责边界
132
+ *
133
+ * `Reader` **只做编排**:把「字节 → 书籍模型」(`core/formats` 的注册表 +
134
+ * 由 `./formats.js` 注入的格式包)、「位置 → 渲染」(`render` 的 `Renderer`)
135
+ * 与「生命周期」(`state.ts` 的迁移表)串成一条链,并保证**每一步都受迁移表
136
+ * 与取消语义约束**。
137
+ *
138
+ * 它**不**解析 EPUB、**不**操作 DOM、**不**测量排版 —— 那些分别属
139
+ * `formats/*` 与 `render/`(ARCHITECTURE §1 依赖方向单向:
140
+ * `reader/` 可依赖 `render` / `core`,反向不可)。
141
+ *
142
+ * ⚠️ **`reader/` 不得静态 import 任何具体格式包**(ADR-0013)。T3.0.3 之前这里
143
+ * 静态 `import` 着 `formats/epub`,EPUB 解析器因此被内联进根入口 —— 实测
144
+ * `dist/index.js` 命中 `META-INF/container.xml` 等 4 个 EPUB 专属串,
145
+ * 而 `dist/formats/epub/index.js` 是**另一份副本**。现在格式包一律经
146
+ * `./formats.js` 的**惰性 thunk** 触达,由 `pnpm size` 的根入口闭包数字守住。
147
+ *
148
+ * ## 生命周期
149
+ *
150
+ * 状态与全部合法迁移见 `docs/ARCHITECTURE.md` §2.2(**唯一事实来源**)。
151
+ * 本类**不使用任何布尔标志位**表达状态(H5),一切判断走 {@link ReaderStateMachine}。
152
+ *
153
+ * | 方法 | 允许的当前状态 |
154
+ * | ---- | -------------- |
155
+ * | `open` | `idle`、`error` |
156
+ * | `next` / `prev` / `goTo` | `ready` |
157
+ * | `close` | 任意非 `closed`(含 `idle`,第 12 条迁移) |
158
+ *
159
+ * 不满足前置条件时抛 `StateError`,**不静默忽略**。
160
+ *
161
+ * ## 检查顺序(本类定稿,T2.7)
162
+ *
163
+ * **`open`:状态 → 宿主 → `initialLocation` 语法 → 进入 `loading` → 取消。**
164
+ * **`next` / `prev` / `goTo`:状态(含会话)→ 取消 → 进入 `paginating`。**
165
+ *
166
+ * 与 `frame.ts` 的「已释放 → 已取消 → 模式 → 尺寸」同构:「已释放 / 状态不对」
167
+ * 与「配置缺失」都是**调用方错误**,必须排在**状态迁移之前** —— 否则一次失败
168
+ * 会留下一个卡在 `loading` 的实例;而且「已关闭的实例即便传了已取消的 `signal`
169
+ * 也应报 `StateError`」这条口径,正靠「状态先判」成立。
170
+ *
171
+ * 两条分岔的原因在**取消时的状态落点不同**:
172
+ *
173
+ * - `open` 的 abort 是**终态**(`closed`),而迁移表里从 `idle` / `error` 到
174
+ * `closed` 的边是给 `close()` 用的。所以先进入 `loading` 再检查取消,
175
+ * 走的正是文档里的 `idle → loading → closed`(#1 → #4)。
176
+ * - `next` / `prev` / `goTo` 的 abort 只是**回到原处**(`ready`)。取消检查前置
177
+ * 即可,不必为此绕一趟 `paginating`(那会在订阅者那里多广播两个状态)。
178
+ *
179
+ * 唯一**不能**前置的是「`initialLocation` 的 CFI 落在哪一章」:它需要书籍模型,
180
+ * 只能等解析完成。所以那类失败(`FormatError` / `RangeError`)落在 `error` 状态,
181
+ * 而「字符串根本不是 CFI」这种纯语法问题仍在前置阶段拦下(状态保持 `idle`)。
182
+ *
183
+ * ## abort 口径(由 `docs/API.md` §3.1 钉死,本类照做)
184
+ *
185
+ * - `open` 被 abort → 抛 `AbortError`,状态**转 `closed`**(迁移 #4)——
186
+ * 一次被取消的打开**不留可复用的实例**。
187
+ * - `next` / `prev` / `goTo` 被 abort → 抛 `AbortError`,状态**回 `ready`**(迁移 #7)——
188
+ * 双缓冲不残留,实例仍可继续翻页。
189
+ *
190
+ * ## 章级跳转与 `RenderLocation.cfi`
191
+ *
192
+ * `next` / `prev` / `goTo(章索引)` 是**章级跳转**,没有章内位置,
193
+ * 故 `RenderLocation.cfi` 传 `undefined`(该字段在 T2.7 收窄为可选,
194
+ * 理由见 `render/types.ts`)。只有 `initialLocation` / `goTo(CFI)` 才带 CFI。
195
+ *
196
+ * ## 导航面读 `chapters`,不读 `spine`(T3.2a 定稿)
197
+ *
198
+ * 本类**只经 `./navigation.js` 触达章节序列**,而那里读的是 `Book.chapters`
199
+ * (跨格式契约)—— 于是 MOBI / FB2 / CBZ 这类 `spine` 为空数组的格式**可读**。
200
+ * `spine` 只剩一处消费者:{@link spineIndexFromCfi},因为 CFI 的 `/6/2n`
201
+ * 描述的就是 **EPUB package 文档内**的位置,本就是 OPF 概念。
202
+ *
203
+ * ⚠️ **这条边界由 `tests/reader/navigation.test.ts` 守住,不是靠本注释** ——
204
+ * `reader.test.ts` 全程用 EPUB 夹具,而 EPUB 下 `chapters` 与 `spine` 逐项相等,
205
+ * 那上千行用例**无法区分**本类读的是哪一套(详见 `navigation.ts` 的模块注释)。
206
+ *
207
+ * ## 刻意不提供的接口
208
+ *
209
+ * `docs/API.md` §3 只登记了 `state` / `open` / `close` / `next` / `prev` /
210
+ * `goTo` / `subscribe`。**不额外暴露** `book` / `toc` / `currentLocation` 等 getter
211
+ * —— 未登记的能力不得擅自扩大公共面(H13);需要时先在 `API.md` 立项。
212
+ */
213
+ declare class Reader {
214
+ #private;
215
+ constructor(options?: ReaderOptions);
216
+ /** 当前状态。只读 —— 只能由内部状态机迁移(H5)。 */
217
+ get state(): ReaderState;
218
+ /**
219
+ * 当前书的目录树(按文档顺序)。无目录时为空数组。
220
+ *
221
+ * ## 为什么这个 getter 存在(T3.2b ③c 立项,ADR-0011 修订)
222
+ *
223
+ * 本节顶部的「刻意不提供的接口」原先写着**不暴露** `toc`。本批**改这条决定**,
224
+ * 理由是:**目录不可达就等于没有目录** —— `Reader` 的公开面原先**没有任何路径**
225
+ * 能让调用方拿到目录(`book` 也没暴露),于是「目录可跳转」这条需求在
226
+ * `Reader` 上**无法被调用方实现**(只能绕过门面直接用 `openBook` + 自备渲染器)。
227
+ * 现在有了 `toc` + {@link goTo} 的 `TocItem` 形态,这条链路才闭合。
228
+ *
229
+ * ⚠️ **只加这一个**:`book` / `currentLocation` 仍**不暴露** —— 它们各自都还没有
230
+ * 「调用方非拿到不可」的场景,而 `toc` 有。**未登记的能力不得擅自扩大公共面**(H13)。
231
+ *
232
+ * ## 前置条件:必须 `ready`
233
+ *
234
+ * 非 `ready` 时抛 `StateError`,**不返回空数组** —— 空数组与「这本书确实没有目录」
235
+ * 在类型上无法区分,返回它就是把**假信息**交给调用方(同 `locationAt` 的
236
+ * 「共 0 项」那条注释)。目录在 `open` 成功时一次性产出,`close` 后随会话一起释放。
237
+ *
238
+ * 返回的数组是**适配器产出的原对象**(不复制、不冻结):`TocItem` 是只读契约,
239
+ * 复制一份既无收益,又会让「同一个目录项对象」在两次读取间不是同一个引用。
240
+ *
241
+ * @throws {StateError} 当前状态不是 `ready`
242
+ */
243
+ get toc(): readonly TocItem[];
244
+ /**
245
+ * 打开一本书。`idle → loading → ready`,失败则 `loading → error`。
246
+ *
247
+ * 输入归一化(`BookInput` 五种形态):`ArrayBuffer` / `Uint8Array` / `BookSource`
248
+ * 直接交给格式注册表(不复制);`Blob` 走 `arrayBuffer()`;`string` 按 **URL**
249
+ * 走 `fetch`。格式的判别(探测)与适配器的加载都在注册表里完成 —— 本方法
250
+ * 对「这是什么格式」一无所知(ADR-0013)。
251
+ *
252
+ * 打开成功后**立即渲染落点**:默认落到第 0 章(`Book.chapters[0]`),传了
253
+ * `ReaderOptions.initialLocation` 则落到该 CFI 所指的章与章内位置。
254
+ *
255
+ * @throws {StateError} 当前状态不是 `idle` / `error`,或 `ReaderOptions.host` 缺失
256
+ * @throws {ParseError} container / OPF / 目录文档 XML 结构损坏
257
+ * @throws {FormatError} 认不出格式、该格式的适配器尚未实现、归档结构非法、
258
+ * OPF 语义违规,或 `initialLocation` 无法解释为书内位置
259
+ * @throws {NetworkError} `string` 输入的 URL 取字节失败(非 2xx / 网络错误)
260
+ * @throws {AbortError} 操作被取消 —— **状态转 `closed`**,已分配资源全部释放
261
+ */
262
+ open(input: BookInput, options?: OperationOptions): Promise<void>;
263
+ /**
264
+ * 关闭并释放全部资源。任意非 `closed` 状态 → `closed`;`closed` 为终态,不可复用。
265
+ *
266
+ * **幂等且不抛**(`docs/API.md` §3.1):重复调用无副作用。
267
+ *
268
+ * 关闭会**中止在飞操作**(内部 `AbortController`)—— 被中止的 `open` 以其
269
+ * `AbortError` 拒绝,被中止的 `next` / `prev` / `goTo` 同样;两者的状态落点
270
+ * 都由迁移表决定(`loading → closed` / `paginating → closed`)。
271
+ *
272
+ * 只有**已挂载过**的渲染器会收到 `unmount()` —— 从未打开过时(`idle` 下
273
+ * 直接 `close()`)没有要释放的东西,不会去打扰调用方传入的自定义渲染器。
274
+ */
275
+ close(): void;
276
+ /**
277
+ * 下一章。仅 `ready` 状态可调用。
278
+ *
279
+ * **「下一章」= 从当前章索引向后第一个 `linear === true` 的章**
280
+ * (`linear="no"` 是封面 / 版权页这类辅助内容,不进阅读顺序)。
281
+ * 后面没有线性项时是**合法的 no-op**:不迁移、不抛、不再渲染 ——
282
+ * `docs/API.md` §3.1 没有给 `next` 登记 `RangeError`,且迁移表里没有
283
+ * `ready → ready` 这条边,两条合起来只允许「什么都不做」。
284
+ *
285
+ * @throws {StateError} 当前状态不是 `ready`
286
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**,双缓冲不残留
287
+ */
288
+ next(options?: OperationOptions): Promise<void>;
289
+ /**
290
+ * 上一章。仅 `ready` 状态可调用。
291
+ *
292
+ * 与 {@link Reader.next} 对称:向前第一个 `linear === true` 的项;
293
+ * 前面没有线性项时是合法的 no-op。
294
+ *
295
+ * @throws {StateError} 当前状态不是 `ready`
296
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**
297
+ */
298
+ prev(options?: OperationOptions): Promise<void>;
299
+ /**
300
+ * 跳转到指定位置。
301
+ *
302
+ * 四种目标形态:
303
+ *
304
+ * | 目标 | 语义 | 失败时 |
305
+ * | ---- | ---- | ------ |
306
+ * | `number` | **章索引**(`Book.chapters` 的下标,`0` 起) | 非整数 / 越界 → `RangeError` |
307
+ * | `Cfi` / CFI 字符串 | 书内 CFI,**按它的 spine 步推出章**,并作为章内位置 | 字符串不是 CFI,或 CFI 推不出章 → `FormatError`;推出的章号越界 → `RangeError` |
308
+ * | `TocItem` | **目录项**(T3.2b ③c):`href` → 章,`fragment` → 章内位置 | `href` 不在阅读顺序里 → `FormatError`(见 {@link locationForTocItem}) |
309
+ *
310
+ * **CFI → 章**的推导口径见 {@link spineIndexFromCfi};**目录项 → 章内位置**
311
+ * 的口径见 {@link locationForTocItem}(那里也登记了 MOBI 目录项当前
312
+ * 「落到章首」这个已知缺口)。
313
+ *
314
+ * `number` 形态**不检查** `linear` —— 索引直达是显式意图,与「翻页跳过辅助内容」分开。
315
+ *
316
+ * 跳到当前所在章**照常重新渲染**(不做特判):调用方拿到的是同一套
317
+ * 「渲染完成」保证,不需要自己判断「这算不算真的跳了」。
318
+ *
319
+ * @throws {StateError} 当前状态不是 `ready`
320
+ * @throws {RangeError} 索引越界(含 CFI 推出的章号越界)
321
+ * @throws {FormatError} 字符串目标不是合法 CFI / CFI 无法映射到书内的章 /
322
+ * 目录项的 `href` 不在阅读顺序里
323
+ * @throws {AbortError} 操作被取消 —— **状态回 `ready`**
324
+ */
325
+ goTo(target: Cfi | string | number | TocItem, options?: OperationOptions): Promise<void>;
326
+ /**
327
+ * 订阅状态变化,返回取消订阅函数。
328
+ *
329
+ * 取消函数**幂等**(重复调用无副作用)。监听器抛错**不**影响其他监听器
330
+ * —— 广播前会复制监听器集合(`state.ts` 已保证)。
331
+ */
332
+ subscribe(listener: ReaderStateListener): () => void;
333
+ }
334
+
335
+ export { type BookInput, BookSource, Cfi, OperationOptions, READER_STATES, Reader, type ReaderOptions, type ReaderState, type ReaderStateListener, RenderHost, Renderer, ResourceIO, canTransition };