epub-codec 1.2.12 → 1.3.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 (170) hide show
  1. package/README.md +24 -10
  2. package/dist/base64--HuymRl_.d.cts +5 -0
  3. package/dist/base64--HuymRl_.d.ts +5 -0
  4. package/dist/build-B1UxbO20.d.cts +5 -0
  5. package/dist/build-BCUsHLYw.d.ts +5 -0
  6. package/dist/codec-CX2Pa6Jq.d.ts +2876 -0
  7. package/dist/codec-qyKMOMt6.d.cts +2876 -0
  8. package/dist/codec.cjs +16 -0
  9. package/dist/codec.d.cts +2 -2702
  10. package/dist/codec.d.ts +2 -2702
  11. package/dist/codec.js +14 -1
  12. package/dist/container-B6cHHiY9.d.cts +4 -0
  13. package/dist/container-B6cHHiY9.d.ts +4 -0
  14. package/dist/context-CTgx7PwU.d.ts +28 -0
  15. package/dist/context-CXs2Yc9w.d.cts +28 -0
  16. package/dist/dimensions-B7ogpvKQ.d.cts +11 -0
  17. package/dist/dimensions-B7ogpvKQ.d.ts +11 -0
  18. package/dist/entities-CfQKtI34.d.ts +7 -0
  19. package/dist/entities-wt9zCx5q.d.cts +7 -0
  20. package/dist/footnote-CxsO5tCV.d.cts +7 -0
  21. package/dist/footnote-iZAPC_NT.d.ts +7 -0
  22. package/dist/format-DxUddUPj.d.cts +6 -0
  23. package/dist/format-DxUddUPj.d.ts +6 -0
  24. package/dist/format.d.cts +1 -5
  25. package/dist/format.d.ts +1 -5
  26. package/dist/image/dimensions.d.cts +1 -10
  27. package/dist/image/dimensions.d.ts +1 -10
  28. package/dist/index.cjs +24 -8
  29. package/dist/index.d.cts +31 -31
  30. package/dist/index.d.ts +31 -31
  31. package/dist/index.js +13 -10
  32. package/dist/inline-DDj6Sl3e.d.ts +12 -0
  33. package/dist/inline-YZOQY2wR.d.cts +12 -0
  34. package/dist/list-id-BB_yxm1H.d.cts +13 -0
  35. package/dist/list-id-BB_yxm1H.d.ts +13 -0
  36. package/dist/metadata-DXto7sM6.d.cts +7 -0
  37. package/dist/metadata-T5V2ovtS.d.ts +7 -0
  38. package/dist/model/package.cjs +19 -0
  39. package/dist/model/package.d.cts +2 -0
  40. package/dist/model/package.d.ts +2 -0
  41. package/dist/model/package.js +15 -0
  42. package/dist/nav/nav3.cjs +1 -1
  43. package/dist/nav/nav3.d.cts +1 -3
  44. package/dist/nav/nav3.d.ts +1 -3
  45. package/dist/nav/nav3.js +1 -1
  46. package/dist/nav/ncx.cjs +1 -1
  47. package/dist/nav/ncx.d.cts +1 -3
  48. package/dist/nav/ncx.d.ts +1 -3
  49. package/dist/nav/ncx.js +1 -1
  50. package/dist/nav/reconcile.d.cts +1 -3
  51. package/dist/nav/reconcile.d.ts +1 -3
  52. package/dist/nav/write.cjs +1 -1
  53. package/dist/nav/write.d.cts +1 -9
  54. package/dist/nav/write.d.ts +1 -9
  55. package/dist/nav/write.js +1 -1
  56. package/dist/nav3-SbAp8zlu.d.cts +4 -0
  57. package/dist/nav3-SbAp8zlu.d.ts +4 -0
  58. package/dist/ncx-kYhlXucj.d.cts +4 -0
  59. package/dist/ncx-kYhlXucj.d.ts +4 -0
  60. package/dist/node-CJLSSs1k.d.cts +84 -0
  61. package/dist/node-CJLSSs1k.d.ts +84 -0
  62. package/dist/ocf/container.cjs +1 -1
  63. package/dist/ocf/container.d.cts +1 -3
  64. package/dist/ocf/container.d.ts +1 -3
  65. package/dist/ocf/container.js +1 -1
  66. package/dist/ocf/write.d.cts +1 -3
  67. package/dist/ocf/write.d.ts +1 -3
  68. package/dist/opf/metadata.d.cts +1 -6
  69. package/dist/opf/metadata.d.ts +1 -6
  70. package/dist/opf/parse.cjs +1 -1
  71. package/dist/opf/parse.d.cts +1 -5
  72. package/dist/opf/parse.d.ts +1 -5
  73. package/dist/opf/parse.js +1 -1
  74. package/dist/opf/types.d.cts +1 -19
  75. package/dist/opf/types.d.ts +1 -19
  76. package/dist/opf/write.cjs +1 -1
  77. package/dist/opf/write.d.cts +1 -11
  78. package/dist/opf/write.d.ts +1 -11
  79. package/dist/opf/write.js +1 -1
  80. package/dist/package-io/read.cjs +39 -0
  81. package/dist/package-io/read.d.cts +2 -0
  82. package/dist/package-io/read.d.ts +2 -0
  83. package/dist/package-io/read.js +37 -0
  84. package/dist/package-io/write.cjs +34 -0
  85. package/dist/package-io/write.d.cts +2 -0
  86. package/dist/package-io/write.d.ts +2 -0
  87. package/dist/package-io/write.js +32 -0
  88. package/dist/parse-Bkkb7qXz.d.cts +6 -0
  89. package/dist/parse-C5GxodcX.d.ts +6 -0
  90. package/dist/parse-CmCjyz8i.d.cts +5 -0
  91. package/dist/parse-Dc76LQIj.d.ts +5 -0
  92. package/dist/path-Cx8-QMD6.d.cts +5 -0
  93. package/dist/path-Cx8-QMD6.d.ts +5 -0
  94. package/dist/path.d.cts +1 -4
  95. package/dist/path.d.ts +1 -4
  96. package/dist/query-B79eElol.d.ts +12 -0
  97. package/dist/query-C6ftPqNQ.d.cts +12 -0
  98. package/dist/read-B7kc_MeO.d.cts +10 -0
  99. package/dist/read-BMGq_D9y.d.cts +30 -0
  100. package/dist/read-Czqr3aop.d.ts +30 -0
  101. package/dist/read-DPkpZsYG.d.ts +10 -0
  102. package/dist/read.cjs +51 -6
  103. package/dist/read.d.cts +1 -9
  104. package/dist/read.d.ts +1 -9
  105. package/dist/read.js +52 -7
  106. package/dist/reconcile-BHky0kta.d.cts +4 -0
  107. package/dist/reconcile-BHky0kta.d.ts +4 -0
  108. package/dist/style-constants-RVoHdmju.d.cts +8 -0
  109. package/dist/style-constants-RVoHdmju.d.ts +8 -0
  110. package/dist/types-BxmkPT_F.d.cts +20 -0
  111. package/dist/types-BxmkPT_F.d.ts +20 -0
  112. package/dist/util/base64.d.cts +1 -4
  113. package/dist/util/base64.d.ts +1 -4
  114. package/dist/write-B89sagtw.d.cts +12 -0
  115. package/dist/write-BQcrPQvx.d.ts +10 -0
  116. package/dist/write-Boxi3aK0.d.cts +10 -0
  117. package/dist/write-COdkb8lG.d.cts +10 -0
  118. package/dist/write-COdkb8lG.d.ts +10 -0
  119. package/dist/write-DwaEwwgZ.d.ts +12 -0
  120. package/dist/write-DxiR-9eH.d.cts +4 -0
  121. package/dist/write-DxiR-9eH.d.ts +4 -0
  122. package/dist/write-Klo0WDhT.d.ts +13 -0
  123. package/dist/write-WSIgARHT.d.cts +13 -0
  124. package/dist/write.cjs +28 -6
  125. package/dist/write.d.cts +1 -9
  126. package/dist/write.d.ts +1 -9
  127. package/dist/write.js +28 -6
  128. package/dist/xhtml/context.d.cts +2 -22
  129. package/dist/xhtml/context.d.ts +2 -22
  130. package/dist/xhtml/footnote.cjs +4 -8
  131. package/dist/xhtml/footnote.d.cts +2 -7
  132. package/dist/xhtml/footnote.d.ts +2 -7
  133. package/dist/xhtml/footnote.js +4 -8
  134. package/dist/xhtml/inline.cjs +13 -7
  135. package/dist/xhtml/inline.d.cts +1 -11
  136. package/dist/xhtml/inline.d.ts +1 -11
  137. package/dist/xhtml/inline.js +13 -7
  138. package/dist/xhtml/link-target.cjs +18 -0
  139. package/dist/xhtml/link-target.d.cts +8 -0
  140. package/dist/xhtml/link-target.d.ts +8 -0
  141. package/dist/xhtml/link-target.js +17 -0
  142. package/dist/xhtml/list-id.d.cts +1 -12
  143. package/dist/xhtml/list-id.d.ts +1 -12
  144. package/dist/xhtml/read.cjs +50 -18
  145. package/dist/xhtml/read.d.cts +2 -20
  146. package/dist/xhtml/read.d.ts +2 -20
  147. package/dist/xhtml/read.js +49 -19
  148. package/dist/xhtml/style-constants.d.cts +1 -7
  149. package/dist/xhtml/style-constants.d.ts +1 -7
  150. package/dist/xhtml/write.cjs +44 -19
  151. package/dist/xhtml/write.d.cts +1 -11
  152. package/dist/xhtml/write.d.ts +1 -11
  153. package/dist/xhtml/write.js +44 -19
  154. package/dist/xml/build.d.cts +1 -4
  155. package/dist/xml/build.d.ts +1 -4
  156. package/dist/xml/entities.d.cts +1 -6
  157. package/dist/xml/entities.d.ts +1 -6
  158. package/dist/xml/node.d.cts +1 -83
  159. package/dist/xml/node.d.ts +1 -83
  160. package/dist/xml/parse.d.cts +1 -4
  161. package/dist/xml/parse.d.ts +1 -4
  162. package/dist/xml/query.cjs +1 -1
  163. package/dist/xml/query.d.cts +1 -11
  164. package/dist/xml/query.d.ts +1 -11
  165. package/dist/xml/query.js +1 -1
  166. package/dist/zip-ie17sOo5.d.cts +9 -0
  167. package/dist/zip-ie17sOo5.d.ts +9 -0
  168. package/dist/zip.d.cts +1 -8
  169. package/dist/zip.d.ts +1 -8
  170. package/package.json +1 -1
