@bendyline/squisq-formats 1.4.2 → 2.0.1

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 (140) hide show
  1. package/README.md +48 -2
  2. package/dist/chunk-2KPARF2P.js +99 -0
  3. package/dist/chunk-2KPARF2P.js.map +1 -0
  4. package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
  5. package/dist/chunk-4VUWTSGM.js.map +1 -0
  6. package/dist/{chunk-YESS7VY3.js → chunk-ABVI556T.js} +154 -39
  7. package/dist/chunk-ABVI556T.js.map +1 -0
  8. package/dist/{chunk-WWBIKRNP.js → chunk-CRAVSMPZ.js} +13 -11
  9. package/dist/chunk-CRAVSMPZ.js.map +1 -0
  10. package/dist/chunk-FE6OJV6O.js +560 -0
  11. package/dist/chunk-FE6OJV6O.js.map +1 -0
  12. package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
  13. package/dist/chunk-GVS2XXV6.js.map +1 -0
  14. package/dist/{chunk-RTS5XBZ7.js → chunk-HTW2M27H.js} +341 -100
  15. package/dist/chunk-HTW2M27H.js.map +1 -0
  16. package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
  17. package/dist/chunk-LXYLOOST.js.map +1 -0
  18. package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
  19. package/dist/chunk-MLX2BOJC.js.map +1 -0
  20. package/dist/chunk-NKAJPJ4G.js +106 -0
  21. package/dist/chunk-NKAJPJ4G.js.map +1 -0
  22. package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
  23. package/dist/chunk-ODL3SSPT.js +18 -0
  24. package/dist/chunk-ODL3SSPT.js.map +1 -0
  25. package/dist/chunk-QRVN6A6E.js +127 -0
  26. package/dist/chunk-QRVN6A6E.js.map +1 -0
  27. package/dist/chunk-U32AG3G3.js +195 -0
  28. package/dist/chunk-U32AG3G3.js.map +1 -0
  29. package/dist/{chunk-EHLGMSTE.js → chunk-VJJM2SSH.js} +10 -8
  30. package/dist/chunk-VJJM2SSH.js.map +1 -0
  31. package/dist/chunk-VSYHZECT.js +778 -0
  32. package/dist/chunk-VSYHZECT.js.map +1 -0
  33. package/dist/{chunk-SHLPSADL.js → chunk-WC7WULGV.js} +81 -189
  34. package/dist/chunk-WC7WULGV.js.map +1 -0
  35. package/dist/chunk-XKUMNGBW.js +131 -0
  36. package/dist/chunk-XKUMNGBW.js.map +1 -0
  37. package/dist/chunk-YRT7GQ5Y.js +28 -0
  38. package/dist/chunk-YRT7GQ5Y.js.map +1 -0
  39. package/dist/container/index.d.ts +13 -3
  40. package/dist/container/index.js +23 -20
  41. package/dist/container/index.js.map +1 -1
  42. package/dist/docx/index.d.ts +6 -2
  43. package/dist/docx/index.js +5 -3
  44. package/dist/epub/index.d.ts +3 -1
  45. package/dist/epub/index.js +3 -3
  46. package/dist/export-D2NkylDT.d.ts +69 -0
  47. package/dist/extract-H6RXJMHP.js +15 -0
  48. package/dist/extract-H6RXJMHP.js.map +1 -0
  49. package/dist/html/index.d.ts +15 -85
  50. package/dist/html/index.js +3 -3
  51. package/dist/images-7FBWPKE3.js +7 -0
  52. package/dist/images-7FBWPKE3.js.map +1 -0
  53. package/dist/import-DTkDxHmZ.d.ts +84 -0
  54. package/dist/import-K8mfc0fz.d.ts +136 -0
  55. package/dist/index.d.ts +14 -4
  56. package/dist/index.js +33 -12
  57. package/dist/infer/index.d.ts +138 -0
  58. package/dist/infer/index.js +28 -0
  59. package/dist/infer/index.js.map +1 -0
  60. package/dist/layouts-BHrgZ5FS.d.ts +150 -0
  61. package/dist/layouts-QVPK3ZCU.js +12 -0
  62. package/dist/layouts-QVPK3ZCU.js.map +1 -0
  63. package/dist/mapTheme-IR27S6IV.js +11 -0
  64. package/dist/mapTheme-IR27S6IV.js.map +1 -0
  65. package/dist/ooxml/index.d.ts +21 -127
  66. package/dist/ooxml/index.js +31 -9
  67. package/dist/pdf/index.d.ts +3 -1
  68. package/dist/pdf/index.js +1 -1
  69. package/dist/pptx/index.d.ts +8 -108
  70. package/dist/pptx/index.js +13 -4
  71. package/dist/reader-B9L8Ucbj.d.ts +140 -0
  72. package/dist/registry/index.d.ts +60 -8
  73. package/dist/registry/index.js +9 -4
  74. package/dist/themeReader-DJKErl_j.d.ts +56 -0
  75. package/dist/xlsx/index.d.ts +6 -66
  76. package/dist/xlsx/index.js +4 -2
  77. package/dist/zipLimits-BOKCB7qk.d.ts +31 -0
  78. package/package.json +8 -2
  79. package/src/__tests__/container.test.ts +41 -0
  80. package/src/__tests__/convert.test.ts +310 -1
  81. package/src/__tests__/epub.test.ts +102 -1
  82. package/src/__tests__/html.test.ts +62 -0
  83. package/src/__tests__/inferTheme.test.ts +135 -0
  84. package/src/__tests__/lossyWarnings.test.ts +41 -0
  85. package/src/__tests__/ooxml.test.ts +76 -2
  86. package/src/__tests__/ooxmlCancellation.test.ts +113 -0
  87. package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
  88. package/src/__tests__/plainHtml.test.ts +15 -1
  89. package/src/__tests__/plainHtmlBundle.test.ts +18 -0
  90. package/src/__tests__/pptxImport.test.ts +30 -0
  91. package/src/__tests__/pptxInferFixtures.ts +314 -0
  92. package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
  93. package/src/__tests__/zipSafety.test.ts +317 -0
  94. package/src/container/index.ts +37 -32
  95. package/src/docx/export.ts +4 -2
  96. package/src/docx/import.ts +4 -3
  97. package/src/epub/export.ts +94 -25
  98. package/src/html/docsHtmlBundle.ts +6 -2
  99. package/src/html/htmlTemplate.ts +31 -6
  100. package/src/html/imageUtils.ts +14 -5
  101. package/src/html/index.ts +12 -1
  102. package/src/html/plainHtml.ts +7 -3
  103. package/src/html/plainHtmlBundle.ts +11 -9
  104. package/src/index.ts +23 -0
  105. package/src/infer/extract.ts +127 -0
  106. package/src/infer/index.ts +199 -0
  107. package/src/infer/mapTheme.ts +176 -0
  108. package/src/infer/types.ts +27 -0
  109. package/src/ooxml/index.ts +14 -0
  110. package/src/ooxml/readUtils.ts +44 -0
  111. package/src/ooxml/reader.ts +129 -29
  112. package/src/ooxml/themeReader.ts +197 -0
  113. package/src/ooxml/types.ts +8 -9
  114. package/src/ooxml/writer.ts +28 -10
  115. package/src/pdf/export.ts +4 -2
  116. package/src/pdf/import.ts +40 -34
  117. package/src/pptx/export.ts +4 -2
  118. package/src/pptx/import.ts +214 -32
  119. package/src/pptx/index.ts +14 -0
  120. package/src/pptx/layouts.ts +1222 -0
  121. package/src/registry/convert.ts +168 -53
  122. package/src/registry/defaultFormats.ts +135 -45
  123. package/src/registry/index.ts +6 -1
  124. package/src/registry/types.ts +69 -6
  125. package/src/shared/boundedZipArchive.ts +383 -0
  126. package/src/shared/fidelity.ts +130 -0
  127. package/src/shared/zipEntryCount.ts +151 -0
  128. package/src/shared/zipLimits.ts +296 -0
  129. package/src/shared/zipSafety.ts +19 -0
  130. package/src/xlsx/import.ts +3 -2
  131. package/dist/chunk-46FMDOWZ.js.map +0 -1
  132. package/dist/chunk-5LSSWZPU.js.map +0 -1
  133. package/dist/chunk-D7IFPWNZ.js.map +0 -1
  134. package/dist/chunk-EHLGMSTE.js.map +0 -1
  135. package/dist/chunk-MJGRI6XR.js.map +0 -1
  136. package/dist/chunk-RTS5XBZ7.js.map +0 -1
  137. package/dist/chunk-SHLPSADL.js.map +0 -1
  138. package/dist/chunk-WWBIKRNP.js.map +0 -1
  139. package/dist/chunk-YESS7VY3.js.map +0 -1
  140. /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
