@bendyline/squisq-formats 2.1.0 → 2.2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +1 -1
  4. package/dist/{chunk-NNHKUXKA.js → chunk-26ISNJ7Y.js} +85 -65
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-WQSHGBLN.js → chunk-3NKXBZSR.js} +193 -42
  7. package/dist/{chunk-KURGXM4I.js → chunk-4V3KCHAP.js} +3 -4
  8. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  9. package/dist/{chunk-EW54IRRS.js → chunk-6S6GU3ZG.js} +5 -6
  10. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  11. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  12. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  13. package/dist/{chunk-SC67HYQJ.js → chunk-EJTNGKEA.js} +5 -8
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  16. package/dist/{chunk-RLU7UFYU.js → chunk-IPN56VLW.js} +83 -58
  17. package/dist/{chunk-DTDF6QDP.js → chunk-JE6LSIHE.js} +81 -20
  18. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  19. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  20. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  21. package/dist/chunk-OGS5VCGJ.js +446 -0
  22. package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
  23. package/dist/{chunk-74GO3FVS.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-QFLDYKCR.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-7ARKUCQT.js → chunk-X2DEAXNK.js} +54 -2
  27. package/dist/container/index.js +1 -2
  28. package/dist/csv/index.d.ts +27 -2
  29. package/dist/csv/index.js +1 -2
  30. package/dist/docx/index.d.ts +5 -1
  31. package/dist/docx/index.js +9 -11
  32. package/dist/epub/index.d.ts +2 -0
  33. package/dist/epub/index.js +5 -6
  34. package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
  35. package/dist/extract-MN7LA3NL.js +13 -0
  36. package/dist/html/index.d.ts +11 -4
  37. package/dist/html/index.js +3 -4
  38. package/dist/images-ESPQKVTW.js +6 -0
  39. package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
  40. package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -26
  43. package/dist/infer/index.d.ts +3 -3
  44. package/dist/infer/index.js +7 -9
  45. package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
  46. package/dist/layouts-DRWZGSPD.js +10 -0
  47. package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
  48. package/dist/ooxml/index.d.ts +3 -3
  49. package/dist/ooxml/index.js +14 -13
  50. package/dist/pdf/index.d.ts +18 -0
  51. package/dist/pdf/index.js +2 -3
  52. package/dist/pptx/index.d.ts +4 -4
  53. package/dist/pptx/index.js +11 -13
  54. package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
  55. package/dist/registry/index.d.ts +21 -5
  56. package/dist/registry/index.js +9 -6
  57. package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
  58. package/dist/xlsx/index.d.ts +3 -3
  59. package/dist/xlsx/index.js +6 -7
  60. package/package.json +6 -3
  61. package/dist/chunk-4VUWTSGM.js.map +0 -1
  62. package/dist/chunk-6M7Z25LA.js +0 -46
  63. package/dist/chunk-6M7Z25LA.js.map +0 -1
  64. package/dist/chunk-74GO3FVS.js.map +0 -1
  65. package/dist/chunk-7ARKUCQT.js.map +0 -1
  66. package/dist/chunk-DTDF6QDP.js.map +0 -1
  67. package/dist/chunk-EW54IRRS.js.map +0 -1
  68. package/dist/chunk-FE6OJV6O.js.map +0 -1
  69. package/dist/chunk-GVS2XXV6.js.map +0 -1
  70. package/dist/chunk-KURGXM4I.js.map +0 -1
  71. package/dist/chunk-MLX2BOJC.js.map +0 -1
  72. package/dist/chunk-NKAJPJ4G.js.map +0 -1
  73. package/dist/chunk-NNHKUXKA.js.map +0 -1
  74. package/dist/chunk-O3GVVND4.js.map +0 -1
  75. package/dist/chunk-ODL3SSPT.js.map +0 -1
  76. package/dist/chunk-PN52A5AA.js.map +0 -1
  77. package/dist/chunk-QFLDYKCR.js.map +0 -1
  78. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  79. package/dist/chunk-RLU7UFYU.js.map +0 -1
  80. package/dist/chunk-SC67HYQJ.js.map +0 -1
  81. package/dist/chunk-SSUPBUF5.js.map +0 -1
  82. package/dist/chunk-U4MRIFKL.js.map +0 -1
  83. package/dist/chunk-UGYF5AZE.js +0 -275
  84. package/dist/chunk-UGYF5AZE.js.map +0 -1
  85. package/dist/chunk-WQSHGBLN.js.map +0 -1
  86. package/dist/chunk-YRT7GQ5Y.js +0 -28
  87. package/dist/chunk-YRT7GQ5Y.js.map +0 -1
  88. package/dist/container/index.js.map +0 -1
  89. package/dist/csv/index.js.map +0 -1
  90. package/dist/docx/index.js.map +0 -1
  91. package/dist/epub/index.js.map +0 -1
  92. package/dist/extract-OJ7ZQV6P.js +0 -15
  93. package/dist/extract-OJ7ZQV6P.js.map +0 -1
  94. package/dist/html/index.js.map +0 -1
  95. package/dist/images-7FBWPKE3.js +0 -7
  96. package/dist/images-7FBWPKE3.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/infer/index.js.map +0 -1
  99. package/dist/layouts-5VDIRPIJ.js +0 -12
  100. package/dist/layouts-5VDIRPIJ.js.map +0 -1
  101. package/dist/mapTheme-IR27S6IV.js.map +0 -1
  102. package/dist/ooxml/index.js.map +0 -1
  103. package/dist/pdf/index.js.map +0 -1
  104. package/dist/pptx/index.js.map +0 -1
  105. package/dist/registry/index.js.map +0 -1
  106. package/dist/xlsx/index.js.map +0 -1
  107. package/src/__tests__/container.test.ts +0 -230
  108. package/src/__tests__/convert.test.ts +0 -495
  109. package/src/__tests__/csvImport.test.ts +0 -84
  110. package/src/__tests__/docxExport.test.ts +0 -491
  111. package/src/__tests__/docxImport.test.ts +0 -531
  112. package/src/__tests__/epub.test.ts +0 -649
  113. package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
  114. package/src/__tests__/formatRegistry.test.ts +0 -174
  115. package/src/__tests__/html.test.ts +0 -439
  116. package/src/__tests__/htmlImport.test.ts +0 -57
  117. package/src/__tests__/inferTheme.test.ts +0 -135
  118. package/src/__tests__/lossyWarnings.test.ts +0 -146
  119. package/src/__tests__/ooxml.test.ts +0 -271
  120. package/src/__tests__/ooxmlCancellation.test.ts +0 -113
  121. package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
  122. package/src/__tests__/pdfExport.test.ts +0 -322
  123. package/src/__tests__/pdfImport.test.ts +0 -384
  124. package/src/__tests__/plainHtml.test.ts +0 -417
  125. package/src/__tests__/plainHtmlBundle.test.ts +0 -253
  126. package/src/__tests__/pptxExport.test.ts +0 -138
  127. package/src/__tests__/pptxImport.test.ts +0 -145
  128. package/src/__tests__/pptxInferFixtures.ts +0 -314
  129. package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
  130. package/src/__tests__/roundTrip.test.ts +0 -201
  131. package/src/__tests__/roundTripAssets.test.ts +0 -50
  132. package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
  133. package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
  134. package/src/__tests__/roundTripMatrix.test.ts +0 -142
  135. package/src/__tests__/sharedContainer.test.ts +0 -41
  136. package/src/__tests__/sharedImages.test.ts +0 -61
  137. package/src/__tests__/xlsxExport.test.ts +0 -164
  138. package/src/__tests__/xlsxImport.test.ts +0 -80
  139. package/src/__tests__/zipSafety.test.ts +0 -317
  140. package/src/container/index.ts +0 -94
  141. package/src/csv/index.ts +0 -188
  142. package/src/docx/export.ts +0 -1375
  143. package/src/docx/import.ts +0 -1250
  144. package/src/docx/index.ts +0 -26
  145. package/src/docx/styles.ts +0 -145
  146. package/src/epub/export.ts +0 -968
  147. package/src/epub/index.ts +0 -20
  148. package/src/html/docsHtmlBundle.ts +0 -373
  149. package/src/html/htmlTemplate.ts +0 -385
  150. package/src/html/imageUtils.ts +0 -61
  151. package/src/html/import.ts +0 -297
  152. package/src/html/index.ts +0 -212
  153. package/src/html/plainHtml.ts +0 -790
  154. package/src/html/plainHtmlBundle.ts +0 -421
  155. package/src/index.ts +0 -109
  156. package/src/infer/extract.ts +0 -127
  157. package/src/infer/index.ts +0 -199
  158. package/src/infer/mapTheme.ts +0 -176
  159. package/src/infer/types.ts +0 -27
  160. package/src/ooxml/index.ts +0 -111
  161. package/src/ooxml/namespaces.ts +0 -217
  162. package/src/ooxml/readUtils.ts +0 -44
  163. package/src/ooxml/reader.ts +0 -318
  164. package/src/ooxml/themeReader.ts +0 -197
  165. package/src/ooxml/types.ts +0 -103
  166. package/src/ooxml/writer.ts +0 -339
  167. package/src/ooxml/xmlUtils.ts +0 -123
  168. package/src/pdf/export.ts +0 -1084
  169. package/src/pdf/import.ts +0 -1164
  170. package/src/pdf/index.ts +0 -29
  171. package/src/pdf/styles.ts +0 -180
  172. package/src/pptx/export.ts +0 -1184
  173. package/src/pptx/import.ts +0 -455
  174. package/src/pptx/index.ts +0 -52
  175. package/src/pptx/layouts.ts +0 -1222
  176. package/src/pptx/styles.ts +0 -96
  177. package/src/pptx/templates.ts +0 -187
  178. package/src/registry/convert.ts +0 -433
  179. package/src/registry/defaultFormats.ts +0 -413
  180. package/src/registry/errors.ts +0 -46
  181. package/src/registry/index.ts +0 -43
  182. package/src/registry/registry.ts +0 -48
  183. package/src/registry/types.ts +0 -170
  184. package/src/shared/boundedZipArchive.ts +0 -383
  185. package/src/shared/container.ts +0 -28
  186. package/src/shared/fidelity.ts +0 -130
  187. package/src/shared/images.ts +0 -44
  188. package/src/shared/inlineRuns.ts +0 -99
  189. package/src/shared/text.ts +0 -41
  190. package/src/shared/zipEntryCount.ts +0 -151
  191. package/src/shared/zipLimits.ts +0 -296
  192. package/src/shared/zipSafety.ts +0 -19
  193. package/src/xlsx/export.ts +0 -253
  194. package/src/xlsx/import.ts +0 -160
  195. package/src/xlsx/index.ts +0 -35
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  ConversionError
3
- } from "./chunk-ODL3SSPT.js";
3
+ } from "./chunk-KXOZMWBS.js";
4
4
  import {
5
5
  ZipSafetyError,
6
- openBoundedZipArchive
7
- } from "./chunk-FE6OJV6O.js";
6
+ openBoundedZipArchive,
7
+ resolveZipSafetyLimits
8
+ } from "./chunk-7AWFHP5U.js";
8
9
 