package/README.md CHANGED
@@ -87,6 +87,17 @@ const bytes2 = z.encode(epubCodec, tree);
87
87
 
88
88
  This is the no-extra-options form only — `readEpub(Content)`/`writeEpub(Content)` remain the entry points for a diagnostic sink.
89
89
 
90
+ The lossless byte-level `Package` (ExaDev/documents.js#963), for a caller that wants a genuine EPUB-to-EPUB round trip with no `ContentDocument`/`DocumentTree` projection in between — `readEpub(Content)`/`writeEpubContent` already cross this exact boundary internally, so reaching for it directly is for a caller inspecting or editing raw parts, not a prerequisite for ordinary reading/writing:
91
+
92
+ ```ts
93
+ import { decodePackage, encodePackage } from "epub-codec";
94
+
95
+ const pkg = decodePackage(epubBytes); // -> { parts: Record<string, Part> }, every zip entry as XML nodes or base64 bytes
96
+ const bytes2 = encodePackage(pkg); // decode -> encode is a fixed point: bytes2 carries the identical part content
97
+ ```
98
+
99
+ Unlike `ooxml.js`/`odf.js`, neither of which offers a one-shot bytes-in convenience at all (their own typed readers take an already-decoded `Package`, e.g. `readDocx(decodePackage(bytes))`), `readEpub(Content)`/`writeEpub(Content)` above stay this package's own established bytes-in/bytes-out API — `decodePackage`/`encodePackage` are additive, not a replacement for it.
100
+
90
101
  One spine itemref becomes one `ContentSection`, in spine order — **every** itemref, including one marked `linear="no"` (an EPUB 2 idiom for supplementary content, most often a footnote/endnote page): this package reads it as an ordinary section rather than silently skipping real content a reading system happens to route around. EPUB has no page concept of its own, so every section is given the same invented A4 + 1in default geometry (`ReadEpubOptions`/`WriteEpubOptions` carry no override for this, unlike `markdown-codec`'s `pageSize`/`margins` options — nothing in this package's own scope needs one yet).
91
102
 
92
103
  ## Architecture
@@ -94,17 +105,18 @@ One spine itemref becomes one `ContentSection`, in spine order — **every** ite
94
105
  Layered from the lossless OCF/XML primitives outward to the XHTML-to-`ContentDocument` mapping itself:
95
106
 
96
107
  - **`src/zip.ts`** — the OCF ZIP container: fixed-mtime, ordered-entries `zipPackage`/`unzipPackage` over `fflate`, hand-duplicated from `ooxml.js`'s and `odf.js`'s own identical wrappers rather than depending on `archive-codec` — see [Dependency choices](#dependency-choices).
97
- - **`src/xml/`** — the lossless XML layer every other module in this package builds on: `parse.ts`/`build.ts` wrap `fast-xml-parser` with the identical `preserveOrder` configuration `ooxml.js`'s and `odf.js`'s own XML modules use (order and mixed content survive; entity encoding stays raw until `entities.ts` decodes it in the one place text actually becomes content), `query.ts` the same handful of tree-walking helpers (`rootElement`, `findChildElement`, `childrenWithTag`, `elementsWithTag`, `attrValue`, plus `findElement` and `textContent`, needed here for footnote-target and nav-toc resolution respectively).
108
+ - **`src/xml/`** — the lossless XML layer every other module in this package builds on: `parse.ts`/`build.ts` wrap `fast-xml-parser` with the identical `preserveOrder` configuration `ooxml.js`'s and `odf.js`'s own XML modules use (order and mixed content survive; entity encoding stays raw until `entities.ts` decodes it in the one place text actually becomes content), `query.ts` the same handful of tree-walking helpers (`rootElement`, `findChildElement`, `childrenWithTag`, `elementsWithTag`, `attrValue`, plus `findElement` and `textContent`, needed here for footnote-target and nav-toc resolution respectively), `node.ts` the ordered `XmlNode` forest itself — also the shape `src/model/package.ts`'s own `XmlPart` carries.
109
+ - **`src/model/package.ts`/`src/package-io/`** — the lossless byte-level `Package` model (ExaDev/documents.js#963), mirroring `ooxml.js`'s and `odf.js`'s own `model/package.ts`/`package-io/` exactly: every zip entry as a `Part` (an `XmlNode[]` forest, or raw base64 bytes), `read.ts`'s `parsePackage`/`packageFromEntries` classifying entries into parts, `write.ts`'s `serializePackage`/`packageToEntries` the structural inverse (including the OCF mimetype-first-stored hoist). `src/codec.ts` wraps the pair as `decodePackage`/`encodePackage`; `src/read.ts`/`src/write.ts` cross this same boundary internally rather than talking to `src/zip.ts` directly.
98
110
  - **`src/ocf/`** — `container.ts` resolves the OPF rootfile from `META-INF/container.xml` (EPUB 3.3 §6.7.2); `write.ts` is its structural inverse.
99
111
  - **`src/opf/`** — `parse.ts`/`write.ts` read and write the OPF package document (§5.4): Dublin Core metadata (`metadata.ts`), the manifest, and the spine.
100
112
  - **`src/nav/`** — `nav3.ts`/`ncx.ts` reduce the EPUB 3 `<nav epub:type="toc">` document and the EPUB 2 NCX to a flat, fragment-stripped href sequence each; `reconcile.ts` compares that sequence against the spine's own reading order (the issue's own explicit "the spine wins" decision); `write.ts` builds a minimal EPUB 3 nav document, one entry per section, titled from each section's own first heading.
101
- - **`src/xhtml/`** — the mapping this package exists for: `read.ts` (`readXhtmlBody`, one XHTML content document's `<body>` to `ContentBlock[]`), `write.ts` (`writeXhtmlBody`, its structural inverse, built on `document-schema.js`'s own `decomposeSection` rather than re-deriving heading/list/construct nesting by hand), `inline.ts` (run-level formatting and footnote reference extents), `footnote.ts` (EPUB 3 `epub:type="noteref"`/`"footnote"` and the EPUB 2 linked-anchor idiom, both mapped onto the identical `anchor` construct), `list-id.ts` (list marker type packed into the opaque `numId`, mirroring `markdown-codec`'s own mechanism for the identical schema gap), `context.ts`/`style-constants.ts` (shared read-side plumbing and round-trip-only styleId/font constants).
113
+ - **`src/xhtml/`** — the mapping this package exists for: `read.ts` (`readXhtmlBody`, one XHTML content document's `<body>` to `ContentBlock[]`), `write.ts` (`writeXhtmlBody`, its structural inverse, built on `document-schema.js`'s own `decomposeSection` rather than re-deriving heading/list/construct nesting by hand), `inline.ts` (run-level formatting, footnote reference extents, and internal-link extents), `footnote.ts` (EPUB 3 `epub:type="noteref"`/`"footnote"` and the EPUB 2 linked-anchor idiom, both mapped onto the identical `anchor` construct), `link-target.ts` (`resolveHrefTarget`, the same-/cross-document href-to-element resolver `src/read.ts`'s own whole-spine internal-link registry and `inline.ts`'s reference-side lookup both build on — ExaDev/documents.js#963), `list-id.ts` (list marker type packed into the opaque `numId`, mirroring `markdown-codec`'s own mechanism for the identical schema gap), `context.ts`/`style-constants.ts` (shared read-side plumbing and round-trip-only styleId/font constants).
102
114
  - **`src/image/dimensions.ts`** — PNG/JPEG format detection and pixel dimensions, hand-written rather than reused from `byte-codec` — see [Dependency choices](#dependency-choices).
103
115
  - **`src/util/base64.ts`** — isomorphic base64 codec, a third hand-written copy of the identical helper `odf.js`'s and `pdf-codec`'s own `src/util/base64.ts` already carry.
104
116
  - **`src/path.ts`** — package-relative path resolution (manifest hrefs against the OPF's own directory, `<img src>` against its own XHTML document's directory), honouring `../` segments.
105
117
  - **`src/diagnostics.ts`** — the three-tier read/write failure policy; see [Conventions](#conventions).
106
- - **`src/read.ts`/`src/write.ts`** — the public entry points, composing every layer above into `readEpub(Content)`/`writeEpub(Content)`.
107
- - **`src/codec.ts`** — `epubCodec`/`epubContentCodec`, the `z.codec()` pair.
118
+ - **`src/read.ts`/`src/write.ts`** — the public entry points, composing every layer above into `readEpub(Content)`/`writeEpub(Content)`, `decodePackage`/`encodePackage` first/last.
119
+ - **`src/codec.ts`** — `epubCodec`/`epubContentCodec` (the `ContentDocument`/`DocumentTree` round trip) and `packageCodec`/`decodePackage`/`encodePackage` (the lossless `Package` round trip), each a `z.codec()` pair.
108
120
 
109
121
  ## Dependency choices
110
122
 
@@ -127,8 +139,8 @@ The issue that created this package ([ExaDev/documents.js#801](https://github.co
127
139
  Every construct this package's XHTML mapping cannot represent losslessly is a documented `EpubDiagnosticCodes` entry — see `src/diagnostics.ts` for the full table, and each module's own top-of-file comment for which side (read/write) it belongs to:
128
140
 
129
141
  - **`epub/element-unmapped`** — `<sub>`/`<sup>` degrade to plain text: `document-schema.js`'s `ContentRun` carries no subscript/superscript field at all, a genuine family-wide schema gap (no sibling codec has ever needed one; `ooxml.js`'s own docx reader has no `w:vertAlign` handling either), not something specific to this package.
130
- - **`epub/link-target-external-only`**every href, external or same-/cross-document, rides `ContentRun.hyperlink` verbatim rather than building the full internal-target `link` construct bookkeeping `document-schema.js`'s own vocabulary supports (an internal link construct needs a resolved same-document `anchor` target this package does not build for ordinary hyperlinks, only for recognised footnote references see below). A deliberate scope decision: every href still restores byte-for-byte either way, and the feature this degrades is real semantic internal-navigation fidelity, not content loss.
131
- - **Footnotes are same-document only.** Both the EPUB 3 structured idiom (`epub:type="noteref"`/`"footnote"`) and the EPUB 2 linked-anchor idiom (a `class="footnote"`/`"noteref"` convention with no `epub:type` vocabulary at all, recognised by `src/xhtml/footnote.ts`) map onto the identical `anchor` construct — a run-level point extent at the reference site, a `constructStart`/blocks/`constructEnd` triple around the body. Recognition never crosses a spine itemref boundary: `document-schema.js`'s own construct-marker contract states a bracket pair can never straddle a block list boundary, and each `ContentSection` is its own block list, so a cross-document footnote (a separate "notes.xhtml", the more common real-world EPUB 2 shape) falls through to the ordinary internal-hyperlink handling above instead of being force-fit into a shape the schema cannot express.
142
+ - **Internal-link semantics** (ExaDev/documents.js#963) a same-document fragment href, or a cross-document href naming another spine document's own id, that resolves to a real, block-level element anywhere in the spine builds `document-schema.js`'s own internal-target `link` construct (a run-level extent, `target: { kind: "internal", anchor }`) rather than riding `ContentRun.hyperlink` as a plain string; the target element itself is wrapped in its own `anchor` construct (`anchorType: "bookmark"`) so the two ends of the link are both addressable. A cross-document target's own name is qualified with its owning document's own path (`"OEBPS/chapter2.xhtml#sec2"`, never a bare `"sec2"`) precisely because two different target documents can otherwise legitimately reuse the identical id; a same-document target keeps the bare fragment. `src/write.ts`'s own whole-document anchor-section index resolves a reference back to either a same-file `"#name"` href or a cross-file `"sectionN.xhtml#name"` one on write, so this round-trips through a same-format write. `epub/link-target-external-only` remains the diagnostic for what's left: an href genuinely external (a URI scheme), or an internal-looking href this package cannot resolve to a real element at all (a nonexistent fragment, or one with no fragment naming only a whole document) — that still rides `ContentRun.hyperlink` verbatim, restoring byte-for-byte either way.
143
+ - **Footnotes recognise a cross-document body** (ExaDev/documents.js#963). Both the EPUB 3 structured idiom (`epub:type="noteref"`/`"footnote"`) and the EPUB 2 linked-anchor idiom (a `class="footnote"`/`"noteref"` convention with no `epub:type` vocabulary at all, recognised by `src/xhtml/footnote.ts`'s `isFootnoteReference`) map onto the identical `anchor` construct — a run-level point extent at the reference site, a `constructStart`/blocks/`constructEnd` triple around the body. A reference and its own body need not live in the same spine document: `document-schema.js`'s own construct-marker contract still forbids a single bracket pair from straddling a block-list boundary (each `ContentSection` is its own block list), but this was never that — the reference's own point extent and the body's own `constructStart`/`constructEnd` pair are two independently well-formed markers in two different sections, connected only by sharing one `name`. `src/read.ts`'s own whole-spine pass resolves every href (same- and cross-document) before any section is read, so a cross-document footnote (a separate "notes.xhtml", the more common real-world EPUB 2 shape) is recognised and linked exactly like a same-document one; a cross-document name is qualified with its owning document's own path (`"OEBPS/notes.xhtml#note1"`) to keep it distinct from a same-named footnote in a different document, and `src/write.ts`'s own whole-document anchor-section index resolves the reference back to a working same-file or cross-file href on every write.
132
144
  - **`epub/style-residue`** — a document's own `<head>` style declarations (`<link rel="stylesheet">`, `<style>`) are quarantined verbatim as `SourceResidue` on the owning `ContentSection`, never interpreted: CSS is residue, not content. `writeEpubContent` re-emits it into the written `<head>` on a same-format write (this family's standard restorable-fidelity re-emission contract).
133
145
  - **An empty or whitespace-only paragraph (`<p></p>`, `<p> </p>`, or bare whitespace text between two block-level siblings — the common case in any pretty-printed real EPUB) is dropped entirely on read**, matching the same "anonymous block box" rule a browser's own HTML block-formatting context already applies to inter-block whitespace, rather than becoming a bogus empty `ContentParagraph`.
134
146
  - **List marker type (bullet vs. ordered, and an `<ol>`'s own non-default `start`) is packed into the opaque `numId`** (`epub{N}:{bullet|ordered}[@{start}]`, `src/xhtml/list-id.ts`), since `ContentListMembership` carries no field of its own for it — the identical mechanism `markdown-codec`'s own `src/shared/list-id.ts` uses for its GFM bullet/ordered distinction, hand-mirrored rather than shared. A numId outside this grammar (odf.js's bare `"list1"`, markdown-codec's own `"md1:bullet"`) is read back as an ordinary bullet list with no declared start.
@@ -145,11 +157,13 @@ Every construct this package's XHTML mapping cannot represent losslessly is a do
145
157
 
146
158
  ## Fidelity
147
159
 
148
- **Semantic fidelity** — headings, paragraphs, lists (nested, bullet/ordered), definition lists, tables, images, hyperlinks, text styling (bold/italic/underline/strike/monospace), blockquotes, pre/code blocks, horizontal rules, figure/figcaption, and footnotes (both EPUB 2 and EPUB 3 idioms) all survive as first-class `document-schema.js` nodes or constructs — see [ExaDev/documents.js#801](https://github.com/ExaDev/documents.js/issues/801) for the full acceptance list this package was built against, and `src/roundtrip.test.ts` for the real end-to-end proof (a hand-built `ContentDocument` covering nearly all of it, written to a genuine EPUB 3 zip and read back unchanged).
160
+ **Semantic fidelity** — headings, paragraphs, lists (nested, bullet/ordered), definition lists, tables, images, hyperlinks (external and internal, same- or cross-document — ExaDev/documents.js#963), text styling (bold/italic/underline/strike/monospace), blockquotes, pre/code blocks, horizontal rules, figure/figcaption, and footnotes (both EPUB 2 and EPUB 3 idioms, same- or cross-document — ExaDev/documents.js#963) all survive as first-class `document-schema.js` nodes or constructs — see [ExaDev/documents.js#801](https://github.com/ExaDev/documents.js/issues/801) for the full acceptance list this package was built against, and `src/roundtrip.test.ts` for the real end-to-end proof (a hand-built `ContentDocument` covering nearly all of it, written to a genuine EPUB 3 zip and read back unchanged).
161
+
162
+ **Restorable fidelity** — a same-format (EPUB-to-EPUB) round trip re-emits quarantined CSS residue verbatim, and re-resolves an internal link/bookmark or footnote reference/body pair to a working same-file or cross-file href on every write (ExaDev/documents.js#963); the one remaining documented gap above (sub/sup styling) is a permanent, structural limit rather than a restorable one, since the source construct itself has nowhere in the schema to ride.
149
163
 
150
- **Restorable fidelity** a same-format (EPUB-to-EPUB) round trip re-emits quarantined CSS residue verbatim; every other documented gap above (sub/sup styling, internal-link semantics, cross-document footnotes) is a permanent, structural limit rather than a restorable one, since the source construct itself has nowhere in the schema to ride.
164
+ **Byte fidelity, at the Package level** (ExaDev/documents.js#963). This package now has the same lossless byte-level `Package` model `ooxml.js`/`odf.js` do — a `decodePackage`/`encodePackage` pair, mirroring theirs exactly, that reads every OCF zip entry into a generic `Part` (an XML entry as an ordered `XmlNode[]` forest, anything else as raw base64 bytes) and writes it straight back, with no EPUB-specific interpretation in between. `decodePackage(bytes) |> encodePackage` is a genuine fixed point: `src/package-round-trip.test.ts` pins `decode -> encode -> decode` idempotence, per-part content preservation (an XML part's own entities/structure, a binary part's own bytes verbatim), and the one deliberate departure from a generic zip-of-XML writer this format shares with ODF's identical requirement — the `mimetype` part hoisted first and stored uncompressed (EPUB 3.3 §6.3), regardless of the input zip's own entry order, and never fabricated if the input never carried one. `readEpub(Content)`/`writeEpubContent` cross this identical boundary internally (`decodePackage` first, `encodePackage` last) rather than talking to the zip layer directly, so the two are the same lossless core, not two independent implementations that happen to agree.
151
165
 
152
- **Not byte fidelity.** This package has no lossless byte-level `Package` model the way `ooxml.js`/`odf.js` do there is no `decodePackage`/`encodePackage` pair, and a read-then-write round trip never reproduces the original bytes (a fresh `dc:identifier` is minted on every write, per this package's own explicit write scope). What _is_ deterministic: two writes of the _same_ `ContentDocument` produce byte-identical zip _layout_ (mimetype-first, stored, ordered entries, fixed mtimes) even though the OPF entry's own compressed bytes differ (the fresh identifier). `src/roundtrip.test.ts` pins the layout invariant directly rather than claiming full byte determinism.
166
+ **Not byte fidelity at the ContentDocument level.** The `ContentDocument`/`DocumentTree` mapping stays a lossy projection on top of that lossless core the same "lossless core vs. lossy views" boundary `ooxml.js`'s own README states for its identical `decodePackage`/`readDocx` split: `writeEpubContent` always builds a _fresh_ `EpubPackage` from the content it's given rather than touching one `readEpubContent` might have decoded, so a read-then-write round trip through the content level never reproduces the original bytes (a fresh `dc:identifier` is minted on every write, per this package's own explicit write scope, among other differences a lossy projection can't avoid). What _is_ deterministic at this level: two writes of the _same_ `ContentDocument` produce byte-identical zip _layout_ (mimetype-first, stored, ordered entries, fixed mtimes) even though the OPF entry's own compressed bytes differ (the fresh identifier). `src/roundtrip.test.ts` pins the layout invariant directly rather than claiming full byte determinism at this level.
153
167
 
154
168
  ## Build, test, and lint
155
169
 
@@ -177,7 +191,7 @@ Conventional Commits, enforced workspace-wide by commitlint through a root `comm
177
191
 
178
192
  - [document-schema.js](../document-schema.js/README.md) — the canonical `ContentDocument`/`DocumentTree` schema and the `decompose`/`flattenTree`/`assembleTree` transform this package's writer is built on.
179
193
  - [markdown-codec](../markdown-codec/README.md) — the closest architectural relative: hand-written, AST-to-`ContentDocument` lowering, the identical dual-level (`DocumentTree`/`ContentDocument`) API, the identical three-tier diagnostic policy, and the identical list-`numId`-packing mechanism for the identical schema gap.
180
- - [ooxml.js](../ooxml.js/README.md) / [odf.js](../odf.js/README.md) — the OCF/ZIP and lossless-XML-layer conventions this package's own `src/zip.ts`/`src/xml/` mirror.
194
+ - [ooxml.js](../ooxml.js/README.md) / [odf.js](../odf.js/README.md) — the OCF/ZIP, lossless-XML-layer, and byte-level `Package` model (`decodePackage`/`encodePackage`, ExaDev/documents.js#963) conventions this package's own `src/zip.ts`/`src/xml/`/`src/model/package.ts`/`src/package-io/` mirror; `odf.js`'s in particular, since ODF shares EPUB's identical OCF mimetype-first-stored requirement.
181
195
  - [archive-codec](../archive-codec/README.md) — the ZIP-in-ZIP/CFB utility package this package deliberately does not depend on; see [Dependency choices](#dependency-choices).
182
196
  - [byte-codec](../byte-codec/README.md) — the byte/image utility package this package deliberately does not depend on for image dimensions; see [Dependency choices](#dependency-choices).
183
197
  - [EPUB 3.3](https://www.w3.org/TR/epub-33/) — the current W3C Recommendation this package's own EPUB 3 reading and writing targets.
@@ -0,0 +1,5 @@
1
+ //#region src/util/base64.d.ts
2
+ declare function bytesToBase64(bytes: Uint8Array<ArrayBuffer>): string;
3
+ declare function base64ToBytes(b64: string): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { bytesToBase64 as n, base64ToBytes as t };
@@ -0,0 +1,5 @@
1
+ //#region src/util/base64.d.ts
2
+ declare function bytesToBase64(bytes: Uint8Array<ArrayBuffer>): string;
3
+ declare function base64ToBytes(b64: string): Uint8Array<ArrayBuffer>;
4
+ //#endregion
5
+ export { bytesToBase64 as n, base64ToBytes as t };
@@ -0,0 +1,5 @@
1
+ import { d as XmlNode } from "./node-CJLSSs1k.cjs";
2
+ //#region src/xml/build.d.ts
3
+ declare function buildXml(nodes: XmlNode[]): string;
4
+ //#endregion
5
+ export { buildXml as t };
@@ -0,0 +1,5 @@
1
+ import { d as XmlNode } from "./node-CJLSSs1k.js";
2
+ //#region src/xml/build.d.ts
3
+ declare function buildXml(nodes: XmlNode[]): string;
4
+ //#endregion
5
+ export { buildXml as t };