epub-codec 0.0.0 → 1.0.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -0
  3. package/dist/codec.cjs +28 -0
  4. package/dist/codec.d.cts +2308 -0
  5. package/dist/codec.d.ts +2308 -0
  6. package/dist/codec.js +25 -0
  7. package/dist/diagnostics-Ee4kG7mw.d.cts +60 -0
  8. package/dist/diagnostics-Ee4kG7mw.d.ts +60 -0
  9. package/dist/diagnostics.cjs +96 -0
  10. package/dist/diagnostics.d.cts +2 -0
  11. package/dist/diagnostics.d.ts +2 -0
  12. package/dist/diagnostics.js +85 -0
  13. package/dist/format.cjs +9 -0
  14. package/dist/format.d.cts +6 -0
  15. package/dist/format.d.ts +6 -0
  16. package/dist/format.js +6 -0
  17. package/dist/image/dimensions.cjs +82 -0
  18. package/dist/image/dimensions.d.cts +11 -0
  19. package/dist/image/dimensions.d.ts +11 -0
  20. package/dist/image/dimensions.js +79 -0
  21. package/dist/index.cjs +105 -0
  22. package/dist/index.d.cts +32 -0
  23. package/dist/index.d.ts +32 -0
  24. package/dist/index.js +32 -0
  25. package/dist/nav/nav3.cjs +19 -0
  26. package/dist/nav/nav3.d.cts +4 -0
  27. package/dist/nav/nav3.d.ts +4 -0
  28. package/dist/nav/nav3.js +18 -0
  29. package/dist/nav/ncx.cjs +17 -0
  30. package/dist/nav/ncx.d.cts +4 -0
  31. package/dist/nav/ncx.d.ts +4 -0
  32. package/dist/nav/ncx.js +16 -0
  33. package/dist/nav/reconcile.cjs +8 -0
  34. package/dist/nav/reconcile.d.cts +4 -0
  35. package/dist/nav/reconcile.d.ts +4 -0
  36. package/dist/nav/reconcile.js +7 -0
  37. package/dist/nav/write.cjs +39 -0
  38. package/dist/nav/write.d.cts +10 -0
  39. package/dist/nav/write.d.ts +10 -0
  40. package/dist/nav/write.js +37 -0
  41. package/dist/ocf/container.cjs +20 -0
  42. package/dist/ocf/container.d.cts +4 -0
  43. package/dist/ocf/container.d.ts +4 -0
  44. package/dist/ocf/container.js +19 -0
  45. package/dist/ocf/write.cjs +13 -0
  46. package/dist/ocf/write.d.cts +4 -0
  47. package/dist/ocf/write.d.ts +4 -0
  48. package/dist/ocf/write.js +12 -0
  49. package/dist/opf/metadata.cjs +51 -0
  50. package/dist/opf/metadata.d.cts +7 -0
  51. package/dist/opf/metadata.d.ts +7 -0
  52. package/dist/opf/metadata.js +50 -0
  53. package/dist/opf/parse.cjs +47 -0
  54. package/dist/opf/parse.d.cts +6 -0
  55. package/dist/opf/parse.d.ts +6 -0
  56. package/dist/opf/parse.js +46 -0
  57. package/dist/opf/types.cjs +0 -0
  58. package/dist/opf/types.d.cts +20 -0
  59. package/dist/opf/types.d.ts +20 -0
  60. package/dist/opf/types.js +0 -0
  61. package/dist/opf/write.cjs +55 -0
  62. package/dist/opf/write.d.cts +12 -0
  63. package/dist/opf/write.d.ts +12 -0
  64. package/dist/opf/write.js +54 -0
  65. package/dist/path.cjs +22 -0
  66. package/dist/path.d.cts +5 -0
  67. package/dist/path.d.ts +5 -0
  68. package/dist/path.js +20 -0
  69. package/dist/read.cjs +166 -0
  70. package/dist/read.d.cts +10 -0
  71. package/dist/read.d.ts +10 -0
  72. package/dist/read.js +164 -0
  73. package/dist/util/base64.cjs +51 -0
  74. package/dist/util/base64.d.cts +5 -0
  75. package/dist/util/base64.d.ts +5 -0
  76. package/dist/util/base64.js +49 -0
  77. package/dist/write.cjs +136 -0
  78. package/dist/write.d.cts +10 -0
  79. package/dist/write.d.ts +10 -0
  80. package/dist/write.js +134 -0
  81. package/dist/xhtml/context.cjs +0 -0
  82. package/dist/xhtml/context.d.cts +20 -0
  83. package/dist/xhtml/context.d.ts +20 -0
  84. package/dist/xhtml/context.js +0 -0
  85. package/dist/xhtml/footnote.cjs +32 -0
  86. package/dist/xhtml/footnote.d.cts +7 -0
  87. package/dist/xhtml/footnote.d.ts +7 -0
  88. package/dist/xhtml/footnote.js +29 -0
  89. package/dist/xhtml/inline.cjs +128 -0
  90. package/dist/xhtml/inline.d.cts +11 -0
  91. package/dist/xhtml/inline.d.ts +11 -0
  92. package/dist/xhtml/inline.js +127 -0
  93. package/dist/xhtml/list-id.cjs +22 -0
  94. package/dist/xhtml/list-id.d.cts +13 -0
  95. package/dist/xhtml/list-id.d.ts +13 -0
  96. package/dist/xhtml/list-id.js +20 -0
  97. package/dist/xhtml/read.cjs +423 -0
  98. package/dist/xhtml/read.d.cts +20 -0
  99. package/dist/xhtml/read.d.ts +20 -0
  100. package/dist/xhtml/read.js +422 -0
  101. package/dist/xhtml/style-constants.cjs +13 -0
  102. package/dist/xhtml/style-constants.d.cts +8 -0
  103. package/dist/xhtml/style-constants.d.ts +8 -0
  104. package/dist/xhtml/style-constants.js +8 -0
  105. package/dist/xhtml/write.cjs +183 -0
  106. package/dist/xhtml/write.d.cts +12 -0
  107. package/dist/xhtml/write.d.ts +12 -0
  108. package/dist/xhtml/write.js +182 -0
  109. package/dist/xml/build.cjs +46 -0
  110. package/dist/xml/build.d.cts +5 -0
  111. package/dist/xml/build.d.ts +5 -0
  112. package/dist/xml/build.js +46 -0
  113. package/dist/xml/entities.cjs +20 -0
  114. package/dist/xml/entities.d.cts +5 -0
  115. package/dist/xml/entities.d.ts +5 -0
  116. package/dist/xml/entities.js +18 -0
  117. package/dist/xml/node.cjs +67 -0
  118. package/dist/xml/node.d.cts +83 -0
  119. package/dist/xml/node.d.ts +83 -0
  120. package/dist/xml/node.js +58 -0
  121. package/dist/xml/parse.cjs +88 -0
  122. package/dist/xml/parse.d.cts +5 -0
  123. package/dist/xml/parse.d.ts +5 -0
  124. package/dist/xml/parse.js +88 -0
  125. package/dist/xml/query.cjs +44 -0
  126. package/dist/xml/query.d.cts +11 -0
  127. package/dist/xml/query.d.ts +11 -0
  128. package/dist/xml/query.js +37 -0
  129. package/dist/zip.cjs +19 -0
  130. package/dist/zip.d.cts +9 -0
  131. package/dist/zip.d.ts +9 -0
  132. package/dist/zip.js +17 -0
  133. package/package.json +95 -2