9
10
  // src/registry/types.ts
10
11
  var BUILTIN_FORMAT_IDS = [
@@ -20,6 +21,84 @@ var BUILTIN_FORMAT_IDS = [
20
21
  "dbk"
21
22
  ];
22
23
 
24
+ // src/registry/limits.ts
25
+ var DEFAULT_CONVERSION_LIMITS = Object.freeze({
26
+ maxInputBytes: 256 * 1024 * 1024,
27
+ maxMarkdownBytes: 16 * 1024 * 1024,
28
+ maxMarkdownNodes: 25e4,
29
+ maxMarkdownDepth: 128,
30
+ maxTableCells: 1e5,
31
+ maxBlocks: 5e4,
32
+ maxBlockDepth: 128,
33
+ maxDurationSeconds: 24 * 60 * 60,
34
+ maxAudioSegments: 1e4
35
+ });
36
+ function resolveConversionLimits(limits) {
37
+ const resolved = { ...DEFAULT_CONVERSION_LIMITS, ...limits };
38
+ for (const [name, value] of Object.entries(resolved)) {
39
+ if (!Number.isSafeInteger(value) || value < 0) {
40
+ throw new RangeError(`${name} must be a non-negative safe integer`);
41
+ }
42
+ }
43
+ return resolved;
44
+ }
45
+ function markdownLimitsFromConversion(limits) {
46
+ if (limits === false) return false;
47
+ const resolved = resolveConversionLimits(limits);
48
+ return {
49
+ maxSourceBytes: resolved.maxMarkdownBytes,
50
+ maxNodes: resolved.maxMarkdownNodes,
51
+ maxDepth: resolved.maxMarkdownDepth,
52
+ maxTableCells: resolved.maxTableCells
53
+ };
54
+ }
55
+ function assertDocWithinConversionLimits(doc, limits, signal) {
56
+ if (limits === false) return;
57
+ const resolved = resolveConversionLimits(limits);
58
+ if (!Number.isFinite(doc.duration) || doc.duration < 0 || doc.duration > resolved.maxDurationSeconds) {
59
+ throw new RangeError(
60
+ `Document duration must be between 0 and ${resolved.maxDurationSeconds} seconds`
61
+ );
62
+ }
63
+ if (doc.audio.segments.length > resolved.maxAudioSegments) {
64
+ throw new RangeError(
65
+ `Document exceeds the ${resolved.maxAudioSegments}-audio-segment safety limit`
66
+ );
67
+ }
68
+ let audioDuration = 0;
69
+ for (const segment of doc.audio.segments) {
70
+ if (!Number.isFinite(segment.duration) || segment.duration < 0) {
71
+ throw new RangeError("Audio segment durations must be non-negative finite numbers");
72
+ }
73
+ audioDuration += segment.duration;
74
+ if (audioDuration > resolved.maxDurationSeconds) {
75
+ throw new RangeError(
76
+ `Narration exceeds the ${resolved.maxDurationSeconds}-second safety limit`
77
+ );
78
+ }
79
+ }
80
+ const stack = [];
81
+ for (let index = doc.blocks.length - 1; index >= 0; index--) {
82
+ stack.push({ block: doc.blocks[index], depth: 0 });
83
+ }
84
+ let count = 0;
85
+ while (stack.length > 0) {
86
+ if ((count & 1023) === 0) signal?.throwIfAborted();
87
+ const { block, depth } = stack.pop();
88
+ if (++count > resolved.maxBlocks) {
89
+ throw new RangeError(`Document exceeds the ${resolved.maxBlocks}-block safety limit`);
90
+ }
91
+ if (depth > resolved.maxBlockDepth) {
92
+ throw new RangeError(
93
+ `Document exceeds the ${resolved.maxBlockDepth}-level block nesting safety limit`
94
+ );
95
+ }
96
+ for (let index = (block.children?.length ?? 0) - 1; index >= 0; index--) {
97
+ stack.push({ block: block.children[index], depth: depth + 1 });
98
+ }
99
+ }
100
+ }
101
+
23
102
  // src/shared/fidelity.ts
24
103
  var CONTENT_NODE_TYPES = {
25
104
  heading: true,
@@ -86,7 +165,7 @@ var PROFILE_LABELS = {
86
165
  pptx: "PPTX",
87
166
  epub: "EPUB"
88
167
  };
89
- function markdownFidelityWarnings(document2, profile) {
168
+ function markdownFidelityWarnings(document, profile) {
90
169
  const supported = PROFILE_SUPPORT[profile];
91
170
  const omitted = /* @__PURE__ */ new Map();
92
171
  const visit = (candidate) => {
@@ -103,7 +182,7 @@ function markdownFidelityWarnings(document2, profile) {
103
182
  for (const child of node.children) visit(child);
104
183
  }
105
184
  };
106
- for (const child of document2.children) visit(child);
185
+ for (const child of document.children) visit(child);
107
186
  if (omitted.size === 0) return [];
108
187
  const total = [...omitted.values()].reduce((sum, count) => sum + count, 0);
109
188
  const details = [...omitted].map(([type, count]) => `${type} (${count})`).join(", ");
@@ -142,17 +221,22 @@ function resolveThemeId(input, options) {
142
221
  return options.themeId ?? input.doc.themeId;
143
222
  }
144
223
  function optionsFor(options, id) {
145
- return options.formatOptions?.[id] ?? {};
224
+ return {
225
+ ...options.formatOptions?.[id] ?? {},
226
+ ...options.signal ? { signal: options.signal } : {}
227
+ };
146
228
  }
147
229
  async function markdownOf(input) {
148
230
  if (input.markdownDoc) return input.markdownDoc;
149
231
  const { docToMarkdown } = await import("@bendyline/squisq/doc");
150
232
  return docToMarkdown(input.doc);
151
233
  }
152
- async function collectContainerImages(container) {
234
+ async function collectContainerImages(container, signal) {
153
235
  const images = /* @__PURE__ */ new Map();
154
236
  const files = await container.listFiles();
155
- for (const file of files) {
237
+ for (let index = 0; index < files.length; index++) {
238
+ if ((index & 63) === 0) signal?.throwIfAborted();
239
+ const file = files[index];
156
240
  if (!/\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i.test(file.path)) continue;
157
241
  const data = await container.readFile(file.path);
158
242
  if (!data) continue;
@@ -162,9 +246,9 @@ async function collectContainerImages(container) {
162
246
  }
163
247
  return images;
164
248
  }
165
- async function collectContainerDocxImages(container) {
166
- const images = await collectContainerImages(container);
167
- const { extToMime } = await import("./images-7FBWPKE3.js");
249
+ async function collectContainerDocxImages(container, signal) {
250
+ const images = await collectContainerImages(container, signal);
251
+ const { extToMime } = await import("./images-ESPQKVTW.js");
168
252
  return new Map(
169
253
  [...images].map(([path, data]) => [
170
254
  path,
@@ -172,11 +256,13 @@ async function collectContainerDocxImages(container) {
172
256
  ])
173
257
  );
174
258
  }
175
- async function snapshotContainerWithDocument(input) {
259
+ async function snapshotContainerWithDocument(input, signal) {
176
260
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
177
261
  const snapshot = new MemoryContentContainer();
178
262
  const entries = await input.container.listFiles();
179
- for (const entry of entries) {
263
+ for (let index = 0; index < entries.length; index++) {
264
+ if ((index & 63) === 0) signal?.throwIfAborted();
265
+ const entry = entries[index];
180
266
  const data = await input.container.readFile(entry.path);
181
267
  if (data) await snapshot.writeFile(entry.path, new Uint8Array(data), entry.mimeType);
182
268
  }
@@ -214,11 +300,21 @@ function defaultFormats() {
214
300
  extensions: [".md", ".markdown"],
215
301
  async importDoc(data, options) {
216
302
  const { parseMarkdown } = await import("@bendyline/squisq/markdown");
217
- return parseMarkdown(new TextDecoder().decode(data), optionsFor(options, "md").parse);
303
+ const raw = optionsFor(options, "md").parse ?? {};
304
+ return parseMarkdown(new TextDecoder().decode(data), {
305
+ ...raw,
306
+ signal: options.signal,
307
+ limits: raw.limits ?? markdownLimitsFromConversion(options.limits)
308
+ });
218
309
  },
219
310
  async exportDoc(input, options) {
220
311
  const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
221
- const text = stringifyMarkdown(await markdownOf(input), optionsFor(options, "md").stringify);
312
+ const raw = optionsFor(options, "md").stringify ?? {};
313
+ const text = stringifyMarkdown(await markdownOf(input), {
314
+ ...raw,
315
+ signal: options.signal,
316
+ limits: raw.limits ?? markdownLimitsFromConversion(options.limits)
317
+ });
222
318
  return ok(new TextEncoder().encode(text), MIME.md);
223
319
  }
224
320
  };
@@ -239,7 +335,7 @@ function defaultFormats() {
239
335
  const { markdownDocToDocx } = await import("./docx/index.js");
240
336
  const raw = optionsFor(options, "docx");
241
337
  const markdownDoc = await markdownOf(input);
242
- const containerImages = await collectContainerDocxImages(input.container);
338
+ const containerImages = await collectContainerDocxImages(input.container, options.signal);
243
339
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
244
340
  const buf = await markdownDocToDocx(markdownDoc, {
245
341
  ...raw,
@@ -268,13 +364,18 @@ function defaultFormats() {
268
364
  const { markdownDocToPdf } = await import("./pdf/index.js");
269
365
  const raw = optionsFor(options, "pdf");
270
366
  const markdownDoc = await markdownOf(input);
367
+ const pdfWarnings = [];
271
368
  const buf = await markdownDocToPdf(markdownDoc, {
272
369
  ...raw,
273
370
  ...options.title !== void 0 ? { title: options.title } : {},
274
371
  themeId: resolveThemeId(input, options),
275
- themeRegistry: options.themeRegistry ?? raw.themeRegistry
372
+ themeRegistry: options.themeRegistry ?? raw.themeRegistry,
373
+ onWarning: (message) => pdfWarnings.push(message)
276
374
  });
277
- return ok(await toBytes(buf), MIME.pdf, markdownFidelityWarnings(markdownDoc, "pdf"));
375
+ return ok(await toBytes(buf), MIME.pdf, [
376
+ ...markdownFidelityWarnings(markdownDoc, "pdf"),
377
+ ...pdfWarnings
378
+ ]);
278
379
  }
279
380
  };
280
381
  const pptx = {
@@ -294,7 +395,7 @@ function defaultFormats() {
294
395
  const { markdownDocToPptx } = await import("./pptx/index.js");
295
396
  const raw = optionsFor(options, "pptx");
296
397
  const markdownDoc = await markdownOf(input);
297
- const containerImages = await collectContainerImages(input.container);
398
+ const containerImages = await collectContainerImages(input.container, options.signal);
298
399
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
299
400
  const buf = await markdownDocToPptx(markdownDoc, {
300
401
  ...raw,
@@ -368,7 +469,7 @@ function defaultFormats() {
368
469
  const playerScript = await requirePlayerScript(options, "html");
369
470
  const { docToHtml } = await import("./html/index.js");
370
471
  const raw = optionsFor(options, "html");
371
- const containerImages = await collectContainerImages(input.container);
472
+ const containerImages = await collectContainerImages(input.container, options.signal);
372
473
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
373
474
  const htmlText = docToHtml(input.doc, {
374
475
  ...raw,
@@ -391,7 +492,7 @@ function defaultFormats() {
391
492
  const playerScript = await requirePlayerScript(options, "htmlzip");
392
493
  const { docToHtmlZip } = await import("./html/index.js");
393
494
  const raw = optionsFor(options, "htmlzip");
394
- const containerImages = await collectContainerImages(input.container);
495
+ const containerImages = await collectContainerImages(input.container, options.signal);
395
496
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
396
497
  const blob = await docToHtmlZip(input.doc, {
397
498
  ...raw,
@@ -414,7 +515,7 @@ function defaultFormats() {
414
515
  const { markdownDocToEpub } = await import("./epub/index.js");
415
516
  const raw = optionsFor(options, "epub");
416
517
  const markdownDoc = await markdownOf(input);
417
- const containerImages = await collectContainerImages(input.container);
518
+ const containerImages = await collectContainerImages(input.container, options.signal);
418
519
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
419
520
  const buf = await markdownDocToEpub(markdownDoc, {
420
521
  ...raw,
@@ -438,7 +539,7 @@ function defaultFormats() {
438
539
  async exportDoc(input, options) {
439
540
  const { containerToZip } = await import("./container/index.js");
440
541
  const blob = await containerToZip(
441
- await snapshotContainerWithDocument(input),
542
+ await snapshotContainerWithDocument(input, options.signal),
442
543
  optionsFor(options, "dbk")
443
544
  );
444
545
  return ok(await toBytes(blob), MIME.zip);
@@ -485,6 +586,22 @@ function defaultRegistry() {
485
586
  function markdownFormatOptions(options) {
486
587
  return options.formatOptions?.md ?? {};
487
588
  }
589
+ function parseOptions(options) {
590
+ const raw = markdownFormatOptions(options).parse ?? {};
591
+ return {
592
+ ...raw,
593
+ signal: options.signal,
594
+ limits: raw.limits ?? markdownLimitsFromConversion(options.limits)
595
+ };
596
+ }
597
+ function stringifyOptions(options) {
598
+ const raw = markdownFormatOptions(options).stringify ?? {};
599
+ return {
600
+ ...raw,
601
+ signal: options.signal,
602
+ limits: raw.limits ?? markdownLimitsFromConversion(options.limits)
603
+ };
604
+ }
488
605
  function throwIfAborted(signal) {
489
606
  if (!signal?.aborted) return;
490
607
  if (signal.reason !== void 0) throw signal.reason;
@@ -501,11 +618,26 @@ function hasPrefix(bytes, prefix) {
501
618
  }
502
619
  var ZIP_MAGIC = [80, 75, 3, 4];
503
620
  var PDF_MAGIC = [37, 80, 68, 70];
504
- async function sniffZip(bytes) {
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 });
508
- });
621
+ var ZIP_SNIFF_CANDIDATES = ["docx", "pptx", "xlsx", "dbk"];
622
+ function sniffZipLimits(options) {
623
+ const effective = ZIP_SNIFF_CANDIDATES.map(
624
+ (id) => resolveZipSafetyLimits(options.formatOptions?.[id] ?? {})
625
+ );
626
+ return {
627
+ signal: options.signal,
628
+ maxEntries: Math.max(...effective.map((l) => l.maxEntries)),
629
+ maxUncompressedBytes: Math.max(...effective.map((l) => l.maxUncompressedBytes)),
630
+ maxEntryUncompressedBytes: Math.max(...effective.map((l) => l.maxEntryUncompressedBytes)),
631
+ maxCompressionRatio: Math.max(...effective.map((l) => l.maxCompressionRatio))
632
+ };
633
+ }
634
+ async function sniffZip(bytes, options) {
635
+ const archive = await openBoundedZipArchive(bytes, sniffZipLimits(options)).catch(
636
+ (cause) => {
637
+ if (cause instanceof ZipSafetyError && cause.code !== "invalid-archive") throw cause;
638
+ throw new ConversionError("invalid-input", "Input is not a readable ZIP archive.", { cause });
639
+ }
640
+ );
509
641
  const contentTypes = archive.entries.find((entry) => entry.path === "[Content_Types].xml");
510
642
  if (!contentTypes) return "dbk";
511
643
  const maxContentTypesBytes = 1024 * 1024;
@@ -528,7 +660,7 @@ async function detectByteFormat(bytes, filename, options, registry) {
528
660
  if (byExt) return byExt.id;
529
661
  }
530
662
  if (hasPrefix(bytes, PDF_MAGIC)) return "pdf";
531
- if (hasPrefix(bytes, ZIP_MAGIC)) return sniffZip(bytes);
663
+ if (hasPrefix(bytes, ZIP_MAGIC)) return sniffZip(bytes, options);
532
664
  return "md";
533
665
  }
534
666
  function extractExt(filename) {
@@ -546,6 +678,12 @@ function baseNameOf(filename) {
546
678
  }
547
679
  async function normalizeBytes(source, options, registry) {
548
680
  const bytes = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
681
+ if (options.limits !== false && bytes.byteLength > resolveConversionLimits(options.limits).maxInputBytes) {
682
+ throw new ConversionError(
683
+ "invalid-input",
684
+ `Input exceeds the ${resolveConversionLimits(options.limits).maxInputBytes}-byte safety limit.`
685
+ );
686
+ }
549
687
  const buffer = bytes.buffer.slice(
550
688
  bytes.byteOffset,
551
689
  bytes.byteOffset + bytes.byteLength
@@ -574,21 +712,23 @@ async function normalizeBytes(source, options, registry) {
574
712
  let container;
575
713
  if (fromDef.importContainer) {
576
714
  container = await fromDef.importContainer(buffer, options);
577
- if (fromDef.id === "pdf" && typeof document === "undefined") {
578
- warnings.push(
579
- "PDF embedded images were skipped \u2014 image decoding requires a browser canvas (running under Node)."
580
- );
581
- }
582
715
  const text = await container.readDocument();
583
- markdownDoc = text ? parseMarkdown(text, markdownFormatOptions(options).parse) : { type: "document", children: [] };
716
+ markdownDoc = text ? parseMarkdown(text, parseOptions(options)) : { type: "document", children: [] };
584
717
  } else {
585
718
  markdownDoc = await fromDef.importDoc(buffer, options);
586
719
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
587
720
  const mem = new MemoryContentContainer();
588
- await mem.writeDocument(stringifyMarkdown(markdownDoc));
721
+ await mem.writeDocument(stringifyMarkdown(markdownDoc, stringifyOptions(options)));
589
722
  container = mem;
590
723
  }
724
+ const { assertMarkdownDocumentWithinLimits } = await import("@bendyline/squisq/markdown");
725
+ assertMarkdownDocumentWithinLimits(
726
+ markdownDoc,
727
+ markdownLimitsFromConversion(options.limits),
728
+ options.signal
729
+ );
591
730
  const doc = markdownToDoc(markdownDoc, { autoTemplates: options.autoTemplates });
731
+ assertDocWithinConversionLimits(doc, options.limits, options.signal);
592
732
  const baseName = source.filename ? baseNameOf(source.filename) : "document";
593
733
  return {
594
734
  input: { doc, markdownDoc, container, baseName },
@@ -599,26 +739,36 @@ async function normalizeMarkdown(source, options) {
599
739
  const { markdownToDoc } = await import("@bendyline/squisq/doc");
600
740
  const { parseMarkdown } = await import("@bendyline/squisq/markdown");
601
741
  const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
602
- const markdownDoc = typeof source.markdown === "string" ? parseMarkdown(source.markdown, markdownFormatOptions(options).parse) : source.markdown;
742
+ const markdownDoc = typeof source.markdown === "string" ? parseMarkdown(source.markdown, parseOptions(options)) : source.markdown;
743
+ const { assertMarkdownDocumentWithinLimits } = await import("@bendyline/squisq/markdown");
744
+ assertMarkdownDocumentWithinLimits(
745
+ markdownDoc,
746
+ markdownLimitsFromConversion(options.limits),
747
+ options.signal
748
+ );
603
749
  let container = source.container;
604
750
  if (!container) {
605
751
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
606
752
  const mem = new MemoryContentContainer();
607
- await mem.writeDocument(stringifyMarkdown(markdownDoc));
753
+ await mem.writeDocument(stringifyMarkdown(markdownDoc, stringifyOptions(options)));
608
754
  container = mem;
609
755
  }
610
756
  const doc = markdownToDoc(markdownDoc, { autoTemplates: options.autoTemplates });
757
+ assertDocWithinConversionLimits(doc, options.limits, options.signal);
611
758
  const baseName = source.baseName ?? "document";
612
759
  return { input: { doc, markdownDoc, container, baseName }, warnings: [] };
613
760
  }
614
- async function normalizeDoc(source) {
761
+ async function normalizeDoc(source, options) {
762
+ assertDocWithinConversionLimits(source.doc, options.limits, options.signal);
615
763
  let container = source.container;
616
764
  if (!container) {
617
765
  const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
618
766
  const { docToMarkdown } = await import("@bendyline/squisq/doc");
619
767
  const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
620
768
  const mem = new MemoryContentContainer();
621
- await mem.writeDocument(stringifyMarkdown(docToMarkdown(source.doc)));
769
+ await mem.writeDocument(
770
+ stringifyMarkdown(docToMarkdown(source.doc), stringifyOptions(options))
771
+ );
622
772
  container = mem;
623
773
  }
624
774
  const baseName = source.baseName ?? "document";
@@ -631,7 +781,7 @@ async function normalize(source, options, registry) {
631
781
  case "markdown":
632
782
  return normalizeMarkdown(source, options);
633
783
  case "doc":
634
- return normalizeDoc(source);
784
+ return normalizeDoc(source, options);
635
785
  }
636
786
  }
637
787
  async function applyTransformStyle(input, transformStyle, themeId, registry) {
@@ -758,10 +908,11 @@ async function convert(source, to, options = {}) {
758
908
 
759
909
  export {
760
910
  BUILTIN_FORMAT_IDS,
911
+ DEFAULT_CONVERSION_LIMITS,
912
+ resolveConversionLimits,
761
913
  defaultFormats,
762
914
  createRegistry,
763
915
  defaultRegistry,
764
916
  prepareConversion,
765
917
  convert
766
918
  };
767
- //# sourceMappingURL=chunk-WQSHGBLN.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  escapeXml,
3
3
  xmlDeclaration
4
- } from "./chunk-U4MRIFKL.js";
4
+ } from "./chunk-JU2RHXUB.js";
5
5
  import {
6
6
  CONTENT_TYPE_CORE_PROPERTIES,
7
7
  CONTENT_TYPE_RELATIONSHIPS,
@@ -12,10 +12,10 @@ import {
12
12
  NS_RELATIONSHIPS,
13
13
  NS_XSI,
14
14
  REL_CORE_PROPERTIES
15
- } from "./chunk-7ARKUCQT.js";
15
+ } from "./chunk-X2DEAXNK.js";
16
16
  import {
17
17
  assertSafeZipPath
18
- } from "./chunk-FE6OJV6O.js";
18
+ } from "./chunk-7AWFHP5U.js";
19
19
 
20
20
  // src/ooxml/writer.ts
21
21
  import JSZip from "jszip";
@@ -192,4 +192,3 @@ function groupRelationshipsBySource(pending) {
192
192
  export {
193
193
  createPackage
194
194
  };
195
- //# sourceMappingURL=chunk-KURGXM4I.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  extToMime
3
- } from "./chunk-O3GVVND4.js";
3
+ } from "./chunk-AONELFLA.js";
4
4
 
5
5
  // src/html/imageUtils.ts
6
6
  function inferMimeType(filename) {
@@ -31,4 +31,3 @@ export {
31
31
  arrayBufferToBase64DataUrl,
32
32
  extractFilename
33
33
  };
34
- //# sourceMappingURL=chunk-MLX2BOJC.js.map
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  ConversionError
3
- } from "./chunk-ODL3SSPT.js";
3
+ } from "./chunk-KXOZMWBS.js";
4
4
  import {
5
5
  extractDocxTheme,
6
6
  extractPptxTheme,
7
7
  extractXlsxTheme
8
- } from "./chunk-74GO3FVS.js";
8
+ } from "./chunk-PU7REGWV.js";
9
9
  import {
10
10
  colorHintsFromExtraction,
11
11
  compileExtractedTheme
12
- } from "./chunk-NKAJPJ4G.js";
12
+ } from "./chunk-2JJ5RFDZ.js";
13
13
  import {
14
14
  openPackage,
15
15
  throwIfOoxmlAborted
16
- } from "./chunk-7ARKUCQT.js";
16
+ } from "./chunk-X2DEAXNK.js";
17
17
 
18
18
  // src/infer/index.ts
19
19
  async function looksLikePdf(data, signal) {
@@ -107,7 +107,7 @@ async function inferThemeFromFile(data, options = {}) {
107
107
  if (format !== "pptx") {
108
108
  warnings.push("Layout inference is only available for PowerPoint files; skipped.");
109
109
  } else {
110
- const { analyzePptxLayouts } = await import("./layouts-5VDIRPIJ.js");
110
+ const { analyzePptxLayouts } = await import("./layouts-DRWZGSPD.js");
111
111
  const analysis = await analyzePptxLayouts(pkg, {
112
112
  colors: colorHintsFromExtraction(extraction),
113
113
  signal: options.signal
@@ -124,4 +124,3 @@ async function inferThemeFromFile(data, options = {}) {
124
124
  export {
125
125
  inferThemeFromFile
126
126
  };
127
- //# sourceMappingURL=chunk-EW54IRRS.js.map
@@ -555,6 +555,6 @@ function makeCrcTable() {
555
555
  export {
556
556
  ZipSafetyError,
557
557
  assertSafeZipPath,
558
+ resolveZipSafetyLimits,
558
559
  openBoundedZipArchive
559
560
  };
560
- //# sourceMappingURL=chunk-FE6OJV6O.js.map
@@ -1,27 +1,54 @@
1
1
  // src/csv/index.ts
2
2
  import { markdownToDoc } from "@bendyline/squisq/doc";
3
+ var DEFAULT_MAX_CSV_CELLS = 1e5;
4
+ var DEFAULT_MAX_CSV_ROWS = 1e4;
5
+ var DEFAULT_MAX_CSV_FIELD_CHARS = 1024 * 1024;
3
6
  async function toText(data) {
4
7
  if (typeof data === "string") return data;
5
8
  if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
6
9
  return new TextDecoder().decode(new Uint8Array(data));
7
10
  }
8
- function parseCsv(text, delimiter = ",") {
11
+ function validateDelimiter(delimiter) {
12
+ if ([...delimiter].length !== 1 || /["\r\n\uFEFF]/u.test(delimiter)) {
13
+ throw new TypeError(
14
+ "CSV delimiter must be exactly one character and cannot be a quote, line break, or BOM"
15
+ );
16
+ }
17
+ return delimiter;
18
+ }
19
+ function parseCsv(text, delimiter = ",", limits = {}) {
20
+ delimiter = validateDelimiter(delimiter);
21
+ const maxCells = safetyInteger("maxCells", limits.maxCells ?? DEFAULT_MAX_CSV_CELLS);
22
+ const maxRows = safetyInteger("maxRows", limits.maxRows ?? DEFAULT_MAX_CSV_ROWS);
23
+ const maxFieldChars = safetyInteger(
24
+ "maxFieldChars",
25
+ limits.maxFieldChars ?? DEFAULT_MAX_CSV_FIELD_CHARS
26
+ );
9
27
  const rows = [];
10
28
  let row = [];
11
29
  let field = "";
12
30
  let inQuotes = false;
13
31
  let sawAny = false;
32
+ let cellCount = 0;
14
33
  const pushField = () => {
34
+ if (++cellCount > maxCells)
35
+ throw new RangeError(`CSV exceeds the ${maxCells}-cell safety limit`);
15
36
  row.push(field);
16
37
  field = "";
17
38
  };
18
39
  const pushRow = () => {
40
+ if (rows.length >= maxRows) throw new RangeError(`CSV exceeds the ${maxRows}-row safety limit`);
19
41
  pushField();
20
42
  rows.push(row);
21
43
  row = [];
22
44
  };
23
45
  for (let i = 0; i < text.length; i++) {
24
- const ch = text[i];
46
+ if ((i & 16383) === 0) limits.signal?.throwIfAborted();
47
+ let ch = text[i];
48
+ if (delimiter.length === 2 && text.slice(i, i + 2) === delimiter) {
49
+ ch = delimiter;
50
+ i++;
51
+ }
25
52
  if (inQuotes) {
26
53
  if (ch === '"') {
27
54
  if (text[i + 1] === '"') {
@@ -32,6 +59,9 @@ function parseCsv(text, delimiter = ",") {
32
59
  }
33
60
  } else {
34
61
  field += ch;
62
+ if (field.length > maxFieldChars) {
63
+ throw new RangeError(`CSV field exceeds the ${maxFieldChars}-character safety limit`);
64
+ }
35
65
  }
36
66
  continue;
37
67
  }
@@ -53,10 +83,19 @@ function parseCsv(text, delimiter = ",") {
53
83
  }
54
84
  sawAny = true;
55
85
  field += ch;
86
+ if (field.length > maxFieldChars) {
87
+ throw new RangeError(`CSV field exceeds the ${maxFieldChars}-character safety limit`);
88
+ }
56
89
  }
57
90
  if (field !== "" || row.length > 0 || sawAny) pushRow();
58
91
  return rows;
59
92
  }
93
+ function safetyInteger(name, value) {
94
+ if (!Number.isSafeInteger(value) || value < 0) {
95
+ throw new RangeError(`${name} must be a non-negative safe integer`);
96
+ }
97
+ return value;
98
+ }
60
99
  function rowsToTable(rows, hasHeader) {
61
100
  const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 1);
62
101
  const mdRows = rows.map((cells, rowIdx) => {
@@ -74,8 +113,8 @@ function rowsToTable(rows, hasHeader) {
74
113
  return { type: "table", children: mdRows };
75
114
  }
76
115
  async function csvToMarkdownDoc(data, options = {}) {
77
- const text = await toText(data);
78
- const rows = parseCsv(text, options.delimiter ?? ",");
116
+ const text = (await toText(data)).replace(/^\uFEFF/, "");
117
+ const rows = parseCsv(text, options.delimiter ?? ",", options);
79
118
  const hasHeader = options.hasHeader ?? true;
80
119
  const children = rows.length > 0 ? [rowsToTable(rows, hasHeader)] : [];
81
120
  return { type: "document", children };
@@ -89,6 +128,11 @@ function escapeCsvField(value, delimiter) {
89
128
  }
90
129
  return value;
91
130
  }
131
+ var SPREADSHEET_FORMULA_PREFIX = /^[\t\r\n \uFEFF]*[=+\-@]/;
132
+ function neutralizeSpreadsheetFormula(value, handling) {
133
+ if (handling === "preserve" || !SPREADSHEET_FORMULA_PREFIX.test(value)) return value;
134
+ return `'${value}`;
135
+ }
92
136
  function cellText(cell) {
93
137
  const walk = (nodes) => nodes.map((n) => {
94
138
  const node = n;
@@ -99,7 +143,8 @@ function cellText(cell) {
99
143
  return walk(cell.children);
100
144
  }
101
145
  function markdownDocToCsv(doc, options = {}) {
102
- const delimiter = options.delimiter ?? ",";
146
+ const delimiter = validateDelimiter(options.delimiter ?? ",");
147
+ const formulaHandling = options.formulaHandling ?? "escape";
103
148
  const tables = doc.children.filter((n) => n.type === "table");
104
149
  const index = options.tableIndex ?? 0;
105
150
  if (options.tableIndex !== void 0 && (!Number.isInteger(index) || index < 0 || index >= tables.length)) {
@@ -109,9 +154,15 @@ function markdownDocToCsv(doc, options = {}) {
109
154
  }
110
155
  const table = tables[index];
111
156
  if (!table) return "";
112
- return table.children.map(
113
- (row) => row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter)
114
- ).join("\r\n");
157
+ const maxCells = safetyInteger("maxCells", options.maxCells ?? DEFAULT_MAX_CSV_CELLS);
158
+ const cellCount = table.children.reduce((count, row) => count + row.children.length, 0);
159
+ if (cellCount > maxCells) throw new RangeError(`CSV exceeds the ${maxCells}-cell safety limit`);
160
+ return table.children.map((row, rowIndex) => {
161
+ if ((rowIndex & 255) === 0) options.signal?.throwIfAborted();
162
+ return row.children.map(
163
+ (cell) => escapeCsvField(neutralizeSpreadsheetFormula(cellText(cell), formulaHandling), delimiter)
164
+ ).join(delimiter);
165
+ }).join("\r\n");
115
166
  }
116
167
 
117
168
  export {
@@ -120,4 +171,3 @@ export {
120
171
  csvToDoc,
121
172
  markdownDocToCsv
122
173
  };
123
- //# sourceMappingURL=chunk-RFAPOKHJ.js.map
@@ -30,4 +30,3 @@ function extToMime(ext) {
30
30
  export {
31
31
  extToMime
32
32
  };
33
- //# sourceMappingURL=chunk-O3GVVND4.js.map