@@ -1,3 +1,11 @@
1
+ import {
2
+ ConversionError
3
+ } from "./chunk-ODL3SSPT.js";
4
+ import {
5
+ ZipSafetyError,
6
+ openBoundedZipArchive
7
+ } from "./chunk-FE6OJV6O.js";
8
+
1
9
  // src/registry/types.ts
2
10
  var BUILTIN_FORMAT_IDS = [
3
11
  "md",
@@ -12,19 +20,97 @@ var BUILTIN_FORMAT_IDS = [
12
20
  "dbk"
13
21
  ];
14
22
 
15
- // src/registry/errors.ts
16
- var ConversionError = class extends Error {
17
- constructor(code, message, opts = {}) {
18
- super(message);
19
- this.name = "ConversionError";
20
- this.code = code;
21
- this.format = opts.format;
22
- this.hint = opts.hint;
23
- if (opts.cause !== void 0) {
24
- this.cause = opts.cause;
25
- }
26
- }
23
+ // src/shared/fidelity.ts
24
+ var CONTENT_NODE_TYPES = {
25
+ heading: true,
26
+ paragraph: true,
27
+ blockquote: true,
28
+ list: true,
29
+ code: true,
30
+ thematicBreak: true,
31
+ table: true,
32
+ htmlBlock: true,
33
+ math: true,
34
+ definition: true,
35
+ footnoteDefinition: true,
36
+ containerDirective: true,
37
+ leafDirective: true,
38
+ definitionList: true,
39
+ text: true,
40
+ emphasis: true,
41
+ strong: true,
42
+ delete: true,
43
+ inlineCode: true,
44
+ link: true,
45
+ image: true,
46
+ break: true,
47
+ htmlInline: true,
48
+ inlineMath: true,
49
+ footnoteReference: true,
50
+ linkReference: true,
51
+ imageReference: true,
52
+ textDirective: true,
53
+ mention: true,
54
+ inlineIcon: true
55
+ };
56
+ var COMMON_SUPPORTED = [
57
+ "heading",
58
+ "paragraph",
59
+ "blockquote",
60
+ "list",
61
+ "code",
62
+ "thematicBreak",
63
+ "table",
64
+ "htmlBlock",
65
+ "math",
66
+ "text",
67
+ "emphasis",
68
+ "strong",
69
+ "delete",
70
+ "inlineCode",
71
+ "link",
72
+ "image",
73
+ "break",
74
+ "htmlInline",
75
+ "inlineMath"
76
+ ];
77
+ var PROFILE_SUPPORT = {
78
+ docx: /* @__PURE__ */ new Set([...COMMON_SUPPORTED, "footnoteDefinition", "footnoteReference"]),
79
+ pdf: /* @__PURE__ */ new Set([...COMMON_SUPPORTED, "footnoteDefinition", "footnoteReference"]),
80
+ pptx: new Set(COMMON_SUPPORTED),
81
+ epub: new Set(COMMON_SUPPORTED)
27
82
  };
83
+ var PROFILE_LABELS = {
84
+ docx: "DOCX",
85
+ pdf: "PDF",
86
+ pptx: "PPTX",
87
+ epub: "EPUB"
88
+ };
89
+ function markdownFidelityWarnings(document2, profile) {
90
+ const supported = PROFILE_SUPPORT[profile];
91
+ const omitted = /* @__PURE__ */ new Map();
92
+ const visit = (candidate) => {
93
+ if (!candidate || typeof candidate !== "object") return;
94
+ const node = candidate;
95
+ const type = node.type;
96
+ if (type && Object.prototype.hasOwnProperty.call(CONTENT_NODE_TYPES, type)) {
97
+ if (!supported.has(type)) {
98
+ omitted.set(type, (omitted.get(type) ?? 0) + 1);
99
+ return;
100
+ }
101
+ }
102
+ if (Array.isArray(node.children)) {
103
+ for (const child of node.children) visit(child);
104
+ }
105
+ };
106
+ for (const child of document2.children) visit(child);
107
+ if (omitted.size === 0) return [];
108
+ const total = [...omitted.values()].reduce((sum, count) => sum + count, 0);
109
+ const details = [...omitted].map(([type, count]) => `${type} (${count})`).join(", ");
110
+ return [
111
+ `${PROFILE_LABELS[profile]} export omitted ${total} unsupported Markdown node(s): ${details}.`
112
+ ];
113
+ }
28
114
 
29
115
  // src/registry/defaultFormats.ts
30
116
  var MIME = {
@@ -55,6 +141,9 @@ async function toBytes(data) {
55
141
  function resolveThemeId(input, options) {
56
142
  return options.themeId ?? input.doc.themeId;
57
143
  }
144
+ function optionsFor(options, id) {
145
+ return options.formatOptions?.[id] ?? {};
146
+ }
58
147
  async function markdownOf(input) {
59
148
  if (input.markdownDoc) return input.markdownDoc;
60
149
  const { docToMarkdown } = await import("@bendyline/squisq/doc");
@@ -73,6 +162,29 @@ async function collectContainerImages(container) {
73
162
  }
74
163
  return images;
75
164
  }
165
+ async function collectContainerDocxImages(container) {
166
+ const images = await collectContainerImages(container);
167
+ const { extToMime } = await import("./images-7FBWPKE3.js");
168
+ return new Map(
169
+ [...images].map(([path, data]) => [
170
+ path,
171
+ { data, contentType: extToMime(path.split(".").pop() ?? "") }
172
+ ])
173
+ );
174
+ }
175
+ async function snapshotContainerWithDocument(input) {
176
+ const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
177
+ const snapshot = new MemoryContentContainer();
178
+ const entries = await input.container.listFiles();
179
+ for (const entry of entries) {
180
+ const data = await input.container.readFile(entry.path);
181
+ if (data) await snapshot.writeFile(entry.path, new Uint8Array(data), entry.mimeType);
182
+ }
183
+ const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
184
+ const documentPath = await input.container.getDocumentPath() ?? "index.md";
185
+ await snapshot.writeDocument(stringifyMarkdown(await markdownOf(input)), documentPath);
186
+ return snapshot;
187
+ }
76
188
  async function requirePlayerScript(options, format) {
77
189
  const script = await options.resolvePlayerScript?.();
78
190
  if (!script) {
@@ -86,19 +198,27 @@ async function requirePlayerScript(options, format) {
86
198
  function ok(bytes, mimeType, warnings = []) {
87
199
  return { bytes, mimeType, suggestedFilename: "", warnings };
88
200
  }
201
+ function pptxImportOptionsFrom(options) {
202
+ const raw = optionsFor(options, "pptx");
203
+ return {
204
+ ...raw,
205
+ inferTheme: raw?.inferTheme !== false,
206
+ inferLayouts: raw?.inferLayouts !== false
207
+ };
208
+ }
89
209
  function defaultFormats() {
90
210
  const md = {
91
211
  id: "md",
92
212
  label: "Markdown",
93
213
  mimeType: MIME.md,
94
214
  extensions: [".md", ".markdown"],
95
- async importDoc(data) {
215
+ async importDoc(data, options) {
96
216
  const { parseMarkdown } = await import("@bendyline/squisq/markdown");
97
- return parseMarkdown(new TextDecoder().decode(data));
217
+ return parseMarkdown(new TextDecoder().decode(data), optionsFor(options, "md").parse);
98
218
  },
99
- async exportDoc(input) {
219
+ async exportDoc(input, options) {
100
220
  const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
101
- const text = stringifyMarkdown(await markdownOf(input));
221
+ const text = stringifyMarkdown(await markdownOf(input), optionsFor(options, "md").stringify);
102
222
  return ok(new TextEncoder().encode(text), MIME.md);
103
223
  }
104
224
  };
@@ -107,20 +227,28 @@ function defaultFormats() {
107
227
  label: "Word (DOCX)",
108
228
  mimeType: MIME.docx,
109
229
  extensions: [".docx"],
110
- async importContainer(data) {
230
+ async importContainer(data, options) {
111
231
  const { docxToContainer } = await import("./docx/index.js");
112
- return docxToContainer(data);
232
+ return docxToContainer(data, optionsFor(options, "docx"));
113
233
  },
114
- async importDoc(data) {
234
+ async importDoc(data, options) {
115
235
  const { docxToMarkdownDoc } = await import("./docx/index.js");
116
- return docxToMarkdownDoc(data);
236
+ return docxToMarkdownDoc(data, optionsFor(options, "docx"));
117
237
  },
118
238
  async exportDoc(input, options) {
119
239
  const { markdownDocToDocx } = await import("./docx/index.js");
120
- const buf = await markdownDocToDocx(await markdownOf(input), {
121
- themeId: resolveThemeId(input, options)
240
+ const raw = optionsFor(options, "docx");
241
+ const markdownDoc = await markdownOf(input);
242
+ const containerImages = await collectContainerDocxImages(input.container);
243
+ const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
244
+ const buf = await markdownDocToDocx(markdownDoc, {
245
+ ...raw,
246
+ ...options.title !== void 0 ? { title: options.title } : {},
247
+ themeId: resolveThemeId(input, options),
248
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry,
249
+ images
122
250
  });
123
- return ok(await toBytes(buf), MIME.docx);
251
+ return ok(await toBytes(buf), MIME.docx, markdownFidelityWarnings(markdownDoc, "docx"));
124
252
  }
125
253
  };
126
254
  const pdf = {
@@ -128,20 +256,25 @@ function defaultFormats() {
128
256
  label: "PDF",
129
257
  mimeType: MIME.pdf,
130
258
  extensions: [".pdf"],
131
- async importContainer(data) {
259
+ async importContainer(data, options) {
132
260
  const { pdfToContainer } = await import("./pdf/index.js");
133
- return pdfToContainer(data);
261
+ return pdfToContainer(data, optionsFor(options, "pdf"));
134
262
  },
135
- async importDoc(data) {
263
+ async importDoc(data, options) {
136
264
  const { pdfToMarkdownDoc } = await import("./pdf/index.js");
137
- return pdfToMarkdownDoc(data);
265
+ return pdfToMarkdownDoc(data, optionsFor(options, "pdf"));
138
266
  },
139
267
  async exportDoc(input, options) {
140
268
  const { markdownDocToPdf } = await import("./pdf/index.js");
141
- const buf = await markdownDocToPdf(await markdownOf(input), {
142
- themeId: resolveThemeId(input, options)
269
+ const raw = optionsFor(options, "pdf");
270
+ const markdownDoc = await markdownOf(input);
271
+ const buf = await markdownDocToPdf(markdownDoc, {
272
+ ...raw,
273
+ ...options.title !== void 0 ? { title: options.title } : {},
274
+ themeId: resolveThemeId(input, options),
275
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry
143
276
  });
144
- return ok(await toBytes(buf), MIME.pdf);
277
+ return ok(await toBytes(buf), MIME.pdf, markdownFidelityWarnings(markdownDoc, "pdf"));
145
278
  }
146
279
  };
147
280
  const pptx = {
@@ -149,22 +282,28 @@ function defaultFormats() {
149
282
  label: "PowerPoint (PPTX)",
150
283
  mimeType: MIME.pptx,
151
284
  extensions: [".pptx"],
152
- async importContainer(data) {
285
+ async importContainer(data, options) {
153
286
  const { pptxToContainer } = await import("./pptx/index.js");
154
- return pptxToContainer(data);
287
+ return pptxToContainer(data, pptxImportOptionsFrom(options));
155
288
  },
156
- async importDoc(data) {
289
+ async importDoc(data, options) {
157
290
  const { pptxToMarkdownDoc } = await import("./pptx/index.js");
158
- return pptxToMarkdownDoc(data);
291
+ return pptxToMarkdownDoc(data, pptxImportOptionsFrom(options));
159
292
  },
160
293
  async exportDoc(input, options) {
161
294
  const { markdownDocToPptx } = await import("./pptx/index.js");
162
- const images = await collectContainerImages(input.container);
163
- const buf = await markdownDocToPptx(await markdownOf(input), {
295
+ const raw = optionsFor(options, "pptx");
296
+ const markdownDoc = await markdownOf(input);
297
+ const containerImages = await collectContainerImages(input.container);
298
+ const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
299
+ const buf = await markdownDocToPptx(markdownDoc, {
300
+ ...raw,
301
+ ...options.title !== void 0 ? { title: options.title } : {},
164
302
  themeId: resolveThemeId(input, options),
303
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry,
165
304
  images
166
305
  });
167
- return ok(await toBytes(buf), MIME.pptx);
306
+ return ok(await toBytes(buf), MIME.pptx, markdownFidelityWarnings(markdownDoc, "pptx"));
168
307
  }
169
308
  };
170
309
  const xlsx = {
@@ -172,18 +311,21 @@ function defaultFormats() {
172
311
  label: "Excel (XLSX)",
173
312
  mimeType: MIME.xlsx,
174
313
  extensions: [".xlsx"],
175
- async importDoc(data) {
314
+ async importDoc(data, options) {
176
315
  const { xlsxToMarkdownDoc } = await import("./xlsx/index.js");
177
- return xlsxToMarkdownDoc(data);
316
+ return xlsxToMarkdownDoc(data, optionsFor(options, "xlsx"));
178
317
  },
179
- async exportDoc(input) {
318
+ async exportDoc(input, options) {
180
319
  const { markdownDocToXlsx } = await import("./xlsx/index.js");
181
320
  const markdownDoc = await markdownOf(input);
182
321
  const omitted = markdownDoc.children.filter(
183
322
  (n) => n.type !== "table" && n.type !== "heading"
184
323
  ).length;
185
324
  const warnings = omitted > 0 ? [`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`] : [];
186
- const blob = await markdownDocToXlsx(markdownDoc);
325
+ const blob = await markdownDocToXlsx(markdownDoc, {
326
+ ...optionsFor(options, "xlsx"),
327
+ ...options.title !== void 0 ? { title: options.title } : {}
328
+ });
187
329
  return ok(await toBytes(blob), MIME.xlsx, warnings);
188
330
  }
189
331
  };
@@ -192,21 +334,22 @@ function defaultFormats() {
192
334
  label: "CSV",
193
335
  mimeType: MIME.csv,
194
336
  extensions: [".csv"],
195
- async importDoc(data) {
337
+ async importDoc(data, options) {
196
338
  const { csvToMarkdownDoc } = await import("./csv/index.js");
197
- return csvToMarkdownDoc(data);
339
+ return csvToMarkdownDoc(data, optionsFor(options, "csv"));
198
340
  },
199
- async exportDoc(input) {
341
+ async exportDoc(input, options) {
200
342
  const { markdownDocToCsv } = await import("./csv/index.js");
201
343
  const markdownDoc = await markdownOf(input);
344
+ const raw = optionsFor(options, "csv");
202
345
  const tableCount = markdownDoc.children.filter((n) => n.type === "table").length;
203
346
  const warnings = [];
204
- if (tableCount > 1) {
347
+ if (tableCount > 1 && raw.tableIndex === void 0) {
205
348
  warnings.push(
206
349
  `Document has ${tableCount} tables; CSV export emitted only the first. Use the csv converter's tableIndex option to select another.`
207
350
  );
208
351
  }
209
- const text = markdownDocToCsv(markdownDoc);
352
+ const text = markdownDocToCsv(markdownDoc, raw);
210
353
  return ok(new TextEncoder().encode(text), MIME.csv, warnings);
211
354
  }
212
355
  };
@@ -215,22 +358,26 @@ function defaultFormats() {
215
358
  label: "HTML (single file)",
216
359
  mimeType: MIME.html,
217
360
  extensions: [".html", ".htm"],
218
- async importDoc(data) {
361
+ async importDoc(data, options) {
219
362
  const { htmlToMarkdownDoc } = await import("./html/index.js");
220
- return htmlToMarkdownDoc(data);
363
+ return htmlToMarkdownDoc(data, optionsFor(options, "html"));
221
364
  },
222
365
  // importContainer omitted: HTML import already inlines images as data URIs,
223
366
  // so there is nothing to extract into a container this wave.
224
367
  async exportDoc(input, options) {
225
368
  const playerScript = await requirePlayerScript(options, "html");
226
369
  const { docToHtml } = await import("./html/index.js");
227
- const images = await collectContainerImages(input.container);
370
+ const raw = optionsFor(options, "html");
371
+ const containerImages = await collectContainerImages(input.container);
372
+ const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
228
373
  const htmlText = docToHtml(input.doc, {
374
+ ...raw,
229
375
  playerScript,
230
376
  images,
231
377
  title: options.title ?? input.baseName,
232
- mode: "static",
233
- themeId: resolveThemeId(input, options)
378
+ mode: raw.mode ?? "static",
379
+ themeId: resolveThemeId(input, options),
380
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry
234
381
  });
235
382
  return ok(new TextEncoder().encode(htmlText), MIME.html);
236
383
  }
@@ -243,13 +390,17 @@ function defaultFormats() {
243
390
  async exportDoc(input, options) {
244
391
  const playerScript = await requirePlayerScript(options, "htmlzip");
245
392
  const { docToHtmlZip } = await import("./html/index.js");
246
- const images = await collectContainerImages(input.container);
393
+ const raw = optionsFor(options, "htmlzip");
394
+ const containerImages = await collectContainerImages(input.container);
395
+ const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
247
396
  const blob = await docToHtmlZip(input.doc, {
397
+ ...raw,
248
398
  playerScript,
249
399
  images,
250
400
  title: options.title ?? input.baseName,
251
- mode: "static",
252
- themeId: resolveThemeId(input, options)
401
+ mode: raw.mode ?? "static",
402
+ themeId: resolveThemeId(input, options),
403
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry
253
404
  });
254
405
  return ok(await toBytes(blob), MIME.zip);
255
406
  }
@@ -261,13 +412,18 @@ function defaultFormats() {
261
412
  extensions: [".epub"],
262
413
  async exportDoc(input, options) {
263
414
  const { markdownDocToEpub } = await import("./epub/index.js");
264
- const images = await collectContainerImages(input.container);
265
- const buf = await markdownDocToEpub(await markdownOf(input), {
415
+ const raw = optionsFor(options, "epub");
416
+ const markdownDoc = await markdownOf(input);
417
+ const containerImages = await collectContainerImages(input.container);
418
+ const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
419
+ const buf = await markdownDocToEpub(markdownDoc, {
420
+ ...raw,
266
421
  title: options.title ?? input.baseName,
267
422
  themeId: resolveThemeId(input, options),
423
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry,
268
424
  images
269
425
  });
270
- return ok(await toBytes(buf), MIME.epub);
426
+ return ok(await toBytes(buf), MIME.epub, markdownFidelityWarnings(markdownDoc, "epub"));
271
427
  }
272
428
  };
273
429
  const dbk = {
@@ -275,13 +431,16 @@ function defaultFormats() {
275
431
  label: "Squisq container (DBK)",
276
432
  mimeType: MIME.zip,
277
433
  extensions: [".dbk", ".zip"],
278
- async importContainer(data) {
434
+ async importContainer(data, options) {
279
435
  const { zipToContainer } = await import("./container/index.js");
280
- return zipToContainer(data);
436
+ return zipToContainer(data, optionsFor(options, "dbk"));
281
437
  },
282
- async exportDoc(input) {
438
+ async exportDoc(input, options) {
283
439
  const { containerToZip } = await import("./container/index.js");
284
- const blob = await containerToZip(input.container);
440
+ const blob = await containerToZip(
441
+ await snapshotContainerWithDocument(input),
442
+ optionsFor(options, "dbk")
443
+ );
285
444
  return ok(await toBytes(blob), MIME.zip);
286
445
  }
287
446
  };
@@ -323,6 +482,16 @@ function defaultRegistry() {
323
482
  }
324
483
 
325
484
  // src/registry/convert.ts
485
+ function markdownFormatOptions(options) {
486
+ return options.formatOptions?.md ?? {};
487
+ }
488
+ function throwIfAborted(signal) {
489
+ if (!signal?.aborted) return;
490
+ if (signal.reason !== void 0) throw signal.reason;
491
+ const error = new Error("Document conversion was cancelled.");
492
+ error.name = "AbortError";
493
+ throw error;
494
+ }
326
495
  function hasPrefix(bytes, prefix) {
327
496
  if (bytes.length < prefix.length) return false;
328
497
  for (let i = 0; i < prefix.length; i++) {
@@ -333,13 +502,19 @@ function hasPrefix(bytes, prefix) {
333
502
  var ZIP_MAGIC = [80, 75, 3, 4];
334
503
  var PDF_MAGIC = [37, 80, 68, 70];
335
504
  async function sniffZip(bytes) {
336
- const JSZip = (await import("jszip")).default;
337
- const zip = await JSZip.loadAsync(bytes).catch(() => {
338
- throw new ConversionError("invalid-input", "Input is not a readable ZIP archive.");
505
+ const archive = await openBoundedZipArchive(bytes).catch((cause) => {
506
+ if (cause instanceof ZipSafetyError && cause.code !== "invalid-archive") throw cause;
507
+ throw new ConversionError("invalid-input", "Input is not a readable ZIP archive.", { cause });
339
508
  });
340
- const contentTypes = zip.file("[Content_Types].xml");
509
+ const contentTypes = archive.entries.find((entry) => entry.path === "[Content_Types].xml");
341
510
  if (!contentTypes) return "dbk";
342
- const xml = await contentTypes.async("string");
511
+ const maxContentTypesBytes = 1024 * 1024;
512
+ if ((contentTypes.declaredSize ?? 0) > maxContentTypesBytes) {
513
+ throw new ConversionError("invalid-input", "OOXML content-types metadata is too large.");
514
+ }
515
+ const xmlBytes = await archive.read(contentTypes.path, maxContentTypesBytes);
516
+ if (!xmlBytes) return "dbk";
517
+ const xml = new TextDecoder().decode(xmlBytes);
343
518
  if (xml.includes("wordprocessingml")) return "docx";
344
519
  if (xml.includes("presentationml")) return "pptx";
345
520
  if (xml.includes("spreadsheetml")) return "xlsx";
@@ -357,13 +532,15 @@ async function detectByteFormat(bytes, filename, options, registry) {
357
532
  return "md";
358
533
  }
359
534
  function extractExt(filename) {
360
- const base = filename.split("/").pop() ?? filename;
535
+ const normalized = filename.replace(/\\/g, "/");
536
+ const base = normalized.split("/").pop() ?? normalized;
361
537
  const dot = base.lastIndexOf(".");
362
538
  if (dot <= 0) return "";
363
539
  return base.slice(dot + 1).toLowerCase();
364
540
  }
365
541
  function baseNameOf(filename) {
366
- const base = filename.split("/").pop() ?? filename;
542
+ const normalized = filename.replace(/\\/g, "/");
543
+ const base = normalized.split("/").pop() ?? normalized;
367
544
  const dot = base.lastIndexOf(".");
368
545
  return dot > 0 ? base.slice(0, dot) : base;
369
546
  }
@@ -403,7 +580,7 @@ async function normalizeBytes(source, options, registry) {
403
580
  );
404
581
  }
405
582
  const text = await container.readDocument();
406
- markdownDoc = text ? parseMarkdown(text) : { type: "document", children: [] };
583
+ markdownDoc = text ? parseMarkdown(text, markdownFormatOptions(options).parse) : { type: "document", children: [] };
407
584
  } else {
408
585
  markdownDoc = await fromDef.importDoc(buffer, options);
409
586
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
@@ -422,7 +599,7 @@ async function normalizeMarkdown(source, options) {
422
599
  const { markdownToDoc } = await import("@bendyline/squisq/doc");
423
600
  const { parseMarkdown } = await import("@bendyline/squisq/markdown");
424
601
  const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
425
- const markdownDoc = typeof source.markdown === "string" ? parseMarkdown(source.markdown) : source.markdown;
602
+ const markdownDoc = typeof source.markdown === "string" ? parseMarkdown(source.markdown, markdownFormatOptions(options).parse) : source.markdown;
426
603
  let container = source.container;
427
604
  if (!container) {
428
605
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
@@ -457,11 +634,11 @@ async function normalize(source, options, registry) {
457
634
  return normalizeDoc(source);
458
635
  }
459
636
  }
460
- async function applyTransformStyle(input, transformStyle, themeId) {
637
+ async function applyTransformStyle(input, transformStyle, themeId, registry) {
461
638
  const { applyTransform, extractDocImages } = await import("@bendyline/squisq/transform");
462
639
  const { docToMarkdown, markdownToDoc } = await import("@bendyline/squisq/doc");
463
640
  const images = extractDocImages(input.doc.blocks);
464
- const result = applyTransform(input.doc, transformStyle, { themeId, images });
641
+ const result = applyTransform(input.doc, transformStyle, { themeId, images, registry });
465
642
  input.doc = result.doc;
466
643
  const markdownDoc = docToMarkdown(result.doc);
467
644
  input.markdownDoc = markdownDoc;
@@ -473,8 +650,77 @@ async function applyTransformStyle(input, transformStyle, themeId) {
473
650
  }
474
651
  return [];
475
652
  }
476
- async function convert(source, to, options = {}) {
653
+ async function prepareConversion(source, options = {}) {
654
+ return prepareConversionInternal(source, options);
655
+ }
656
+ async function prepareConversionInternal(source, options, failureFormat) {
657
+ throwIfAborted(options.signal);
477
658
  const registry = options.registry ?? defaultRegistry();
659
+ let normalized;
660
+ try {
661
+ normalized = await normalize(source, options, registry);
662
+ throwIfAborted(options.signal);
663
+ } catch (err) {
664
+ throwIfAborted(options.signal);
665
+ if (err instanceof ConversionError) throw err;
666
+ const message = err instanceof Error ? err.message : String(err);
667
+ throw new ConversionError("invalid-input", message, {
668
+ format: options.from,
669
+ cause: err
670
+ });
671
+ }
672
+ const { input, warnings } = normalized;
673
+ if (options.themeId && !input.doc.themeId) {
674
+ input.doc = { ...input.doc, themeId: options.themeId };
675
+ }
676
+ try {
677
+ if (options.transformStyle) {
678
+ warnings.push(
679
+ ...await applyTransformStyle(
680
+ input,
681
+ options.transformStyle,
682
+ options.themeId ?? input.doc.themeId,
683
+ options.transformRegistry
684
+ )
685
+ );
686
+ throwIfAborted(options.signal);
687
+ }
688
+ } catch (err) {
689
+ throwIfAborted(options.signal);
690
+ if (err instanceof ConversionError) throw err;
691
+ const message = err instanceof Error ? err.message : String(err);
692
+ throw new ConversionError("conversion-failed", message, {
693
+ format: failureFormat,
694
+ cause: err
695
+ });
696
+ }
697
+ const preparedWarnings = Object.freeze([...warnings]);
698
+ const preparedInput = { ...input };
699
+ return Object.freeze({
700
+ async convert(to, targetOptions = {}) {
701
+ const target = requireExportTarget(registry, to);
702
+ const exportOptions = mergePreparedExportOptions(options, targetOptions);
703
+ throwIfAborted(exportOptions.signal);
704
+ let result;
705
+ try {
706
+ result = await target.exportDoc({ ...preparedInput }, exportOptions);
707
+ throwIfAborted(exportOptions.signal);
708
+ } catch (err) {
709
+ throwIfAborted(exportOptions.signal);
710
+ if (err instanceof ConversionError) throw err;
711
+ const message = err instanceof Error ? err.message : String(err);
712
+ throw new ConversionError("conversion-failed", message, { format: to, cause: err });
713
+ }
714
+ const ext = target.extensions[0]?.replace(/^\.+/, "") ?? to;
715
+ return {
716
+ ...result,
717
+ suggestedFilename: `${preparedInput.baseName}.${ext}`,
718
+ warnings: [...preparedWarnings, ...result.warnings]
719
+ };
720
+ }
721
+ });
722
+ }
723
+ function requireExportTarget(registry, to) {
478
724
  const target = registry.get(to);
479
725
  if (!target) {
480
726
  throw new ConversionError("unknown-format", `Unknown target format "${to}".`, { format: to });
@@ -486,41 +732,36 @@ async function convert(source, to, options = {}) {
486
732
  { format: to, hint: "This format is import-only." }
487
733
  );
488
734
  }
489
- const { input, warnings } = await normalize(source, options, registry);
490
- if (options.themeId && !input.doc.themeId) {
491
- input.doc.themeId = options.themeId;
492
- }
493
- if (options.transformStyle) {
494
- warnings.push(
495
- ...await applyTransformStyle(
496
- input,
497
- options.transformStyle,
498
- options.themeId ?? input.doc.themeId
499
- )
500
- );
501
- }
502
- let result;
503
- try {
504
- result = await target.exportDoc(input, options);
505
- } catch (err) {
506
- if (err instanceof ConversionError) throw err;
507
- const message = err instanceof Error ? err.message : String(err);
508
- throw new ConversionError("conversion-failed", message, { format: to, cause: err });
509
- }
510
- const ext = target.extensions[0]?.replace(/^\.+/, "") ?? to;
735
+ return target;
736
+ }
737
+ function mergePreparedExportOptions(prepared, target) {
511
738
  return {
512
- ...result,
513
- suggestedFilename: `${input.baseName}.${ext}`,
514
- warnings: [...warnings, ...result.warnings]
739
+ ...prepared,
740
+ signal: mergeAbortSignals(prepared.signal, target.signal),
741
+ title: target.title ?? prepared.title,
742
+ resolvePlayerScript: target.resolvePlayerScript ?? prepared.resolvePlayerScript,
743
+ formatOptions: target.formatOptions === void 0 ? prepared.formatOptions : { ...prepared.formatOptions ?? {}, ...target.formatOptions }
515
744
  };
516
745
  }
746
+ function mergeAbortSignals(preparedSignal, targetSignal) {
747
+ if (!preparedSignal) return targetSignal;
748
+ if (!targetSignal || preparedSignal === targetSignal) return preparedSignal;
749
+ return AbortSignal.any([preparedSignal, targetSignal]);
750
+ }
751
+ async function convert(source, to, options = {}) {
752
+ throwIfAborted(options.signal);
753
+ const registry = options.registry ?? defaultRegistry();
754
+ requireExportTarget(registry, to);
755
+ const prepared = await prepareConversionInternal(source, { ...options, registry }, to);
756
+ return prepared.convert(to);
757
+ }
517
758
 
518
759
  export {
519
760
  BUILTIN_FORMAT_IDS,
520
- ConversionError,
521
761
  defaultFormats,
522
762
  createRegistry,
523
763
  defaultRegistry,
764
+ prepareConversion,
524
765
  convert
525
766
  };
526
- //# sourceMappingURL=chunk-RTS5XBZ7.js.map
767
+ //# sourceMappingURL=chunk-HTW2M27H.js.map