@@ -0,0 +1,46 @@
1
+ import { EpubInvalidOpfError, NOOP_EPUB_DIAGNOSTIC_SINK } from "../diagnostics.js";
2
+ import { attrValue, childrenWithTag, findChildElement, rootElement } from "../xml/query.js";
3
+ import { parseXml } from "../xml/parse.js";
4
+ import { readOpfMetadata } from "./metadata.js";
5
+ //#region src/opf/parse.ts
6
+ function parseOpf(opfXml, sink = NOOP_EPUB_DIAGNOSTIC_SINK) {
7
+ const nodes = parseXml(opfXml);
8
+ const root = rootElement(nodes);
9
+ if (root?.tag !== "package") throw new EpubInvalidOpfError("the OPF document has no <package> root element");
10
+ const metadataElement = findChildElement(root.children, "metadata");
11
+ const metadata = metadataElement === void 0 ? {} : readOpfMetadata(metadataElement, sink);
12
+ const manifestElement = findChildElement(root.children, "manifest");
13
+ if (manifestElement === void 0) throw new EpubInvalidOpfError("the OPF document has no <manifest> element");
14
+ const manifest = childrenWithTag(manifestElement, "item").map(parseManifestItem).filter((item) => item !== void 0);
15
+ const spineElement = findChildElement(root.children, "spine");
16
+ if (spineElement === void 0) throw new EpubInvalidOpfError("the OPF document has no <spine> element");
17
+ return {
18
+ metadata,
19
+ manifest,
20
+ spine: childrenWithTag(spineElement, "itemref").map(parseSpineItemRef).filter((item) => item !== void 0),
21
+ ncxId: attrValue(spineElement, "toc")
22
+ };
23
+ }
24
+ function parseManifestItem(element) {
25
+ const id = attrValue(element, "id");
26
+ const href = attrValue(element, "href");
27
+ const mediaType = attrValue(element, "media-type");
28
+ if (id === void 0 || href === void 0 || mediaType === void 0) return;
29
+ const properties = attrValue(element, "properties");
30
+ return {
31
+ id,
32
+ href,
33
+ mediaType,
34
+ properties: properties === void 0 ? [] : properties.split(/\s+/u).filter((p) => p.length > 0)
35
+ };
36
+ }
37
+ function parseSpineItemRef(element) {
38
+ const idref = attrValue(element, "idref");
39
+ if (idref === void 0) return;
40
+ return {
41
+ idref,
42
+ linear: attrValue(element, "linear") !== "no"
43
+ };
44
+ }
45
+ //#endregion
46
+ export { parseOpf };
File without changes
@@ -0,0 +1,20 @@
1
+ import { LayoutMetadata } from "document-schema.js";
2
+ //#region src/opf/types.d.ts
3
+ interface OpfManifestItem {
4
+ readonly id: string;
5
+ readonly href: string;
6
+ readonly mediaType: string;
7
+ readonly properties: readonly string[];
8
+ }
9
+ interface OpfSpineItemRef {
10
+ readonly idref: string;
11
+ readonly linear: boolean;
12
+ }
13
+ interface OpfPackage {
14
+ readonly metadata: LayoutMetadata;
15
+ readonly manifest: readonly OpfManifestItem[];
16
+ readonly spine: readonly OpfSpineItemRef[];
17
+ readonly ncxId: string | undefined;
18
+ }
19
+ //#endregion
20
+ export { OpfManifestItem, OpfPackage, OpfSpineItemRef };
@@ -0,0 +1,20 @@
1
+ import { LayoutMetadata } from "document-schema.js";
2
+ //#region src/opf/types.d.ts
3
+ interface OpfManifestItem {
4
+ readonly id: string;
5
+ readonly href: string;
6
+ readonly mediaType: string;
7
+ readonly properties: readonly string[];
8
+ }
9
+ interface OpfSpineItemRef {
10
+ readonly idref: string;
11
+ readonly linear: boolean;
12
+ }
13
+ interface OpfPackage {
14
+ readonly metadata: LayoutMetadata;
15
+ readonly manifest: readonly OpfManifestItem[];
16
+ readonly spine: readonly OpfSpineItemRef[];
17
+ readonly ncxId: string | undefined;
18
+ }
19
+ //#endregion
20
+ export { OpfManifestItem, OpfPackage, OpfSpineItemRef };
File without changes
@@ -0,0 +1,55 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_xml_entities = require("../xml/entities.cjs");
3
+ const require_xml_build = require("../xml/build.cjs");
4
+ //#region src/opf/write.ts
5
+ function element(tag, attrs = {}, children = []) {
6
+ return {
7
+ type: "element",
8
+ tag,
9
+ attributes: Object.entries(attrs).map(([name, value]) => ({
10
+ name,
11
+ value
12
+ })),
13
+ children
14
+ };
15
+ }
16
+ function text(value) {
17
+ return {
18
+ type: "text",
19
+ value: require_xml_entities.encodeEntities(value)
20
+ };
21
+ }
22
+ function dcElement(tag, value) {
23
+ return element(tag, {}, [text(value)]);
24
+ }
25
+ function writeOpf(input) {
26
+ const metadataChildren = [element("dc:identifier", { id: "pub-id" }, [text(input.identifier)])];
27
+ if (input.metadata.title !== void 0) metadataChildren.push(dcElement("dc:title", input.metadata.title));
28
+ if (input.metadata.author !== void 0) metadataChildren.push(dcElement("dc:creator", input.metadata.author));
29
+ for (const keyword of input.metadata.keywords ?? []) metadataChildren.push(dcElement("dc:subject", keyword));
30
+ metadataChildren.push(dcElement("dc:language", input.metadata.language ?? "en"));
31
+ if (input.metadata.createdIso !== void 0) metadataChildren.push(dcElement("dc:date", input.metadata.createdIso));
32
+ if (input.metadata.modifiedIso !== void 0) metadataChildren.push(element("meta", { property: "dcterms:modified" }, [text(input.metadata.modifiedIso)]));
33
+ const manifestChildren = input.manifestItems.map((item) => {
34
+ const attrs = {
35
+ id: item.id,
36
+ href: item.href,
37
+ "media-type": item.mediaType
38
+ };
39
+ if (item.properties.length > 0) attrs.properties = item.properties.join(" ");
40
+ return element("item", attrs);
41
+ });
42
+ const spineChildren = input.spineIdrefs.map((idref) => element("itemref", { idref }));
43
+ const packageElement = element("package", {
44
+ xmlns: "http://www.idpf.org/2007/opf",
45
+ version: "3.0",
46
+ "unique-identifier": "pub-id"
47
+ }, [
48
+ element("metadata", { "xmlns:dc": "http://purl.org/dc/elements/1.1/" }, metadataChildren),
49
+ element("manifest", {}, manifestChildren),
50
+ element("spine", {}, spineChildren)
51
+ ]);
52
+ return `<?xml version="1.0" encoding="UTF-8"?>\n${require_xml_build.buildXml([packageElement])}`;
53
+ }
54
+ //#endregion
55
+ exports.writeOpf = writeOpf;
@@ -0,0 +1,12 @@
1
+ import { OpfManifestItem } from "./types.cjs";
2
+ import { LayoutMetadata } from "document-schema.js";
3
+ //#region src/opf/write.d.ts
4
+ interface WriteOpfInput {
5
+ readonly metadata: LayoutMetadata;
6
+ readonly manifestItems: readonly OpfManifestItem[];
7
+ readonly spineIdrefs: readonly string[];
8
+ readonly identifier: string;
9
+ }
10
+ declare function writeOpf(input: WriteOpfInput): string;
11
+ //#endregion
12
+ export { WriteOpfInput, writeOpf };
@@ -0,0 +1,12 @@
1
+ import { OpfManifestItem } from "./types.js";
2
+ import { LayoutMetadata } from "document-schema.js";
3
+ //#region src/opf/write.d.ts
4
+ interface WriteOpfInput {
5
+ readonly metadata: LayoutMetadata;
6
+ readonly manifestItems: readonly OpfManifestItem[];
7
+ readonly spineIdrefs: readonly string[];
8
+ readonly identifier: string;
9
+ }
10
+ declare function writeOpf(input: WriteOpfInput): string;
11
+ //#endregion
12
+ export { WriteOpfInput, writeOpf };
@@ -0,0 +1,54 @@
1
+ import { encodeEntities } from "../xml/entities.js";
2
+ import { buildXml } from "../xml/build.js";
3
+ //#region src/opf/write.ts
4
+ function element(tag, attrs = {}, children = []) {
5
+ return {
6
+ type: "element",
7
+ tag,
8
+ attributes: Object.entries(attrs).map(([name, value]) => ({
9
+ name,
10
+ value
11
+ })),
12
+ children
13
+ };
14
+ }
15
+ function text(value) {
16
+ return {
17
+ type: "text",
18
+ value: encodeEntities(value)
19
+ };
20
+ }
21
+ function dcElement(tag, value) {
22
+ return element(tag, {}, [text(value)]);
23
+ }
24
+ function writeOpf(input) {
25
+ const metadataChildren = [element("dc:identifier", { id: "pub-id" }, [text(input.identifier)])];
26
+ if (input.metadata.title !== void 0) metadataChildren.push(dcElement("dc:title", input.metadata.title));
27
+ if (input.metadata.author !== void 0) metadataChildren.push(dcElement("dc:creator", input.metadata.author));
28
+ for (const keyword of input.metadata.keywords ?? []) metadataChildren.push(dcElement("dc:subject", keyword));
29
+ metadataChildren.push(dcElement("dc:language", input.metadata.language ?? "en"));
30
+ if (input.metadata.createdIso !== void 0) metadataChildren.push(dcElement("dc:date", input.metadata.createdIso));
31
+ if (input.metadata.modifiedIso !== void 0) metadataChildren.push(element("meta", { property: "dcterms:modified" }, [text(input.metadata.modifiedIso)]));
32
+ const manifestChildren = input.manifestItems.map((item) => {
33
+ const attrs = {
34
+ id: item.id,
35
+ href: item.href,
36
+ "media-type": item.mediaType
37
+ };
38
+ if (item.properties.length > 0) attrs.properties = item.properties.join(" ");
39
+ return element("item", attrs);
40
+ });
41
+ const spineChildren = input.spineIdrefs.map((idref) => element("itemref", { idref }));
42
+ const packageElement = element("package", {
43
+ xmlns: "http://www.idpf.org/2007/opf",
44
+ version: "3.0",
45
+ "unique-identifier": "pub-id"
46
+ }, [
47
+ element("metadata", { "xmlns:dc": "http://purl.org/dc/elements/1.1/" }, metadataChildren),
48
+ element("manifest", {}, manifestChildren),
49
+ element("spine", {}, spineChildren)
50
+ ]);
51
+ return `<?xml version="1.0" encoding="UTF-8"?>\n${buildXml([packageElement])}`;
52
+ }
53
+ //#endregion
54
+ export { writeOpf };
package/dist/path.cjs ADDED
@@ -0,0 +1,22 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/path.ts
3
+ function dirname(path) {
4
+ const slash = path.lastIndexOf("/");
5
+ return slash === -1 ? "" : path.slice(0, slash);
6
+ }
7
+ function resolvePackagePath(baseDir, relative) {
8
+ if (relative.startsWith("/") || /^[a-z][a-z0-9+.-]*:/iu.test(relative)) return relative;
9
+ const segments = [...baseDir.length > 0 ? baseDir.split("/") : []];
10
+ for (const part of relative.split("/")) {
11
+ if (part === "" || part === ".") continue;
12
+ if (part === "..") {
13
+ segments.pop();
14
+ continue;
15
+ }
16
+ segments.push(part);
17
+ }
18
+ return segments.join("/");
19
+ }
20
+ //#endregion
21
+ exports.dirname = dirname;
22
+ exports.resolvePackagePath = resolvePackagePath;
@@ -0,0 +1,5 @@
1
+ //#region src/path.d.ts
2
+ declare function dirname(path: string): string;
3
+ declare function resolvePackagePath(baseDir: string, relative: string): string;
4
+ //#endregion
5
+ export { dirname, resolvePackagePath };
package/dist/path.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ //#region src/path.d.ts
2
+ declare function dirname(path: string): string;
3
+ declare function resolvePackagePath(baseDir: string, relative: string): string;
4
+ //#endregion
5
+ export { dirname, resolvePackagePath };
package/dist/path.js ADDED
@@ -0,0 +1,20 @@
1
+ //#region src/path.ts
2
+ function dirname(path) {
3
+ const slash = path.lastIndexOf("/");
4
+ return slash === -1 ? "" : path.slice(0, slash);
5
+ }
6
+ function resolvePackagePath(baseDir, relative) {
7
+ if (relative.startsWith("/") || /^[a-z][a-z0-9+.-]*:/iu.test(relative)) return relative;
8
+ const segments = [...baseDir.length > 0 ? baseDir.split("/") : []];
9
+ for (const part of relative.split("/")) {
10
+ if (part === "" || part === ".") continue;
11
+ if (part === "..") {
12
+ segments.pop();
13
+ continue;
14
+ }
15
+ segments.push(part);
16
+ }
17
+ return segments.join("/");
18
+ }
19
+ //#endregion
20
+ export { dirname, resolvePackagePath };
package/dist/read.cjs ADDED
@@ -0,0 +1,166 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_diagnostics = require("./diagnostics.cjs");
3
+ const require_ocf_container = require("./ocf/container.cjs");
4
+ require("./format.cjs");
5
+ const require_nav_nav3 = require("./nav/nav3.cjs");
6
+ const require_nav_ncx = require("./nav/ncx.cjs");
7
+ const require_nav_reconcile = require("./nav/reconcile.cjs");
8
+ const require_opf_parse = require("./opf/parse.cjs");
9
+ const require_xhtml_read = require("./xhtml/read.cjs");
10
+ const require_path = require("./path.cjs");
11
+ const require_zip = require("./zip.cjs");
12
+ let document_schema_js = require("document-schema.js");
13
+ //#region src/read.ts
14
+ const A4_MARGINS = {
15
+ topPt: 72,
16
+ rightPt: 72,
17
+ bottomPt: 72,
18
+ leftPt: 72
19
+ };
20
+ const CONTENT_WIDTH_PT = document_schema_js.PAGE_SIZE_A4.widthPt - A4_MARGINS.leftPt - A4_MARGINS.rightPt;
21
+ function decodeText(bytes) {
22
+ return new TextDecoder("utf-8").decode(bytes);
23
+ }
24
+ function readEpubInternal(bytes, sink) {
25
+ const entries = require_zip.unzipPackage(bytes);
26
+ const mimetypeBytes = entries.mimetype;
27
+ if (mimetypeBytes === void 0 || decodeText(mimetypeBytes) !== "application/epub+zip") throw new require_diagnostics.EpubInvalidMimetypeError();
28
+ const containerBytes = entries["META-INF/container.xml"];
29
+ if (containerBytes === void 0) throw new require_diagnostics.EpubInvalidContainerError("the zip carries no META-INF/container.xml entry");
30
+ const opfPath = require_ocf_container.resolveOpfPath(decodeText(containerBytes));
31
+ const opfBytes = entries[opfPath];
32
+ if (opfBytes === void 0) throw new require_diagnostics.EpubInvalidOpfError(`META-INF/container.xml names an OPF rootfile ("${opfPath}") the zip does not contain`);
33
+ const opfDir = require_path.dirname(opfPath);
34
+ const opf = require_opf_parse.parseOpf(decodeText(opfBytes), sink);
35
+ const manifestById = new Map(opf.manifest.map((item) => [item.id, item]));
36
+ const sections = [];
37
+ const spineFullPaths = [];
38
+ for (const itemref of opf.spine) {
39
+ const manifestItem = manifestById.get(itemref.idref);
40
+ if (manifestItem === void 0) {
41
+ sink({
42
+ code: require_diagnostics.EpubDiagnosticCodes.SPINE_ITEMREF_UNRESOLVED,
43
+ severity: "warning",
44
+ message: `spine itemref "${itemref.idref}" names no manifest item; skipped`
45
+ });
46
+ continue;
47
+ }
48
+ const fullPath = require_path.resolvePackagePath(opfDir, manifestItem.href);
49
+ spineFullPaths.push(fullPath);
50
+ const xhtmlBytes = entries[fullPath];
51
+ if (xhtmlBytes === void 0) {
52
+ sink({
53
+ code: require_diagnostics.EpubDiagnosticCodes.MANIFEST_ITEM_MISSING,
54
+ severity: "warning",
55
+ message: `manifest item "${manifestItem.id}" names a part ("${fullPath}") the zip does not contain; skipped`,
56
+ href: fullPath
57
+ });
58
+ continue;
59
+ }
60
+ const sectionDir = require_path.dirname(fullPath);
61
+ const { blocks, source } = require_xhtml_read.readXhtmlBody(decodeText(xhtmlBytes), {
62
+ resolveImage: (src) => entries[require_path.resolvePackagePath(sectionDir, src)],
63
+ sink,
64
+ sourceHref: fullPath,
65
+ contentWidthPt: CONTENT_WIDTH_PT
66
+ });
67
+ sections.push({
68
+ pageSize: document_schema_js.PAGE_SIZE_A4,
69
+ margins: A4_MARGINS,
70
+ blocks,
71
+ ...source !== void 0 ? { source } : {}
72
+ });
73
+ }
74
+ if (sections.length === 0) throw new require_diagnostics.EpubEmptySpineError();
75
+ sink({
76
+ code: require_diagnostics.EpubDiagnosticCodes.INVENTED_PAGE_GEOMETRY,
77
+ severity: "info",
78
+ message: "EPUB has no page concept of its own; every section was given A4 + 1in default page geometry"
79
+ });
80
+ const navResidue = reconcileNavigation(opf, entries, opfDir, spineFullPaths, sink);
81
+ return {
82
+ document: {
83
+ kind: "wordprocessing",
84
+ metadata: opf.metadata,
85
+ sections
86
+ },
87
+ navResidue
88
+ };
89
+ }
90
+ function reconcileNavigation(opf, entries, opfDir, spineFullPaths, sink) {
91
+ const navItem = opf.manifest.find((item) => item.properties.includes("nav"));
92
+ if (navItem !== void 0) {
93
+ const navPath = require_path.resolvePackagePath(opfDir, navItem.href);
94
+ const navBytes = entries[navPath];
95
+ if (navBytes !== void 0) {
96
+ const navXml = decodeText(navBytes);
97
+ const navHrefs = require_nav_nav3.readNav3TocHrefs(navXml);
98
+ if (navHrefs === void 0) sink({
99
+ code: require_diagnostics.EpubDiagnosticCodes.NAV_DOCUMENT_MISSING,
100
+ severity: "warning",
101
+ message: `the nav document ("${navPath}") carries no <nav epub:type="toc">`,
102
+ href: navPath
103
+ });
104
+ else {
105
+ const resolvedNavHrefs = navHrefs.map((href) => require_path.resolvePackagePath(require_path.dirname(navPath), href));
106
+ if (!require_nav_reconcile.navMatchesSpine(resolvedNavHrefs, spineFullPaths)) {
107
+ sink({
108
+ code: require_diagnostics.EpubDiagnosticCodes.NAV_SPINE_ORDER_MISMATCH,
109
+ severity: "warning",
110
+ message: "the EPUB 3 navigation document's own toc order disagrees with the spine; the spine's reading order wins and the nav document is quarantined as residue",
111
+ href: navPath
112
+ });
113
+ return {
114
+ format: "epub",
115
+ xml: navXml
116
+ };
117
+ }
118
+ }
119
+ }
120
+ }
121
+ if (opf.ncxId !== void 0) {
122
+ const ncxItem = opf.manifest.find((item) => item.id === opf.ncxId);
123
+ const ncxPath = ncxItem === void 0 ? void 0 : require_path.resolvePackagePath(opfDir, ncxItem.href);
124
+ const ncxBytes = ncxPath === void 0 ? void 0 : entries[ncxPath];
125
+ if (ncxBytes === void 0) {
126
+ sink({
127
+ code: require_diagnostics.EpubDiagnosticCodes.NCX_MISSING,
128
+ severity: "warning",
129
+ message: `the spine names an NCX ("${opf.ncxId}") the manifest does not resolve to a real part`
130
+ });
131
+ return;
132
+ }
133
+ const ncxXml = decodeText(ncxBytes);
134
+ const ncxHrefs = require_nav_ncx.readNcxHrefs(ncxXml);
135
+ if (ncxHrefs !== void 0) {
136
+ const resolvedNcxHrefs = ncxHrefs.map((href) => require_path.resolvePackagePath(opfDir, href));
137
+ if (!require_nav_reconcile.navMatchesSpine(resolvedNcxHrefs, spineFullPaths)) {
138
+ sink({
139
+ code: require_diagnostics.EpubDiagnosticCodes.NAV_SPINE_ORDER_MISMATCH,
140
+ severity: "warning",
141
+ message: "the EPUB 2 NCX's own navMap order disagrees with the spine; the spine's reading order wins and the NCX is quarantined as residue",
142
+ href: ncxPath
143
+ });
144
+ return {
145
+ format: "epub",
146
+ xml: ncxXml
147
+ };
148
+ }
149
+ }
150
+ }
151
+ }
152
+ function readEpubContent(bytes, options = {}) {
153
+ const { document } = readEpubInternal(bytes, options.sink ?? require_diagnostics.NOOP_EPUB_DIAGNOSTIC_SINK);
154
+ return document;
155
+ }
156
+ function readEpub(bytes, options = {}) {
157
+ const { document, navResidue } = readEpubInternal(bytes, options.sink ?? require_diagnostics.NOOP_EPUB_DIAGNOSTIC_SINK);
158
+ const tree = (0, document_schema_js.assembleTree)(document);
159
+ return navResidue === void 0 ? tree : {
160
+ ...tree,
161
+ source: { nav: navResidue }
162
+ };
163
+ }
164
+ //#endregion
165
+ exports.readEpub = readEpub;
166
+ exports.readEpubContent = readEpubContent;
@@ -0,0 +1,10 @@
1
+ import { i as EpubDiagnosticSink } from "./diagnostics-Ee4kG7mw.cjs";
2
+ import { ContentDocument, DocumentTree } from "document-schema.js";
3
+ //#region src/read.d.ts
4
+ interface ReadEpubOptions {
5
+ readonly sink?: EpubDiagnosticSink;
6
+ }
7
+ declare function readEpubContent(bytes: Uint8Array<ArrayBuffer>, options?: ReadEpubOptions): ContentDocument;
8
+ declare function readEpub(bytes: Uint8Array<ArrayBuffer>, options?: ReadEpubOptions): DocumentTree;
9
+ //#endregion
10
+ export { ReadEpubOptions, readEpub, readEpubContent };
package/dist/read.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { i as EpubDiagnosticSink } from "./diagnostics-Ee4kG7mw.js";
2
+ import { ContentDocument, DocumentTree } from "document-schema.js";
3
+ //#region src/read.d.ts
4
+ interface ReadEpubOptions {
5
+ readonly sink?: EpubDiagnosticSink;
6
+ }
7
+ declare function readEpubContent(bytes: Uint8Array<ArrayBuffer>, options?: ReadEpubOptions): ContentDocument;
8
+ declare function readEpub(bytes: Uint8Array<ArrayBuffer>, options?: ReadEpubOptions): DocumentTree;
9
+ //#endregion
10
+ export { ReadEpubOptions, readEpub, readEpubContent };
package/dist/read.js ADDED
@@ -0,0 +1,164 @@
1
+ import { EpubDiagnosticCodes, EpubEmptySpineError, EpubInvalidContainerError, EpubInvalidMimetypeError, EpubInvalidOpfError, NOOP_EPUB_DIAGNOSTIC_SINK } from "./diagnostics.js";
2
+ import { resolveOpfPath } from "./ocf/container.js";
3
+ import "./format.js";
4
+ import { readNav3TocHrefs } from "./nav/nav3.js";
5
+ import { readNcxHrefs } from "./nav/ncx.js";
6
+ import { navMatchesSpine } from "./nav/reconcile.js";
7
+ import { parseOpf } from "./opf/parse.js";
8
+ import { readXhtmlBody } from "./xhtml/read.js";
9
+ import { dirname, resolvePackagePath } from "./path.js";
10
+ import { unzipPackage } from "./zip.js";
11
+ import { PAGE_SIZE_A4, assembleTree } from "document-schema.js";
12
+ //#region src/read.ts
13
+ const A4_MARGINS = {
14
+ topPt: 72,
15
+ rightPt: 72,
16
+ bottomPt: 72,
17
+ leftPt: 72
18
+ };
19
+ const CONTENT_WIDTH_PT = PAGE_SIZE_A4.widthPt - A4_MARGINS.leftPt - A4_MARGINS.rightPt;
20
+ function decodeText(bytes) {
21
+ return new TextDecoder("utf-8").decode(bytes);
22
+ }
23
+ function readEpubInternal(bytes, sink) {
24
+ const entries = unzipPackage(bytes);
25
+ const mimetypeBytes = entries.mimetype;
26
+ if (mimetypeBytes === void 0 || decodeText(mimetypeBytes) !== "application/epub+zip") throw new EpubInvalidMimetypeError();
27
+ const containerBytes = entries["META-INF/container.xml"];
28
+ if (containerBytes === void 0) throw new EpubInvalidContainerError("the zip carries no META-INF/container.xml entry");
29
+ const opfPath = resolveOpfPath(decodeText(containerBytes));
30
+ const opfBytes = entries[opfPath];
31
+ if (opfBytes === void 0) throw new EpubInvalidOpfError(`META-INF/container.xml names an OPF rootfile ("${opfPath}") the zip does not contain`);
32
+ const opfDir = dirname(opfPath);
33
+ const opf = parseOpf(decodeText(opfBytes), sink);
34
+ const manifestById = new Map(opf.manifest.map((item) => [item.id, item]));
35
+ const sections = [];
36
+ const spineFullPaths = [];
37
+ for (const itemref of opf.spine) {
38
+ const manifestItem = manifestById.get(itemref.idref);
39
+ if (manifestItem === void 0) {
40
+ sink({
41
+ code: EpubDiagnosticCodes.SPINE_ITEMREF_UNRESOLVED,
42
+ severity: "warning",
43
+ message: `spine itemref "${itemref.idref}" names no manifest item; skipped`
44
+ });
45
+ continue;
46
+ }
47
+ const fullPath = resolvePackagePath(opfDir, manifestItem.href);
48
+ spineFullPaths.push(fullPath);
49
+ const xhtmlBytes = entries[fullPath];
50
+ if (xhtmlBytes === void 0) {
51
+ sink({
52
+ code: EpubDiagnosticCodes.MANIFEST_ITEM_MISSING,
53
+ severity: "warning",
54
+ message: `manifest item "${manifestItem.id}" names a part ("${fullPath}") the zip does not contain; skipped`,
55
+ href: fullPath
56
+ });
57
+ continue;
58
+ }
59
+ const sectionDir = dirname(fullPath);
60
+ const { blocks, source } = readXhtmlBody(decodeText(xhtmlBytes), {
61
+ resolveImage: (src) => entries[resolvePackagePath(sectionDir, src)],
62
+ sink,
63
+ sourceHref: fullPath,
64
+ contentWidthPt: CONTENT_WIDTH_PT
65
+ });
66
+ sections.push({
67
+ pageSize: PAGE_SIZE_A4,
68
+ margins: A4_MARGINS,
69
+ blocks,
70
+ ...source !== void 0 ? { source } : {}
71
+ });
72
+ }
73
+ if (sections.length === 0) throw new EpubEmptySpineError();
74
+ sink({
75
+ code: EpubDiagnosticCodes.INVENTED_PAGE_GEOMETRY,
76
+ severity: "info",
77
+ message: "EPUB has no page concept of its own; every section was given A4 + 1in default page geometry"
78
+ });
79
+ const navResidue = reconcileNavigation(opf, entries, opfDir, spineFullPaths, sink);
80
+ return {
81
+ document: {
82
+ kind: "wordprocessing",
83
+ metadata: opf.metadata,
84
+ sections
85
+ },
86
+ navResidue
87
+ };
88
+ }
89
+ function reconcileNavigation(opf, entries, opfDir, spineFullPaths, sink) {
90
+ const navItem = opf.manifest.find((item) => item.properties.includes("nav"));
91
+ if (navItem !== void 0) {
92
+ const navPath = resolvePackagePath(opfDir, navItem.href);
93
+ const navBytes = entries[navPath];
94
+ if (navBytes !== void 0) {
95
+ const navXml = decodeText(navBytes);
96
+ const navHrefs = readNav3TocHrefs(navXml);
97
+ if (navHrefs === void 0) sink({
98
+ code: EpubDiagnosticCodes.NAV_DOCUMENT_MISSING,
99
+ severity: "warning",
100
+ message: `the nav document ("${navPath}") carries no <nav epub:type="toc">`,
101
+ href: navPath
102
+ });
103
+ else {
104
+ const resolvedNavHrefs = navHrefs.map((href) => resolvePackagePath(dirname(navPath), href));
105
+ if (!navMatchesSpine(resolvedNavHrefs, spineFullPaths)) {
106
+ sink({
107
+ code: EpubDiagnosticCodes.NAV_SPINE_ORDER_MISMATCH,
108
+ severity: "warning",
109
+ message: "the EPUB 3 navigation document's own toc order disagrees with the spine; the spine's reading order wins and the nav document is quarantined as residue",
110
+ href: navPath
111
+ });
112
+ return {
113
+ format: "epub",
114
+ xml: navXml
115
+ };
116
+ }
117
+ }
118
+ }
119
+ }
120
+ if (opf.ncxId !== void 0) {
121
+ const ncxItem = opf.manifest.find((item) => item.id === opf.ncxId);
122
+ const ncxPath = ncxItem === void 0 ? void 0 : resolvePackagePath(opfDir, ncxItem.href);
123
+ const ncxBytes = ncxPath === void 0 ? void 0 : entries[ncxPath];
124
+ if (ncxBytes === void 0) {
125
+ sink({
126
+ code: EpubDiagnosticCodes.NCX_MISSING,
127
+ severity: "warning",
128
+ message: `the spine names an NCX ("${opf.ncxId}") the manifest does not resolve to a real part`
129
+ });
130
+ return;
131
+ }
132
+ const ncxXml = decodeText(ncxBytes);
133
+ const ncxHrefs = readNcxHrefs(ncxXml);
134
+ if (ncxHrefs !== void 0) {
135
+ const resolvedNcxHrefs = ncxHrefs.map((href) => resolvePackagePath(opfDir, href));
136
+ if (!navMatchesSpine(resolvedNcxHrefs, spineFullPaths)) {
137
+ sink({
138
+ code: EpubDiagnosticCodes.NAV_SPINE_ORDER_MISMATCH,
139
+ severity: "warning",
140
+ message: "the EPUB 2 NCX's own navMap order disagrees with the spine; the spine's reading order wins and the NCX is quarantined as residue",
141
+ href: ncxPath
142
+ });
143
+ return {
144
+ format: "epub",
145
+ xml: ncxXml
146
+ };
147
+ }
148
+ }
149
+ }
150
+ }
151
+ function readEpubContent(bytes, options = {}) {
152
+ const { document } = readEpubInternal(bytes, options.sink ?? NOOP_EPUB_DIAGNOSTIC_SINK);
153
+ return document;
154
+ }
155
+ function readEpub(bytes, options = {}) {
156
+ const { document, navResidue } = readEpubInternal(bytes, options.sink ?? NOOP_EPUB_DIAGNOSTIC_SINK);
157
+ const tree = assembleTree(document);
158
+ return navResidue === void 0 ? tree : {
159
+ ...tree,
160
+ source: { nav: navResidue }
161
+ };
162
+ }
163
+ //#endregion
164
+ export { readEpub, readEpubContent };