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,11 @@
1
+ import { XmlNode } from "../xml/node.js";
2
+ import { InlineStyle, XhtmlReadContext } from "./context.js";
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 };
@@ -0,0 +1,127 @@
1
+ import { EpubDiagnosticCodes } from "../diagnostics.js";
2
+ import { attrValue } from "../xml/query.js";
3
+ import { decodeEntities } from "../xml/entities.js";
4
+ import { isFootnoteReferenceAnchor, sameDocumentFragment } from "./footnote.js";
5
+ import { MONOSPACE_FONT_FAMILY } from "./style-constants.js";
6
+ //#region src/xhtml/inline.ts
7
+ function normalizeWhitespace(text) {
8
+ return text.replace(/[ \t\r\n]+/gu, " ");
9
+ }
10
+ function mergeStyle(outer, inner) {
11
+ return {
12
+ ...outer,
13
+ ...inner
14
+ };
15
+ }
16
+ function styledRun(text, style, hyperlink) {
17
+ const run = { text };
18
+ if (style.bold === true) run.bold = true;
19
+ if (style.italic === true) run.italic = true;
20
+ if (style.underline === true) run.underline = true;
21
+ if (style.strike === true) run.strike = true;
22
+ if (style.fontFamily !== void 0) run.fontFamily = style.fontFamily;
23
+ if (hyperlink !== void 0) run.hyperlink = hyperlink;
24
+ return run;
25
+ }
26
+ function buildInlineRuns(nodes, style, context) {
27
+ const runs = [];
28
+ const constructs = [];
29
+ for (const node of nodes) {
30
+ if (node.type === "text") {
31
+ const text = normalizeWhitespace(decodeEntities(node.value));
32
+ if (text.length > 0) runs.push(styledRun(text, style));
33
+ continue;
34
+ }
35
+ if (node.type !== "element") continue;
36
+ appendElement(node, style, context, runs, constructs);
37
+ }
38
+ return {
39
+ runs,
40
+ constructs
41
+ };
42
+ }
43
+ function appendElement(element, style, context, runs, constructs) {
44
+ switch (element.tag) {
45
+ case "strong":
46
+ case "b":
47
+ appendNested(element, mergeStyle(style, { bold: true }), context, runs, constructs);
48
+ return;
49
+ case "em":
50
+ case "i":
51
+ appendNested(element, mergeStyle(style, { italic: true }), context, runs, constructs);
52
+ return;
53
+ case "u":
54
+ appendNested(element, mergeStyle(style, { underline: true }), context, runs, constructs);
55
+ return;
56
+ case "s":
57
+ case "strike":
58
+ case "del":
59
+ appendNested(element, mergeStyle(style, { strike: true }), context, runs, constructs);
60
+ return;
61
+ case "code":
62
+ case "kbd":
63
+ case "samp":
64
+ appendNested(element, mergeStyle(style, { fontFamily: MONOSPACE_FONT_FAMILY }), context, runs, constructs);
65
+ return;
66
+ case "sub":
67
+ case "sup":
68
+ context.sink({
69
+ code: EpubDiagnosticCodes.ELEMENT_UNMAPPED,
70
+ severity: "info",
71
+ message: `<${element.tag}> has no document-schema.js run-level field to carry its vertical position; the text is kept, the styling is not`,
72
+ href: context.sourceHref
73
+ });
74
+ appendNested(element, style, context, runs, constructs);
75
+ return;
76
+ case "br":
77
+ runs.push(styledRun("\n", style));
78
+ return;
79
+ case "a":
80
+ appendAnchor(element, style, context, runs, constructs);
81
+ return;
82
+ default: appendNested(element, style, context, runs, constructs);
83
+ }
84
+ }
85
+ function appendNested(element, style, context, runs, constructs) {
86
+ const nested = buildInlineRuns(element.children, style, context);
87
+ runs.push(...nested.runs);
88
+ constructs.push(...nested.constructs);
89
+ }
90
+ function appendAnchor(element, style, context, runs, constructs) {
91
+ const footnoteName = isFootnoteReferenceAnchor(element, context.idElements);
92
+ if (footnoteName !== void 0) {
93
+ const startRun = runs.length;
94
+ const nested = buildInlineRuns(element.children, style, context);
95
+ runs.push(...nested.runs);
96
+ constructs.push(...nested.constructs);
97
+ constructs.push({
98
+ descriptor: {
99
+ kind: "anchor",
100
+ anchorType: "footnote",
101
+ name: footnoteName
102
+ },
103
+ startRun,
104
+ endRun: runs.length
105
+ });
106
+ return;
107
+ }
108
+ const href = attrValue(element, "href");
109
+ if (href === void 0 || href.length === 0) {
110
+ appendNested(element, style, context, runs, constructs);
111
+ return;
112
+ }
113
+ const nested = buildInlineRuns(element.children, style, context);
114
+ for (const run of nested.runs) runs.push({
115
+ ...run,
116
+ hyperlink: href
117
+ });
118
+ constructs.push(...nested.constructs);
119
+ if (sameDocumentFragment(href) === void 0 && !/^[a-z][a-z0-9+.-]*:/iu.test(href)) context.sink({
120
+ code: EpubDiagnosticCodes.LINK_TARGET_EXTERNAL_ONLY,
121
+ severity: "info",
122
+ 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`,
123
+ href: context.sourceHref
124
+ });
125
+ }
126
+ //#endregion
127
+ export { buildInlineRuns };
@@ -0,0 +1,22 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/xhtml/list-id.ts
3
+ const NUMID_PATTERN = /^epub(\d+):(bullet|ordered)(?:@(\d+))?$/u;
4
+ function mintListNumId(nextId, options) {
5
+ const suffix = options.type === "ordered" && options.start !== void 0 && options.start !== 1 ? `ordered@${String(options.start)}` : options.type;
6
+ return `epub${String(nextId)}:${suffix}`;
7
+ }
8
+ function parseListNumId(numId) {
9
+ const match = NUMID_PATTERN.exec(numId);
10
+ if (match === null) return;
11
+ const type = match[2];
12
+ if (type !== "bullet" && type !== "ordered") return;
13
+ const startText = match[3];
14
+ if (type === "ordered" && startText !== void 0) return {
15
+ type,
16
+ start: Number.parseInt(startText, 10)
17
+ };
18
+ return { type };
19
+ }
20
+ //#endregion
21
+ exports.mintListNumId = mintListNumId;
22
+ exports.parseListNumId = parseListNumId;
@@ -0,0 +1,13 @@
1
+ //#region src/xhtml/list-id.d.ts
2
+ interface ListNumIdInfo {
3
+ readonly type: "bullet" | "ordered";
4
+ readonly start?: number;
5
+ }
6
+ interface MintListNumIdOptions {
7
+ readonly type: "bullet" | "ordered";
8
+ readonly start?: number;
9
+ }
10
+ declare function mintListNumId(nextId: number, options: MintListNumIdOptions): string;
11
+ declare function parseListNumId(numId: string): ListNumIdInfo | undefined;
12
+ //#endregion
13
+ export { ListNumIdInfo, MintListNumIdOptions, mintListNumId, parseListNumId };
@@ -0,0 +1,13 @@
1
+ //#region src/xhtml/list-id.d.ts
2
+ interface ListNumIdInfo {
3
+ readonly type: "bullet" | "ordered";
4
+ readonly start?: number;
5
+ }
6
+ interface MintListNumIdOptions {
7
+ readonly type: "bullet" | "ordered";
8
+ readonly start?: number;
9
+ }
10
+ declare function mintListNumId(nextId: number, options: MintListNumIdOptions): string;
11
+ declare function parseListNumId(numId: string): ListNumIdInfo | undefined;
12
+ //#endregion
13
+ export { ListNumIdInfo, MintListNumIdOptions, mintListNumId, parseListNumId };
@@ -0,0 +1,20 @@
1
+ //#region src/xhtml/list-id.ts
2
+ const NUMID_PATTERN = /^epub(\d+):(bullet|ordered)(?:@(\d+))?$/u;
3
+ function mintListNumId(nextId, options) {
4
+ const suffix = options.type === "ordered" && options.start !== void 0 && options.start !== 1 ? `ordered@${String(options.start)}` : options.type;
5
+ return `epub${String(nextId)}:${suffix}`;
6
+ }
7
+ function parseListNumId(numId) {
8
+ const match = NUMID_PATTERN.exec(numId);
9
+ if (match === null) return;
10
+ const type = match[2];
11
+ if (type !== "bullet" && type !== "ordered") return;
12
+ const startText = match[3];
13
+ if (type === "ordered" && startText !== void 0) return {
14
+ type,
15
+ start: Number.parseInt(startText, 10)
16
+ };
17
+ return { type };
18
+ }
19
+ //#endregion
20
+ export { mintListNumId, parseListNumId };
@@ -0,0 +1,423 @@
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_parse = require("../xml/parse.cjs");
5
+ const require_xml_entities = require("../xml/entities.cjs");
6
+ const require_image_dimensions = require("../image/dimensions.cjs");
7
+ const require_util_base64 = require("../util/base64.cjs");
8
+ const require_xml_build = require("../xml/build.cjs");
9
+ const require_xhtml_footnote = require("./footnote.cjs");
10
+ const require_xhtml_style_constants = require("./style-constants.cjs");
11
+ const require_xhtml_inline = require("./inline.cjs");
12
+ const require_xhtml_list_id = require("./list-id.cjs");
13
+ let document_schema_js = require("document-schema.js");
14
+ //#region src/xhtml/read.ts
15
+ function createIdMinter() {
16
+ let nextList = 0;
17
+ let nextItem = 0;
18
+ return {
19
+ mintNumId: (options) => {
20
+ nextList += 1;
21
+ return require_xhtml_list_id.mintListNumId(nextList, options);
22
+ },
23
+ mintItemId: () => {
24
+ nextItem += 1;
25
+ return `item${String(nextItem)}`;
26
+ }
27
+ };
28
+ }
29
+ function withQuote(state) {
30
+ return {
31
+ ...state,
32
+ quoteDepth: state.quoteDepth + 1
33
+ };
34
+ }
35
+ function withListItem(state, listItem) {
36
+ return {
37
+ ...state,
38
+ listItem,
39
+ list: {
40
+ numId: listItem.numId,
41
+ level: listItem.level
42
+ }
43
+ };
44
+ }
45
+ function decorateParagraph(paragraph, state) {
46
+ let decorated = paragraph;
47
+ if (state.quoteDepth > 0) decorated = {
48
+ ...decorated,
49
+ indentLeftPt: state.quoteDepth * 36,
50
+ styleId: decorated.styleId ?? "Quote"
51
+ };
52
+ if (state.listItem !== void 0) {
53
+ const membership = {
54
+ numId: state.listItem.numId,
55
+ level: state.listItem.level,
56
+ itemId: state.listItem.itemId
57
+ };
58
+ decorated = {
59
+ ...decorated,
60
+ list: membership
61
+ };
62
+ }
63
+ return decorated;
64
+ }
65
+ function buildIdElementMap(nodes) {
66
+ const map = /* @__PURE__ */ new Map();
67
+ const stack = [...nodes];
68
+ while (stack.length > 0) {
69
+ const node = stack.pop();
70
+ if (node?.type !== "element") continue;
71
+ const id = require_xml_query.attrValue(node, "id");
72
+ if (id !== void 0 && !map.has(id)) map.set(id, node);
73
+ stack.push(...node.children);
74
+ }
75
+ return map;
76
+ }
77
+ function readStyleResidue(html, context) {
78
+ const head = require_xml_query.findChildElement(html.children, "head");
79
+ if (head === void 0) return;
80
+ const styleElements = head.children.filter((node) => node.type === "element" && (node.tag === "style" || node.tag === "link" && require_xml_query.attrValue(node, "rel") === "stylesheet"));
81
+ if (styleElements.length === 0) return;
82
+ context.sink({
83
+ code: require_diagnostics.EpubDiagnosticCodes.STYLE_RESIDUE,
84
+ severity: "info",
85
+ message: "the document's own <head> style declarations (CSS) are quarantined as residue rather than interpreted; the schema is content, not styling",
86
+ href: context.sourceHref
87
+ });
88
+ return {
89
+ format: "epub",
90
+ xml: require_xml_build.buildXml(styleElements)
91
+ };
92
+ }
93
+ function readXhtmlBody(xml, options) {
94
+ const nodes = require_xml_parse.parseXml(xml);
95
+ const html = require_xml_query.rootElement(nodes);
96
+ const body = html === void 0 ? void 0 : require_xml_query.findChildElement(html.children, "body");
97
+ if (body === void 0 || html === void 0) return {
98
+ blocks: [],
99
+ source: void 0
100
+ };
101
+ const idElements = buildIdElementMap(body.children);
102
+ const footnoteTargetIds = /* @__PURE__ */ new Set();
103
+ for (const anchor of require_xml_query.elementsWithTag(body.children, "a")) {
104
+ const name = require_xhtml_footnote.isFootnoteReferenceAnchor(anchor, idElements);
105
+ if (name !== void 0) footnoteTargetIds.add(name);
106
+ }
107
+ const context = {
108
+ resolveImage: options.resolveImage,
109
+ sink: options.sink,
110
+ sourceHref: options.sourceHref,
111
+ idElements,
112
+ footnoteTargetIds,
113
+ quoteDepth: 0
114
+ };
115
+ const state = {
116
+ context,
117
+ minter: createIdMinter(),
118
+ quoteDepth: 0,
119
+ listItem: void 0,
120
+ list: void 0,
121
+ contentWidthPt: options.contentWidthPt
122
+ };
123
+ return {
124
+ blocks: readContainerChildren(body.children, state),
125
+ source: readStyleResidue(html, context)
126
+ };
127
+ }
128
+ const BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
129
+ "p",
130
+ "h1",
131
+ "h2",
132
+ "h3",
133
+ "h4",
134
+ "h5",
135
+ "h6",
136
+ "ul",
137
+ "ol",
138
+ "dl",
139
+ "table",
140
+ "blockquote",
141
+ "pre",
142
+ "hr",
143
+ "figure",
144
+ "figcaption",
145
+ "div",
146
+ "section",
147
+ "article",
148
+ "aside",
149
+ "nav",
150
+ "img"
151
+ ]);
152
+ function readContainerChildren(nodes, state) {
153
+ const blocks = [];
154
+ let segment = [];
155
+ const flush = () => {
156
+ if (segment.length === 0) return;
157
+ const inline = require_xhtml_inline.buildInlineRuns(segment, {}, state.context);
158
+ segment = [];
159
+ if (inline.runs.every((run) => run.text.trim().length === 0)) return;
160
+ const paragraph = {
161
+ kind: "paragraph",
162
+ runs: inline.runs,
163
+ ...inline.constructs.length > 0 ? { constructs: inline.constructs } : {}
164
+ };
165
+ blocks.push(decorateParagraph(paragraph, state));
166
+ };
167
+ for (const node of nodes) {
168
+ if (node.type === "element" && BLOCK_LEVEL_TAGS.has(node.tag)) {
169
+ flush();
170
+ blocks.push(...readBlockElement(node, state));
171
+ continue;
172
+ }
173
+ if (node.type === "element" || node.type === "text") segment.push(node);
174
+ }
175
+ flush();
176
+ return blocks;
177
+ }
178
+ function headingLevelOf(tag) {
179
+ const match = /^h([1-6])$/u.exec(tag);
180
+ return match?.[1] === void 0 ? void 0 : (0, document_schema_js.clampHeadingLevel)(Number(match[1]));
181
+ }
182
+ function readBlockElement(element, state) {
183
+ const id = require_xml_query.attrValue(element, "id");
184
+ const isFootnoteTarget = id !== void 0 && state.context.footnoteTargetIds.has(id) && !require_xhtml_footnote.isFootnoteAside(element);
185
+ const blocks = readBlockElementInner(element, state);
186
+ if (!isFootnoteTarget) return blocks;
187
+ return [
188
+ {
189
+ kind: "constructStart",
190
+ descriptor: {
191
+ kind: "anchor",
192
+ anchorType: "footnote",
193
+ name: id
194
+ }
195
+ },
196
+ ...blocks,
197
+ { kind: "constructEnd" }
198
+ ];
199
+ }
200
+ function readBlockElementInner(element, state) {
201
+ const headingLevel = headingLevelOf(element.tag);
202
+ if (headingLevel !== void 0) {
203
+ const inline = require_xhtml_inline.buildInlineRuns(element.children, {}, state.context);
204
+ return [decorateParagraph({
205
+ kind: "paragraph",
206
+ headingLevel,
207
+ runs: inline.runs,
208
+ ...inline.constructs.length > 0 ? { constructs: inline.constructs } : {}
209
+ }, state)];
210
+ }
211
+ switch (element.tag) {
212
+ case "p": return readContainerChildren(element.children, state);
213
+ case "hr": return [decorateParagraph({
214
+ kind: "paragraph",
215
+ runs: [],
216
+ styleId: require_xhtml_style_constants.HORIZONTAL_RULE_STYLE_ID
217
+ }, state)];
218
+ case "pre": return [readPre(element, state)];
219
+ case "blockquote": return readBlockquote(element, state);
220
+ case "ul":
221
+ case "ol": return readList(element, state);
222
+ case "dl": return readDefinitionList(element, state);
223
+ case "table": return [readTable(element, state)];
224
+ case "figure": return readContainerChildren(element.children, state);
225
+ case "figcaption": return [decorateParagraph({
226
+ kind: "paragraph",
227
+ runs: require_xhtml_inline.buildInlineRuns(element.children, {}, state.context).runs
228
+ }, state)];
229
+ case "img": {
230
+ const block = readImage(element, state);
231
+ return block === void 0 ? [] : [block];
232
+ }
233
+ case "aside": return readAside(element, state);
234
+ default: return readContainerChildren(element.children, state);
235
+ }
236
+ }
237
+ function readPre(element, state) {
238
+ const languageSource = require_xml_query.findChildElement(element.children, "code") ?? element;
239
+ const codeLanguage = languageFromClass(require_xml_query.attrValue(languageSource, "class"));
240
+ const text = require_xml_entities.decodeEntities(require_xml_query.textContent(element.children));
241
+ return decorateParagraph({
242
+ kind: "paragraph",
243
+ runs: text.length > 0 ? [{
244
+ text,
245
+ fontFamily: require_xhtml_style_constants.MONOSPACE_FONT_FAMILY
246
+ }] : [],
247
+ ...codeLanguage !== void 0 ? { codeLanguage } : {}
248
+ }, state);
249
+ }
250
+ function languageFromClass(className) {
251
+ if (className === void 0) return;
252
+ return /(?:^|\s)language-(\S+)/u.exec(className)?.[1];
253
+ }
254
+ function containsHeading(nodes) {
255
+ for (const node of nodes) {
256
+ if (node.type !== "element") continue;
257
+ if (headingLevelOf(node.tag) !== void 0) return true;
258
+ if (containsHeading(node.children)) return true;
259
+ }
260
+ return false;
261
+ }
262
+ function readBlockquote(element, state) {
263
+ const nestedState = withQuote(state);
264
+ const blocks = readContainerChildren(element.children, nestedState);
265
+ if (containsHeading(element.children)) return blocks;
266
+ return [
267
+ {
268
+ kind: "constructStart",
269
+ descriptor: { kind: "division" }
270
+ },
271
+ ...blocks,
272
+ { kind: "constructEnd" }
273
+ ];
274
+ }
275
+ function readList(element, state) {
276
+ const numId = state.list?.numId ?? state.minter.mintNumId({
277
+ type: element.tag === "ol" ? "ordered" : "bullet",
278
+ start: startAttr(element)
279
+ });
280
+ const level = state.list === void 0 ? 0 : state.list.level + 1;
281
+ const blocks = [];
282
+ for (const child of element.children) {
283
+ if (child.type !== "element" || child.tag !== "li") continue;
284
+ const itemState = withListItem(state, {
285
+ numId,
286
+ level,
287
+ itemId: state.minter.mintItemId()
288
+ });
289
+ blocks.push(...readContainerChildren(child.children, itemState));
290
+ }
291
+ return blocks;
292
+ }
293
+ function startAttr(element) {
294
+ return positiveIntAttr(element, "start");
295
+ }
296
+ function readDefinitionList(element, state) {
297
+ const blocks = [];
298
+ for (const child of element.children) {
299
+ if (child.type !== "element") continue;
300
+ if (child.tag === "dt") {
301
+ const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
302
+ blocks.push(decorateParagraph({
303
+ kind: "paragraph",
304
+ runs: inline.runs
305
+ }, state));
306
+ } else if (child.tag === "dd") {
307
+ const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
308
+ blocks.push(decorateParagraph({
309
+ kind: "paragraph",
310
+ runs: inline.runs,
311
+ indentLeftPt: 36 + state.quoteDepth * 36
312
+ }, state));
313
+ }
314
+ }
315
+ return blocks;
316
+ }
317
+ function readTable(element, state) {
318
+ const rows = [];
319
+ let columnCount = 0;
320
+ for (const section of element.children) {
321
+ if (section.type !== "element") continue;
322
+ const rowContainers = section.tag === "tr" ? [section] : section.tag === "thead" || section.tag === "tbody" || section.tag === "tfoot" ? section.children.filter((c) => c.type === "element" && c.tag === "tr") : [];
323
+ for (const tr of rowContainers) {
324
+ const cells = [];
325
+ for (const cellNode of tr.children) {
326
+ if (cellNode.type !== "element" || cellNode.tag !== "td" && cellNode.tag !== "th") continue;
327
+ const cellStyle = cellNode.tag === "th" ? { bold: true } : {};
328
+ const paragraph = {
329
+ kind: "paragraph",
330
+ runs: require_xhtml_inline.buildInlineRuns(cellNode.children, cellStyle, state.context).runs
331
+ };
332
+ const colSpan = positiveIntAttr(cellNode, "colspan");
333
+ const rowSpan = positiveIntAttr(cellNode, "rowspan");
334
+ cells.push({
335
+ blocks: [paragraph],
336
+ ...colSpan !== void 0 ? { colSpan } : {},
337
+ ...rowSpan !== void 0 ? { rowSpan } : {}
338
+ });
339
+ }
340
+ columnCount = Math.max(columnCount, cells.length);
341
+ rows.push({ cells });
342
+ }
343
+ }
344
+ const width = columnCount > 0 ? state.contentWidthPt / columnCount : state.contentWidthPt;
345
+ return {
346
+ kind: "table",
347
+ rows,
348
+ columnWidthsPt: new Array(Math.max(columnCount, 1)).fill(width)
349
+ };
350
+ }
351
+ function positiveIntAttr(element, name) {
352
+ const raw = require_xml_query.attrValue(element, name);
353
+ if (raw === void 0) return;
354
+ const value = Number.parseInt(raw, 10);
355
+ return Number.isInteger(value) && value > 0 ? value : void 0;
356
+ }
357
+ function readImage(element, state) {
358
+ const src = require_xml_query.attrValue(element, "src");
359
+ const alt = require_xml_query.attrValue(element, "alt");
360
+ if (src === void 0) return;
361
+ const bytes = state.context.resolveImage(src);
362
+ if (bytes === void 0) {
363
+ state.context.sink({
364
+ code: require_diagnostics.EpubDiagnosticCodes.IMAGE_UNRESOLVED,
365
+ severity: "warning",
366
+ message: `<img src="${src}"> names no resolvable manifest part; degraded to its alt text`,
367
+ href: state.context.sourceHref
368
+ });
369
+ return alt === void 0 || alt.length === 0 ? void 0 : decorateParagraph({
370
+ kind: "paragraph",
371
+ runs: [{ text: require_xml_entities.decodeEntities(alt) }]
372
+ }, state);
373
+ }
374
+ const format = require_image_dimensions.detectImageFormat(bytes);
375
+ const dimensions = require_image_dimensions.readImageDimensions(bytes);
376
+ if (format === void 0 || dimensions === void 0) {
377
+ state.context.sink({
378
+ code: require_diagnostics.EpubDiagnosticCodes.IMAGE_FORMAT_UNSUPPORTED,
379
+ severity: "warning",
380
+ message: `<img src="${src}"> is neither a PNG nor a JPEG (document-schema.js's ContentImageBlock supports only those two); degraded to its alt text`,
381
+ href: state.context.sourceHref
382
+ });
383
+ return alt === void 0 || alt.length === 0 ? void 0 : decorateParagraph({
384
+ kind: "paragraph",
385
+ runs: [{ text: require_xml_entities.decodeEntities(alt) }]
386
+ }, state);
387
+ }
388
+ return {
389
+ kind: "image",
390
+ format,
391
+ base64: require_util_base64.bytesToBase64(bytes),
392
+ widthPt: dimensions.widthPx * require_image_dimensions.POINTS_PER_PIXEL,
393
+ heightPt: dimensions.heightPx * require_image_dimensions.POINTS_PER_PIXEL,
394
+ ...alt !== void 0 && alt.length > 0 ? { altText: require_xml_entities.decodeEntities(alt) } : {}
395
+ };
396
+ }
397
+ function readAside(element, state) {
398
+ if (!require_xhtml_footnote.isFootnoteAside(element)) return readContainerChildren(element.children, state);
399
+ const name = require_xml_query.attrValue(element, "id");
400
+ if (name === void 0) {
401
+ state.context.sink({
402
+ code: require_diagnostics.EpubDiagnosticCodes.FOOTNOTE_TARGET_UNRESOLVED,
403
+ severity: "warning",
404
+ message: "a footnote <aside> carries no id and cannot be referenced; read as ordinary content",
405
+ href: state.context.sourceHref
406
+ });
407
+ return readContainerChildren(element.children, state);
408
+ }
409
+ return [
410
+ {
411
+ kind: "constructStart",
412
+ descriptor: {
413
+ kind: "anchor",
414
+ anchorType: "footnote",
415
+ name
416
+ }
417
+ },
418
+ ...readContainerChildren(element.children, state),
419
+ { kind: "constructEnd" }
420
+ ];
421
+ }
422
+ //#endregion
423
+ exports.readXhtmlBody = readXhtmlBody;
@@ -0,0 +1,20 @@
1
+ import { ContentBlock, SourceResidue } from "document-schema.js";
2
+ //#region src/xhtml/read.d.ts
3
+ interface ReadXhtmlBodyOptions {
4
+ readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
5
+ readonly sink: (diagnostic: {
6
+ code: string;
7
+ severity: "info" | "warning";
8
+ message: string;
9
+ href?: string;
10
+ }) => void;
11
+ readonly sourceHref: string;
12
+ readonly contentWidthPt: number;
13
+ }
14
+ interface ReadXhtmlBodyResult {
15
+ readonly blocks: ContentBlock[];
16
+ readonly source: SourceResidue | undefined;
17
+ }
18
+ declare function readXhtmlBody(xml: string, options: ReadXhtmlBodyOptions): ReadXhtmlBodyResult;
19
+ //#endregion
20
+ export { ReadXhtmlBodyOptions, ReadXhtmlBodyResult, readXhtmlBody };
@@ -0,0 +1,20 @@
1
+ import { ContentBlock, SourceResidue } from "document-schema.js";
2
+ //#region src/xhtml/read.d.ts
3
+ interface ReadXhtmlBodyOptions {
4
+ readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
5
+ readonly sink: (diagnostic: {
6
+ code: string;
7
+ severity: "info" | "warning";
8
+ message: string;
9
+ href?: string;
10
+ }) => void;
11
+ readonly sourceHref: string;
12
+ readonly contentWidthPt: number;
13
+ }
14
+ interface ReadXhtmlBodyResult {
15
+ readonly blocks: ContentBlock[];
16
+ readonly source: SourceResidue | undefined;
17
+ }
18
+ declare function readXhtmlBody(xml: string, options: ReadXhtmlBodyOptions): ReadXhtmlBodyResult;
19
+ //#endregion
20
+ export { ReadXhtmlBodyOptions, ReadXhtmlBodyResult, readXhtmlBody };