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.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/codec.cjs +28 -0
- package/dist/codec.d.cts +2308 -0
- package/dist/codec.d.ts +2308 -0
- package/dist/codec.js +25 -0
- package/dist/diagnostics-Ee4kG7mw.d.cts +60 -0
- package/dist/diagnostics-Ee4kG7mw.d.ts +60 -0
- package/dist/diagnostics.cjs +96 -0
- package/dist/diagnostics.d.cts +2 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +85 -0
- package/dist/format.cjs +9 -0
- package/dist/format.d.cts +6 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.js +6 -0
- package/dist/image/dimensions.cjs +82 -0
- package/dist/image/dimensions.d.cts +11 -0
- package/dist/image/dimensions.d.ts +11 -0
- package/dist/image/dimensions.js +79 -0
- package/dist/index.cjs +105 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/nav/nav3.cjs +19 -0
- package/dist/nav/nav3.d.cts +4 -0
- package/dist/nav/nav3.d.ts +4 -0
- package/dist/nav/nav3.js +18 -0
- package/dist/nav/ncx.cjs +17 -0
- package/dist/nav/ncx.d.cts +4 -0
- package/dist/nav/ncx.d.ts +4 -0
- package/dist/nav/ncx.js +16 -0
- package/dist/nav/reconcile.cjs +8 -0
- package/dist/nav/reconcile.d.cts +4 -0
- package/dist/nav/reconcile.d.ts +4 -0
- package/dist/nav/reconcile.js +7 -0
- package/dist/nav/write.cjs +39 -0
- package/dist/nav/write.d.cts +10 -0
- package/dist/nav/write.d.ts +10 -0
- package/dist/nav/write.js +37 -0
- package/dist/ocf/container.cjs +20 -0
- package/dist/ocf/container.d.cts +4 -0
- package/dist/ocf/container.d.ts +4 -0
- package/dist/ocf/container.js +19 -0
- package/dist/ocf/write.cjs +13 -0
- package/dist/ocf/write.d.cts +4 -0
- package/dist/ocf/write.d.ts +4 -0
- package/dist/ocf/write.js +12 -0
- package/dist/opf/metadata.cjs +51 -0
- package/dist/opf/metadata.d.cts +7 -0
- package/dist/opf/metadata.d.ts +7 -0
- package/dist/opf/metadata.js +50 -0
- package/dist/opf/parse.cjs +47 -0
- package/dist/opf/parse.d.cts +6 -0
- package/dist/opf/parse.d.ts +6 -0
- package/dist/opf/parse.js +46 -0
- package/dist/opf/types.cjs +0 -0
- package/dist/opf/types.d.cts +20 -0
- package/dist/opf/types.d.ts +20 -0
- package/dist/opf/types.js +0 -0
- package/dist/opf/write.cjs +55 -0
- package/dist/opf/write.d.cts +12 -0
- package/dist/opf/write.d.ts +12 -0
- package/dist/opf/write.js +54 -0
- package/dist/path.cjs +22 -0
- package/dist/path.d.cts +5 -0
- package/dist/path.d.ts +5 -0
- package/dist/path.js +20 -0
- package/dist/read.cjs +166 -0
- package/dist/read.d.cts +10 -0
- package/dist/read.d.ts +10 -0
- package/dist/read.js +164 -0
- package/dist/util/base64.cjs +51 -0
- package/dist/util/base64.d.cts +5 -0
- package/dist/util/base64.d.ts +5 -0
- package/dist/util/base64.js +49 -0
- package/dist/write.cjs +136 -0
- package/dist/write.d.cts +10 -0
- package/dist/write.d.ts +10 -0
- package/dist/write.js +134 -0
- package/dist/xhtml/context.cjs +0 -0
- package/dist/xhtml/context.d.cts +20 -0
- package/dist/xhtml/context.d.ts +20 -0
- package/dist/xhtml/context.js +0 -0
- package/dist/xhtml/footnote.cjs +32 -0
- package/dist/xhtml/footnote.d.cts +7 -0
- package/dist/xhtml/footnote.d.ts +7 -0
- package/dist/xhtml/footnote.js +29 -0
- package/dist/xhtml/inline.cjs +128 -0
- package/dist/xhtml/inline.d.cts +11 -0
- package/dist/xhtml/inline.d.ts +11 -0
- package/dist/xhtml/inline.js +127 -0
- package/dist/xhtml/list-id.cjs +22 -0
- package/dist/xhtml/list-id.d.cts +13 -0
- package/dist/xhtml/list-id.d.ts +13 -0
- package/dist/xhtml/list-id.js +20 -0
- package/dist/xhtml/read.cjs +423 -0
- package/dist/xhtml/read.d.cts +20 -0
- package/dist/xhtml/read.d.ts +20 -0
- package/dist/xhtml/read.js +422 -0
- package/dist/xhtml/style-constants.cjs +13 -0
- package/dist/xhtml/style-constants.d.cts +8 -0
- package/dist/xhtml/style-constants.d.ts +8 -0
- package/dist/xhtml/style-constants.js +8 -0
- package/dist/xhtml/write.cjs +183 -0
- package/dist/xhtml/write.d.cts +12 -0
- package/dist/xhtml/write.d.ts +12 -0
- package/dist/xhtml/write.js +182 -0
- package/dist/xml/build.cjs +46 -0
- package/dist/xml/build.d.cts +5 -0
- package/dist/xml/build.d.ts +5 -0
- package/dist/xml/build.js +46 -0
- package/dist/xml/entities.cjs +20 -0
- package/dist/xml/entities.d.cts +5 -0
- package/dist/xml/entities.d.ts +5 -0
- package/dist/xml/entities.js +18 -0
- package/dist/xml/node.cjs +67 -0
- package/dist/xml/node.d.cts +83 -0
- package/dist/xml/node.d.ts +83 -0
- package/dist/xml/node.js +58 -0
- package/dist/xml/parse.cjs +88 -0
- package/dist/xml/parse.d.cts +5 -0
- package/dist/xml/parse.d.ts +5 -0
- package/dist/xml/parse.js +88 -0
- package/dist/xml/query.cjs +44 -0
- package/dist/xml/query.d.cts +11 -0
- package/dist/xml/query.d.ts +11 -0
- package/dist/xml/query.js +37 -0
- package/dist/zip.cjs +19 -0
- package/dist/zip.d.cts +9 -0
- package/dist/zip.d.ts +9 -0
- package/dist/zip.js +17 -0
- package/package.json +95 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/util/base64.ts
|
|
3
|
+
const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4
|
+
const DECODE = (() => {
|
|
5
|
+
const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
|
|
6
|
+
for (let i = 0; i < 64; i += 1) map[TABLE.charCodeAt(i)] = i;
|
|
7
|
+
return map;
|
|
8
|
+
})();
|
|
9
|
+
function bytesToBase64(bytes) {
|
|
10
|
+
let out = "";
|
|
11
|
+
const len = bytes.length;
|
|
12
|
+
for (let i = 0; i < len; i += 3) {
|
|
13
|
+
const b0 = bytes[i] ?? 0;
|
|
14
|
+
const b1 = bytes[i + 1] ?? 0;
|
|
15
|
+
const b2 = bytes[i + 2] ?? 0;
|
|
16
|
+
out += TABLE.charAt(b0 >> 2);
|
|
17
|
+
out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
|
|
18
|
+
out += i + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
|
|
19
|
+
out += i + 2 < len ? TABLE.charAt(b2 & 63) : "=";
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function base64ToBytes(b64) {
|
|
24
|
+
const clean = b64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
25
|
+
const len = clean.length;
|
|
26
|
+
const out = new Uint8Array(Math.floor(len * 3 / 4));
|
|
27
|
+
let position = 0;
|
|
28
|
+
for (let i = 0; i < len; i += 4) {
|
|
29
|
+
const c0 = DECODE[clean.charCodeAt(i)] ?? 255;
|
|
30
|
+
const c1 = DECODE[clean.charCodeAt(i + 1)] ?? 255;
|
|
31
|
+
const code2 = clean.charCodeAt(i + 2);
|
|
32
|
+
const code3 = clean.charCodeAt(i + 3);
|
|
33
|
+
if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
|
|
34
|
+
out[position] = c0 << 2 | c1 >> 4;
|
|
35
|
+
position += 1;
|
|
36
|
+
if (code2 !== 61) {
|
|
37
|
+
const d2 = DECODE[code2] ?? 255;
|
|
38
|
+
out[position] = (c1 & 15) << 4 | d2 >> 2;
|
|
39
|
+
position += 1;
|
|
40
|
+
if (code3 !== 61) {
|
|
41
|
+
const d3 = DECODE[code3] ?? 255;
|
|
42
|
+
out[position] = (d2 & 3) << 6 | d3;
|
|
43
|
+
position += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out.subarray(0, position);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
exports.base64ToBytes = base64ToBytes;
|
|
51
|
+
exports.bytesToBase64 = bytesToBase64;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/util/base64.ts
|
|
2
|
+
const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
+
const DECODE = (() => {
|
|
4
|
+
const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
|
|
5
|
+
for (let i = 0; i < 64; i += 1) map[TABLE.charCodeAt(i)] = i;
|
|
6
|
+
return map;
|
|
7
|
+
})();
|
|
8
|
+
function bytesToBase64(bytes) {
|
|
9
|
+
let out = "";
|
|
10
|
+
const len = bytes.length;
|
|
11
|
+
for (let i = 0; i < len; i += 3) {
|
|
12
|
+
const b0 = bytes[i] ?? 0;
|
|
13
|
+
const b1 = bytes[i + 1] ?? 0;
|
|
14
|
+
const b2 = bytes[i + 2] ?? 0;
|
|
15
|
+
out += TABLE.charAt(b0 >> 2);
|
|
16
|
+
out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
|
|
17
|
+
out += i + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
|
|
18
|
+
out += i + 2 < len ? TABLE.charAt(b2 & 63) : "=";
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function base64ToBytes(b64) {
|
|
23
|
+
const clean = b64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
24
|
+
const len = clean.length;
|
|
25
|
+
const out = new Uint8Array(Math.floor(len * 3 / 4));
|
|
26
|
+
let position = 0;
|
|
27
|
+
for (let i = 0; i < len; i += 4) {
|
|
28
|
+
const c0 = DECODE[clean.charCodeAt(i)] ?? 255;
|
|
29
|
+
const c1 = DECODE[clean.charCodeAt(i + 1)] ?? 255;
|
|
30
|
+
const code2 = clean.charCodeAt(i + 2);
|
|
31
|
+
const code3 = clean.charCodeAt(i + 3);
|
|
32
|
+
if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
|
|
33
|
+
out[position] = c0 << 2 | c1 >> 4;
|
|
34
|
+
position += 1;
|
|
35
|
+
if (code2 !== 61) {
|
|
36
|
+
const d2 = DECODE[code2] ?? 255;
|
|
37
|
+
out[position] = (c1 & 15) << 4 | d2 >> 2;
|
|
38
|
+
position += 1;
|
|
39
|
+
if (code3 !== 61) {
|
|
40
|
+
const d3 = DECODE[code3] ?? 255;
|
|
41
|
+
out[position] = (d2 & 3) << 6 | d3;
|
|
42
|
+
position += 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out.subarray(0, position);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { base64ToBytes, bytesToBase64 };
|
package/dist/write.cjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_diagnostics = require("./diagnostics.cjs");
|
|
3
|
+
const require_xml_parse = require("./xml/parse.cjs");
|
|
4
|
+
const require_format = require("./format.cjs");
|
|
5
|
+
const require_xml_entities = require("./xml/entities.cjs");
|
|
6
|
+
const require_xml_build = require("./xml/build.cjs");
|
|
7
|
+
const require_zip = require("./zip.cjs");
|
|
8
|
+
const require_ocf_write = require("./ocf/write.cjs");
|
|
9
|
+
const require_nav_write = require("./nav/write.cjs");
|
|
10
|
+
const require_opf_write = require("./opf/write.cjs");
|
|
11
|
+
const require_xhtml_write = require("./xhtml/write.cjs");
|
|
12
|
+
let document_schema_js = require("document-schema.js");
|
|
13
|
+
//#region src/write.ts
|
|
14
|
+
const OPF_DIR = "OEBPS";
|
|
15
|
+
const OPF_PATH = `${OPF_DIR}/content.opf`;
|
|
16
|
+
const NAV_PATH = `${OPF_DIR}/nav.xhtml`;
|
|
17
|
+
const XHTML_DOCTYPE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">";
|
|
18
|
+
function sectionXhtmlPath(index) {
|
|
19
|
+
return `${OPF_DIR}/section${String(index + 1)}.xhtml`;
|
|
20
|
+
}
|
|
21
|
+
function buildHead(title, residueXml) {
|
|
22
|
+
return {
|
|
23
|
+
type: "element",
|
|
24
|
+
tag: "head",
|
|
25
|
+
attributes: [],
|
|
26
|
+
children: [{
|
|
27
|
+
type: "element",
|
|
28
|
+
tag: "title",
|
|
29
|
+
attributes: [],
|
|
30
|
+
children: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
value: require_xml_entities.encodeEntities(title)
|
|
33
|
+
}]
|
|
34
|
+
}, ...residueXml === void 0 ? [] : require_xml_parse.parseXml(residueXml)]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function imageExtension(format) {
|
|
38
|
+
return format === "png" ? "png" : "jpg";
|
|
39
|
+
}
|
|
40
|
+
function imageMediaType(format) {
|
|
41
|
+
return format === "png" ? "image/png" : "image/jpeg";
|
|
42
|
+
}
|
|
43
|
+
function writeEpubContent(document, options = {}) {
|
|
44
|
+
if (document.kind !== "wordprocessing") throw new require_diagnostics.EpubUnsupportedDocumentKindError(document.kind);
|
|
45
|
+
const sink = options.sink ?? require_diagnostics.NOOP_EPUB_DIAGNOSTIC_SINK;
|
|
46
|
+
const registeredImages = [];
|
|
47
|
+
const registerImage = (bytes, format) => {
|
|
48
|
+
const index = registeredImages.length + 1;
|
|
49
|
+
const href = `images/img${String(index)}.${imageExtension(format)}`;
|
|
50
|
+
registeredImages.push({
|
|
51
|
+
href,
|
|
52
|
+
bytes,
|
|
53
|
+
mediaType: imageMediaType(format)
|
|
54
|
+
});
|
|
55
|
+
return href;
|
|
56
|
+
};
|
|
57
|
+
const sectionXhtml = [];
|
|
58
|
+
document.sections.forEach((section, index) => {
|
|
59
|
+
const sourceHref = sectionXhtmlPath(index);
|
|
60
|
+
try {
|
|
61
|
+
const body = require_xhtml_write.writeXhtmlBody(section.blocks, {
|
|
62
|
+
registerImage,
|
|
63
|
+
sink,
|
|
64
|
+
sourceHref
|
|
65
|
+
});
|
|
66
|
+
const residueXml = section.source?.format === "epub" ? section.source.xml : void 0;
|
|
67
|
+
sectionXhtml.push({
|
|
68
|
+
body,
|
|
69
|
+
residueXml
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof document_schema_js.ConstructMarkerImbalanceError) throw new require_diagnostics.EpubUnbalancedConstructMarkersError(error.imbalance.kind, error.imbalance.index);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const navEntries = document.sections.map((section, index) => ({
|
|
77
|
+
href: `section${String(index + 1)}.xhtml`,
|
|
78
|
+
section
|
|
79
|
+
}));
|
|
80
|
+
const manifestItems = [
|
|
81
|
+
{
|
|
82
|
+
id: "nav",
|
|
83
|
+
href: "nav.xhtml",
|
|
84
|
+
mediaType: "application/xhtml+xml",
|
|
85
|
+
properties: ["nav"]
|
|
86
|
+
},
|
|
87
|
+
...document.sections.map((_section, index) => ({
|
|
88
|
+
id: `s${String(index + 1)}`,
|
|
89
|
+
href: `section${String(index + 1)}.xhtml`,
|
|
90
|
+
mediaType: "application/xhtml+xml",
|
|
91
|
+
properties: []
|
|
92
|
+
})),
|
|
93
|
+
...registeredImages.map((image, index) => ({
|
|
94
|
+
id: `img${String(index + 1)}`,
|
|
95
|
+
href: image.href,
|
|
96
|
+
mediaType: image.mediaType,
|
|
97
|
+
properties: []
|
|
98
|
+
}))
|
|
99
|
+
];
|
|
100
|
+
const opfXml = require_opf_write.writeOpf({
|
|
101
|
+
metadata: document.metadata,
|
|
102
|
+
manifestItems,
|
|
103
|
+
spineIdrefs: document.sections.map((_section, index) => `s${String(index + 1)}`),
|
|
104
|
+
identifier: `urn:uuid:${crypto.randomUUID()}`
|
|
105
|
+
});
|
|
106
|
+
const entries = [
|
|
107
|
+
[require_format.OCF_MIMETYPE_PATH, {
|
|
108
|
+
bytes: new TextEncoder().encode(require_format.EPUB_MIME_TYPE),
|
|
109
|
+
stored: true
|
|
110
|
+
}],
|
|
111
|
+
[require_format.OCF_CONTAINER_PATH, { bytes: new TextEncoder().encode(require_ocf_write.writeContainerXml(OPF_PATH)) }],
|
|
112
|
+
[OPF_PATH, { bytes: new TextEncoder().encode(opfXml) }],
|
|
113
|
+
[NAV_PATH, { bytes: new TextEncoder().encode(require_nav_write.writeNav3Document(navEntries)) }]
|
|
114
|
+
];
|
|
115
|
+
sectionXhtml.forEach(({ body, residueXml }, index) => {
|
|
116
|
+
const section = document.sections[index];
|
|
117
|
+
const head = buildHead(section === void 0 ? `Section ${String(index + 1)}` : require_nav_write.sectionTitle(section, index), residueXml);
|
|
118
|
+
const xml = `${XHTML_DOCTYPE}${require_xml_build.buildXml([head, body])}</html>`;
|
|
119
|
+
entries.push([sectionXhtmlPath(index), { bytes: new TextEncoder().encode(xml) }]);
|
|
120
|
+
});
|
|
121
|
+
for (const image of registeredImages) entries.push([`${OPF_DIR}/${image.href}`, { bytes: image.bytes }]);
|
|
122
|
+
return require_zip.zipPackage(entries);
|
|
123
|
+
}
|
|
124
|
+
function writeEpub(tree, options = {}) {
|
|
125
|
+
if (tree.kind !== "wordprocessing") throw new require_diagnostics.EpubUnsupportedDocumentKindError(tree.kind);
|
|
126
|
+
let flat;
|
|
127
|
+
try {
|
|
128
|
+
flat = (0, document_schema_js.flattenTree)(tree);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new require_diagnostics.EpubPackageFlattenError(error);
|
|
131
|
+
}
|
|
132
|
+
return writeEpubContent(flat, options);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
exports.writeEpub = writeEpub;
|
|
136
|
+
exports.writeEpubContent = writeEpubContent;
|
package/dist/write.d.cts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { i as EpubDiagnosticSink } from "./diagnostics-Ee4kG7mw.cjs";
|
|
2
|
+
import { ContentDocument, DocumentTree } from "document-schema.js";
|
|
3
|
+
//#region src/write.d.ts
|
|
4
|
+
interface WriteEpubOptions {
|
|
5
|
+
readonly sink?: EpubDiagnosticSink;
|
|
6
|
+
}
|
|
7
|
+
declare function writeEpubContent(document: ContentDocument, options?: WriteEpubOptions): Uint8Array<ArrayBuffer>;
|
|
8
|
+
declare function writeEpub(tree: DocumentTree, options?: WriteEpubOptions): Uint8Array<ArrayBuffer>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { WriteEpubOptions, writeEpub, writeEpubContent };
|
package/dist/write.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/write.d.ts
|
|
4
|
+
interface WriteEpubOptions {
|
|
5
|
+
readonly sink?: EpubDiagnosticSink;
|
|
6
|
+
}
|
|
7
|
+
declare function writeEpubContent(document: ContentDocument, options?: WriteEpubOptions): Uint8Array<ArrayBuffer>;
|
|
8
|
+
declare function writeEpub(tree: DocumentTree, options?: WriteEpubOptions): Uint8Array<ArrayBuffer>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { WriteEpubOptions, writeEpub, writeEpubContent };
|
package/dist/write.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { EpubPackageFlattenError, EpubUnbalancedConstructMarkersError, EpubUnsupportedDocumentKindError, NOOP_EPUB_DIAGNOSTIC_SINK } from "./diagnostics.js";
|
|
2
|
+
import { parseXml } from "./xml/parse.js";
|
|
3
|
+
import { EPUB_MIME_TYPE, OCF_CONTAINER_PATH, OCF_MIMETYPE_PATH } from "./format.js";
|
|
4
|
+
import { encodeEntities } from "./xml/entities.js";
|
|
5
|
+
import { buildXml } from "./xml/build.js";
|
|
6
|
+
import { zipPackage } from "./zip.js";
|
|
7
|
+
import { writeContainerXml } from "./ocf/write.js";
|
|
8
|
+
import { sectionTitle, writeNav3Document } from "./nav/write.js";
|
|
9
|
+
import { writeOpf } from "./opf/write.js";
|
|
10
|
+
import { writeXhtmlBody } from "./xhtml/write.js";
|
|
11
|
+
import { ConstructMarkerImbalanceError, flattenTree } from "document-schema.js";
|
|
12
|
+
//#region src/write.ts
|
|
13
|
+
const OPF_DIR = "OEBPS";
|
|
14
|
+
const OPF_PATH = `${OPF_DIR}/content.opf`;
|
|
15
|
+
const NAV_PATH = `${OPF_DIR}/nav.xhtml`;
|
|
16
|
+
const XHTML_DOCTYPE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">";
|
|
17
|
+
function sectionXhtmlPath(index) {
|
|
18
|
+
return `${OPF_DIR}/section${String(index + 1)}.xhtml`;
|
|
19
|
+
}
|
|
20
|
+
function buildHead(title, residueXml) {
|
|
21
|
+
return {
|
|
22
|
+
type: "element",
|
|
23
|
+
tag: "head",
|
|
24
|
+
attributes: [],
|
|
25
|
+
children: [{
|
|
26
|
+
type: "element",
|
|
27
|
+
tag: "title",
|
|
28
|
+
attributes: [],
|
|
29
|
+
children: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
value: encodeEntities(title)
|
|
32
|
+
}]
|
|
33
|
+
}, ...residueXml === void 0 ? [] : parseXml(residueXml)]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function imageExtension(format) {
|
|
37
|
+
return format === "png" ? "png" : "jpg";
|
|
38
|
+
}
|
|
39
|
+
function imageMediaType(format) {
|
|
40
|
+
return format === "png" ? "image/png" : "image/jpeg";
|
|
41
|
+
}
|
|
42
|
+
function writeEpubContent(document, options = {}) {
|
|
43
|
+
if (document.kind !== "wordprocessing") throw new EpubUnsupportedDocumentKindError(document.kind);
|
|
44
|
+
const sink = options.sink ?? NOOP_EPUB_DIAGNOSTIC_SINK;
|
|
45
|
+
const registeredImages = [];
|
|
46
|
+
const registerImage = (bytes, format) => {
|
|
47
|
+
const index = registeredImages.length + 1;
|
|
48
|
+
const href = `images/img${String(index)}.${imageExtension(format)}`;
|
|
49
|
+
registeredImages.push({
|
|
50
|
+
href,
|
|
51
|
+
bytes,
|
|
52
|
+
mediaType: imageMediaType(format)
|
|
53
|
+
});
|
|
54
|
+
return href;
|
|
55
|
+
};
|
|
56
|
+
const sectionXhtml = [];
|
|
57
|
+
document.sections.forEach((section, index) => {
|
|
58
|
+
const sourceHref = sectionXhtmlPath(index);
|
|
59
|
+
try {
|
|
60
|
+
const body = writeXhtmlBody(section.blocks, {
|
|
61
|
+
registerImage,
|
|
62
|
+
sink,
|
|
63
|
+
sourceHref
|
|
64
|
+
});
|
|
65
|
+
const residueXml = section.source?.format === "epub" ? section.source.xml : void 0;
|
|
66
|
+
sectionXhtml.push({
|
|
67
|
+
body,
|
|
68
|
+
residueXml
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof ConstructMarkerImbalanceError) throw new EpubUnbalancedConstructMarkersError(error.imbalance.kind, error.imbalance.index);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const navEntries = document.sections.map((section, index) => ({
|
|
76
|
+
href: `section${String(index + 1)}.xhtml`,
|
|
77
|
+
section
|
|
78
|
+
}));
|
|
79
|
+
const manifestItems = [
|
|
80
|
+
{
|
|
81
|
+
id: "nav",
|
|
82
|
+
href: "nav.xhtml",
|
|
83
|
+
mediaType: "application/xhtml+xml",
|
|
84
|
+
properties: ["nav"]
|
|
85
|
+
},
|
|
86
|
+
...document.sections.map((_section, index) => ({
|
|
87
|
+
id: `s${String(index + 1)}`,
|
|
88
|
+
href: `section${String(index + 1)}.xhtml`,
|
|
89
|
+
mediaType: "application/xhtml+xml",
|
|
90
|
+
properties: []
|
|
91
|
+
})),
|
|
92
|
+
...registeredImages.map((image, index) => ({
|
|
93
|
+
id: `img${String(index + 1)}`,
|
|
94
|
+
href: image.href,
|
|
95
|
+
mediaType: image.mediaType,
|
|
96
|
+
properties: []
|
|
97
|
+
}))
|
|
98
|
+
];
|
|
99
|
+
const opfXml = writeOpf({
|
|
100
|
+
metadata: document.metadata,
|
|
101
|
+
manifestItems,
|
|
102
|
+
spineIdrefs: document.sections.map((_section, index) => `s${String(index + 1)}`),
|
|
103
|
+
identifier: `urn:uuid:${crypto.randomUUID()}`
|
|
104
|
+
});
|
|
105
|
+
const entries = [
|
|
106
|
+
[OCF_MIMETYPE_PATH, {
|
|
107
|
+
bytes: new TextEncoder().encode(EPUB_MIME_TYPE),
|
|
108
|
+
stored: true
|
|
109
|
+
}],
|
|
110
|
+
[OCF_CONTAINER_PATH, { bytes: new TextEncoder().encode(writeContainerXml(OPF_PATH)) }],
|
|
111
|
+
[OPF_PATH, { bytes: new TextEncoder().encode(opfXml) }],
|
|
112
|
+
[NAV_PATH, { bytes: new TextEncoder().encode(writeNav3Document(navEntries)) }]
|
|
113
|
+
];
|
|
114
|
+
sectionXhtml.forEach(({ body, residueXml }, index) => {
|
|
115
|
+
const section = document.sections[index];
|
|
116
|
+
const head = buildHead(section === void 0 ? `Section ${String(index + 1)}` : sectionTitle(section, index), residueXml);
|
|
117
|
+
const xml = `${XHTML_DOCTYPE}${buildXml([head, body])}</html>`;
|
|
118
|
+
entries.push([sectionXhtmlPath(index), { bytes: new TextEncoder().encode(xml) }]);
|
|
119
|
+
});
|
|
120
|
+
for (const image of registeredImages) entries.push([`${OPF_DIR}/${image.href}`, { bytes: image.bytes }]);
|
|
121
|
+
return zipPackage(entries);
|
|
122
|
+
}
|
|
123
|
+
function writeEpub(tree, options = {}) {
|
|
124
|
+
if (tree.kind !== "wordprocessing") throw new EpubUnsupportedDocumentKindError(tree.kind);
|
|
125
|
+
let flat;
|
|
126
|
+
try {
|
|
127
|
+
flat = flattenTree(tree);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new EpubPackageFlattenError(error);
|
|
130
|
+
}
|
|
131
|
+
return writeEpubContent(flat, options);
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { writeEpub, writeEpubContent };
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { i as EpubDiagnosticSink } from "../diagnostics-Ee4kG7mw.cjs";
|
|
2
|
+
import { XmlElement } from "../xml/node.cjs";
|
|
3
|
+
//#region src/xhtml/context.d.ts
|
|
4
|
+
interface XhtmlReadContext {
|
|
5
|
+
readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
|
6
|
+
readonly sink: EpubDiagnosticSink;
|
|
7
|
+
readonly sourceHref: string;
|
|
8
|
+
readonly idElements: ReadonlyMap<string, XmlElement>;
|
|
9
|
+
readonly footnoteTargetIds: ReadonlySet<string>;
|
|
10
|
+
readonly quoteDepth: number;
|
|
11
|
+
}
|
|
12
|
+
interface InlineStyle {
|
|
13
|
+
readonly bold?: boolean;
|
|
14
|
+
readonly italic?: boolean;
|
|
15
|
+
readonly underline?: boolean;
|
|
16
|
+
readonly strike?: boolean;
|
|
17
|
+
readonly fontFamily?: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { InlineStyle, XhtmlReadContext };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { i as EpubDiagnosticSink } from "../diagnostics-Ee4kG7mw.js";
|
|
2
|
+
import { XmlElement } from "../xml/node.js";
|
|
3
|
+
//#region src/xhtml/context.d.ts
|
|
4
|
+
interface XhtmlReadContext {
|
|
5
|
+
readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
|
6
|
+
readonly sink: EpubDiagnosticSink;
|
|
7
|
+
readonly sourceHref: string;
|
|
8
|
+
readonly idElements: ReadonlyMap<string, XmlElement>;
|
|
9
|
+
readonly footnoteTargetIds: ReadonlySet<string>;
|
|
10
|
+
readonly quoteDepth: number;
|
|
11
|
+
}
|
|
12
|
+
interface InlineStyle {
|
|
13
|
+
readonly bold?: boolean;
|
|
14
|
+
readonly italic?: boolean;
|
|
15
|
+
readonly underline?: boolean;
|
|
16
|
+
readonly strike?: boolean;
|
|
17
|
+
readonly fontFamily?: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { InlineStyle, XhtmlReadContext };
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_xml_query = require("../xml/query.cjs");
|
|
3
|
+
//#region src/xhtml/footnote.ts
|
|
4
|
+
const FOOTNOTE_CLASS_PATTERN = /footnote|noteref/i;
|
|
5
|
+
function epubTypeValues(element) {
|
|
6
|
+
const value = require_xml_query.attrValue(element, "epub:type");
|
|
7
|
+
return value === void 0 ? [] : value.split(/\s+/u).filter((v) => v.length > 0);
|
|
8
|
+
}
|
|
9
|
+
function hasFootnoteClass(element) {
|
|
10
|
+
const className = require_xml_query.attrValue(element, "class");
|
|
11
|
+
return className !== void 0 && FOOTNOTE_CLASS_PATTERN.test(className);
|
|
12
|
+
}
|
|
13
|
+
function sameDocumentFragment(href) {
|
|
14
|
+
if (href === void 0 || !href.startsWith("#") || href.length < 2) return;
|
|
15
|
+
return href.slice(1);
|
|
16
|
+
}
|
|
17
|
+
function isFootnoteReferenceAnchor(anchor, idElements) {
|
|
18
|
+
const fragment = sameDocumentFragment(require_xml_query.attrValue(anchor, "href"));
|
|
19
|
+
if (fragment === void 0) return;
|
|
20
|
+
const target = idElements.get(fragment);
|
|
21
|
+
if (target === void 0) return;
|
|
22
|
+
if (epubTypeValues(anchor).some((v) => /noteref/i.test(v))) return fragment;
|
|
23
|
+
if (hasFootnoteClass(anchor) || hasFootnoteClass(target)) return fragment;
|
|
24
|
+
}
|
|
25
|
+
function isFootnoteAside(element) {
|
|
26
|
+
if (element.tag !== "aside") return false;
|
|
27
|
+
return epubTypeValues(element).some((v) => /footnote|rearnote/i.test(v));
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
exports.isFootnoteAside = isFootnoteAside;
|
|
31
|
+
exports.isFootnoteReferenceAnchor = isFootnoteReferenceAnchor;
|
|
32
|
+
exports.sameDocumentFragment = sameDocumentFragment;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { XmlElement } from "../xml/node.cjs";
|
|
2
|
+
//#region src/xhtml/footnote.d.ts
|
|
3
|
+
declare function sameDocumentFragment(href: string | undefined): string | undefined;
|
|
4
|
+
declare function isFootnoteReferenceAnchor(anchor: XmlElement, idElements: ReadonlyMap<string, XmlElement>): string | undefined;
|
|
5
|
+
declare function isFootnoteAside(element: XmlElement): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { isFootnoteAside, isFootnoteReferenceAnchor, sameDocumentFragment };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { XmlElement } from "../xml/node.js";
|
|
2
|
+
//#region src/xhtml/footnote.d.ts
|
|
3
|
+
declare function sameDocumentFragment(href: string | undefined): string | undefined;
|
|
4
|
+
declare function isFootnoteReferenceAnchor(anchor: XmlElement, idElements: ReadonlyMap<string, XmlElement>): string | undefined;
|
|
5
|
+
declare function isFootnoteAside(element: XmlElement): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { isFootnoteAside, isFootnoteReferenceAnchor, sameDocumentFragment };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { attrValue } from "../xml/query.js";
|
|
2
|
+
//#region src/xhtml/footnote.ts
|
|
3
|
+
const FOOTNOTE_CLASS_PATTERN = /footnote|noteref/i;
|
|
4
|
+
function epubTypeValues(element) {
|
|
5
|
+
const value = attrValue(element, "epub:type");
|
|
6
|
+
return value === void 0 ? [] : value.split(/\s+/u).filter((v) => v.length > 0);
|
|
7
|
+
}
|
|
8
|
+
function hasFootnoteClass(element) {
|
|
9
|
+
const className = attrValue(element, "class");
|
|
10
|
+
return className !== void 0 && FOOTNOTE_CLASS_PATTERN.test(className);
|
|
11
|
+
}
|
|
12
|
+
function sameDocumentFragment(href) {
|
|
13
|
+
if (href === void 0 || !href.startsWith("#") || href.length < 2) return;
|
|
14
|
+
return href.slice(1);
|
|
15
|
+
}
|
|
16
|
+
function isFootnoteReferenceAnchor(anchor, idElements) {
|
|
17
|
+
const fragment = sameDocumentFragment(attrValue(anchor, "href"));
|
|
18
|
+
if (fragment === void 0) return;
|
|
19
|
+
const target = idElements.get(fragment);
|
|
20
|
+
if (target === void 0) return;
|
|
21
|
+
if (epubTypeValues(anchor).some((v) => /noteref/i.test(v))) return fragment;
|
|
22
|
+
if (hasFootnoteClass(anchor) || hasFootnoteClass(target)) return fragment;
|
|
23
|
+
}
|
|
24
|
+
function isFootnoteAside(element) {
|
|
25
|
+
if (element.tag !== "aside") return false;
|
|
26
|
+
return epubTypeValues(element).some((v) => /footnote|rearnote/i.test(v));
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { isFootnoteAside, isFootnoteReferenceAnchor, sameDocumentFragment };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_diagnostics = require("../diagnostics.cjs");
|
|
3
|
+
const require_xml_query = require("../xml/query.cjs");
|
|
4
|
+
const require_xml_entities = require("../xml/entities.cjs");
|
|
5
|
+
const require_xhtml_footnote = require("./footnote.cjs");
|
|
6
|
+
const require_xhtml_style_constants = require("./style-constants.cjs");
|
|
7
|
+
//#region src/xhtml/inline.ts
|
|
8
|
+
function normalizeWhitespace(text) {
|
|
9
|
+
return text.replace(/[ \t\r\n]+/gu, " ");
|
|
10
|
+
}
|
|
11
|
+
function mergeStyle(outer, inner) {
|
|
12
|
+
return {
|
|
13
|
+
...outer,
|
|
14
|
+
...inner
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function styledRun(text, style, hyperlink) {
|
|
18
|
+
const run = { text };
|
|
19
|
+
if (style.bold === true) run.bold = true;
|
|
20
|
+
if (style.italic === true) run.italic = true;
|
|
21
|
+
if (style.underline === true) run.underline = true;
|
|
22
|
+
if (style.strike === true) run.strike = true;
|
|
23
|
+
if (style.fontFamily !== void 0) run.fontFamily = style.fontFamily;
|
|
24
|
+
if (hyperlink !== void 0) run.hyperlink = hyperlink;
|
|
25
|
+
return run;
|
|
26
|
+
}
|
|
27
|
+
function buildInlineRuns(nodes, style, context) {
|
|
28
|
+
const runs = [];
|
|
29
|
+
const constructs = [];
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
if (node.type === "text") {
|
|
32
|
+
const text = normalizeWhitespace(require_xml_entities.decodeEntities(node.value));
|
|
33
|
+
if (text.length > 0) runs.push(styledRun(text, style));
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (node.type !== "element") continue;
|
|
37
|
+
appendElement(node, style, context, runs, constructs);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
runs,
|
|
41
|
+
constructs
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function appendElement(element, style, context, runs, constructs) {
|
|
45
|
+
switch (element.tag) {
|
|
46
|
+
case "strong":
|
|
47
|
+
case "b":
|
|
48
|
+
appendNested(element, mergeStyle(style, { bold: true }), context, runs, constructs);
|
|
49
|
+
return;
|
|
50
|
+
case "em":
|
|
51
|
+
case "i":
|
|
52
|
+
appendNested(element, mergeStyle(style, { italic: true }), context, runs, constructs);
|
|
53
|
+
return;
|
|
54
|
+
case "u":
|
|
55
|
+
appendNested(element, mergeStyle(style, { underline: true }), context, runs, constructs);
|
|
56
|
+
return;
|
|
57
|
+
case "s":
|
|
58
|
+
case "strike":
|
|
59
|
+
case "del":
|
|
60
|
+
appendNested(element, mergeStyle(style, { strike: true }), context, runs, constructs);
|
|
61
|
+
return;
|
|
62
|
+
case "code":
|
|
63
|
+
case "kbd":
|
|
64
|
+
case "samp":
|
|
65
|
+
appendNested(element, mergeStyle(style, { fontFamily: require_xhtml_style_constants.MONOSPACE_FONT_FAMILY }), context, runs, constructs);
|
|
66
|
+
return;
|
|
67
|
+
case "sub":
|
|
68
|
+
case "sup":
|
|
69
|
+
context.sink({
|
|
70
|
+
code: require_diagnostics.EpubDiagnosticCodes.ELEMENT_UNMAPPED,
|
|
71
|
+
severity: "info",
|
|
72
|
+
message: `<${element.tag}> has no document-schema.js run-level field to carry its vertical position; the text is kept, the styling is not`,
|
|
73
|
+
href: context.sourceHref
|
|
74
|
+
});
|
|
75
|
+
appendNested(element, style, context, runs, constructs);
|
|
76
|
+
return;
|
|
77
|
+
case "br":
|
|
78
|
+
runs.push(styledRun("\n", style));
|
|
79
|
+
return;
|
|
80
|
+
case "a":
|
|
81
|
+
appendAnchor(element, style, context, runs, constructs);
|
|
82
|
+
return;
|
|
83
|
+
default: appendNested(element, style, context, runs, constructs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function appendNested(element, style, context, runs, constructs) {
|
|
87
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
88
|
+
runs.push(...nested.runs);
|
|
89
|
+
constructs.push(...nested.constructs);
|
|
90
|
+
}
|
|
91
|
+
function appendAnchor(element, style, context, runs, constructs) {
|
|
92
|
+
const footnoteName = require_xhtml_footnote.isFootnoteReferenceAnchor(element, context.idElements);
|
|
93
|
+
if (footnoteName !== void 0) {
|
|
94
|
+
const startRun = runs.length;
|
|
95
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
96
|
+
runs.push(...nested.runs);
|
|
97
|
+
constructs.push(...nested.constructs);
|
|
98
|
+
constructs.push({
|
|
99
|
+
descriptor: {
|
|
100
|
+
kind: "anchor",
|
|
101
|
+
anchorType: "footnote",
|
|
102
|
+
name: footnoteName
|
|
103
|
+
},
|
|
104
|
+
startRun,
|
|
105
|
+
endRun: runs.length
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const href = require_xml_query.attrValue(element, "href");
|
|
110
|
+
if (href === void 0 || href.length === 0) {
|
|
111
|
+
appendNested(element, style, context, runs, constructs);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
115
|
+
for (const run of nested.runs) runs.push({
|
|
116
|
+
...run,
|
|
117
|
+
hyperlink: href
|
|
118
|
+
});
|
|
119
|
+
constructs.push(...nested.constructs);
|
|
120
|
+
if (require_xhtml_footnote.sameDocumentFragment(href) === void 0 && !/^[a-z][a-z0-9+.-]*:/iu.test(href)) context.sink({
|
|
121
|
+
code: require_diagnostics.EpubDiagnosticCodes.LINK_TARGET_EXTERNAL_ONLY,
|
|
122
|
+
severity: "info",
|
|
123
|
+
message: `href "${href}" carries no URI scheme and is not a same-document fragment; it is stored verbatim on ContentRun.hyperlink without resolution against the package's own manifest`,
|
|
124
|
+
href: context.sourceHref
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
exports.buildInlineRuns = buildInlineRuns;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { XmlNode } from "../xml/node.cjs";
|
|
2
|
+
import { InlineStyle, XhtmlReadContext } from "./context.cjs";
|
|
3
|
+
import { ContentRun, RunConstructExtent } from "document-schema.js";
|
|
4
|
+
//#region src/xhtml/inline.d.ts
|
|
5
|
+
interface InlineResult {
|
|
6
|
+
readonly runs: ContentRun[];
|
|
7
|
+
readonly constructs: RunConstructExtent[];
|
|
8
|
+
}
|
|
9
|
+
declare function buildInlineRuns(nodes: readonly XmlNode[], style: InlineStyle, context: XhtmlReadContext): InlineResult;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { InlineResult, buildInlineRuns };
|