@bendyline/squisq-formats 1.4.2 → 2.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 (139) hide show
  1. package/README.md +48 -2
  2. package/dist/{chunk-WWBIKRNP.js → chunk-2CSZJEAL.js} +13 -11
  3. package/dist/chunk-2CSZJEAL.js.map +1 -0
  4. package/dist/{chunk-RTS5XBZ7.js → chunk-2EFDWKC6.js} +262 -84
  5. package/dist/chunk-2EFDWKC6.js.map +1 -0
  6. package/dist/chunk-2PRVLAU3.js +740 -0
  7. package/dist/chunk-2PRVLAU3.js.map +1 -0
  8. package/dist/{chunk-SHLPSADL.js → chunk-4HJ6AK2Q.js} +54 -193
  9. package/dist/chunk-4HJ6AK2Q.js.map +1 -0
  10. package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
  11. package/dist/chunk-4VUWTSGM.js.map +1 -0
  12. package/dist/{chunk-YESS7VY3.js → chunk-CVNVD7VH.js} +154 -39
  13. package/dist/chunk-CVNVD7VH.js.map +1 -0
  14. package/dist/chunk-CX3LH47J.js +99 -0
  15. package/dist/chunk-CX3LH47J.js.map +1 -0
  16. package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
  17. package/dist/chunk-GVS2XXV6.js.map +1 -0
  18. package/dist/chunk-H7T5DXUU.js +479 -0
  19. package/dist/chunk-H7T5DXUU.js.map +1 -0
  20. package/dist/{chunk-EHLGMSTE.js → chunk-HBSCRS5P.js} +10 -8
  21. package/dist/chunk-HBSCRS5P.js.map +1 -0
  22. package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
  23. package/dist/chunk-LXYLOOST.js.map +1 -0
  24. package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
  25. package/dist/chunk-MLX2BOJC.js.map +1 -0
  26. package/dist/chunk-NKAJPJ4G.js +106 -0
  27. package/dist/chunk-NKAJPJ4G.js.map +1 -0
  28. package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
  29. package/dist/chunk-ODL3SSPT.js +18 -0
  30. package/dist/chunk-ODL3SSPT.js.map +1 -0
  31. package/dist/chunk-X4USWOQ2.js +131 -0
  32. package/dist/chunk-X4USWOQ2.js.map +1 -0
  33. package/dist/chunk-XTLQXR4U.js +195 -0
  34. package/dist/chunk-XTLQXR4U.js.map +1 -0
  35. package/dist/chunk-YRT7GQ5Y.js +28 -0
  36. package/dist/chunk-YRT7GQ5Y.js.map +1 -0
  37. package/dist/chunk-ZZ7D4DN6.js +97 -0
  38. package/dist/chunk-ZZ7D4DN6.js.map +1 -0
  39. package/dist/container/index.d.ts +6 -2
  40. package/dist/container/index.js +15 -17
  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-Djev9BLU.d.ts +69 -0
  47. package/dist/extract-LNOA7DBK.js +15 -0
  48. package/dist/extract-LNOA7DBK.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-Yivc3kZM.d.ts +136 -0
  55. package/dist/index.d.ts +13 -4
  56. package/dist/index.js +30 -11
  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-7OUUVAGU.js +12 -0
  61. package/dist/layouts-7OUUVAGU.js.map +1 -0
  62. package/dist/layouts-CZ0bdsNL.d.ts +150 -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-Gof6OrJX.d.ts +140 -0
  72. package/dist/registry/index.d.ts +46 -8
  73. package/dist/registry/index.js +5 -2
  74. package/dist/themeReader-DPw4J5Ss.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-bGYMS9cI.d.ts +29 -0
  78. package/package.json +8 -2
  79. package/src/__tests__/container.test.ts +41 -0
  80. package/src/__tests__/convert.test.ts +167 -0
  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 +59 -2
  86. package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
  87. package/src/__tests__/plainHtml.test.ts +15 -1
  88. package/src/__tests__/plainHtmlBundle.test.ts +18 -0
  89. package/src/__tests__/pptxImport.test.ts +30 -0
  90. package/src/__tests__/pptxInferFixtures.ts +314 -0
  91. package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
  92. package/src/__tests__/zipSafety.test.ts +317 -0
  93. package/src/container/index.ts +20 -29
  94. package/src/docx/export.ts +4 -2
  95. package/src/docx/import.ts +4 -3
  96. package/src/epub/export.ts +94 -25
  97. package/src/html/docsHtmlBundle.ts +6 -2
  98. package/src/html/htmlTemplate.ts +31 -6
  99. package/src/html/imageUtils.ts +14 -5
  100. package/src/html/index.ts +12 -1
  101. package/src/html/plainHtml.ts +7 -3
  102. package/src/html/plainHtmlBundle.ts +11 -9
  103. package/src/index.ts +20 -0
  104. package/src/infer/extract.ts +127 -0
  105. package/src/infer/index.ts +168 -0
  106. package/src/infer/mapTheme.ts +176 -0
  107. package/src/infer/types.ts +27 -0
  108. package/src/ooxml/index.ts +14 -0
  109. package/src/ooxml/readUtils.ts +44 -0
  110. package/src/ooxml/reader.ts +87 -28
  111. package/src/ooxml/themeReader.ts +197 -0
  112. package/src/ooxml/types.ts +8 -9
  113. package/src/ooxml/writer.ts +28 -10
  114. package/src/pdf/export.ts +4 -2
  115. package/src/pdf/import.ts +40 -34
  116. package/src/pptx/export.ts +4 -2
  117. package/src/pptx/import.ts +214 -32
  118. package/src/pptx/index.ts +14 -0
  119. package/src/pptx/layouts.ts +1168 -0
  120. package/src/registry/convert.ts +54 -23
  121. package/src/registry/defaultFormats.ts +131 -44
  122. package/src/registry/index.ts +3 -0
  123. package/src/registry/types.ts +52 -6
  124. package/src/shared/boundedZipArchive.ts +342 -0
  125. package/src/shared/fidelity.ts +130 -0
  126. package/src/shared/zipEntryCount.ts +97 -0
  127. package/src/shared/zipLimits.ts +286 -0
  128. package/src/shared/zipSafety.ts +19 -0
  129. package/src/xlsx/import.ts +3 -2
  130. package/dist/chunk-46FMDOWZ.js.map +0 -1
  131. package/dist/chunk-5LSSWZPU.js.map +0 -1
  132. package/dist/chunk-D7IFPWNZ.js.map +0 -1
  133. package/dist/chunk-EHLGMSTE.js.map +0 -1
  134. package/dist/chunk-MJGRI6XR.js.map +0 -1
  135. package/dist/chunk-RTS5XBZ7.js.map +0 -1
  136. package/dist/chunk-SHLPSADL.js.map +0 -1
  137. package/dist/chunk-WWBIKRNP.js.map +0 -1
  138. package/dist/chunk-YESS7VY3.js.map +0 -1
  139. /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
@@ -0,0 +1,97 @@
1
+ import {
2
+ ConversionError
3
+ } from "./chunk-ODL3SSPT.js";
4
+ import {
5
+ extractDocxTheme,
6
+ extractPptxTheme,
7
+ extractXlsxTheme
8
+ } from "./chunk-CX3LH47J.js";
9
+ import {
10
+ colorHintsFromExtraction,
11
+ compileExtractedTheme
12
+ } from "./chunk-NKAJPJ4G.js";
13
+ import {
14
+ openPackage
15
+ } from "./chunk-4HJ6AK2Q.js";
16
+
17
+ // src/infer/index.ts
18
+ async function looksLikePdf(data) {
19
+ let head = null;
20
+ const blob = data;
21
+ if (typeof blob.size === "number" && typeof blob.slice === "function" && typeof blob.arrayBuffer === "function") {
22
+ head = new Uint8Array(await blob.slice(0, 5).arrayBuffer());
23
+ } else if (typeof data.byteLength === "number") {
24
+ const buf = data;
25
+ head = new Uint8Array(buf, 0, Math.min(5, buf.byteLength));
26
+ }
27
+ if (!head || head.length < 4) return false;
28
+ return head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70;
29
+ }
30
+ function sniffOoxmlFormat(pkg) {
31
+ const values = [...pkg.contentTypes.overrides.values(), ...pkg.contentTypes.defaults.values()];
32
+ for (const ct of values) {
33
+ if (ct.includes("wordprocessingml")) return "docx";
34
+ if (ct.includes("presentationml")) return "pptx";
35
+ if (ct.includes("spreadsheetml")) return "xlsx";
36
+ }
37
+ return void 0;
38
+ }
39
+ var EXTRACTORS = {
40
+ docx: extractDocxTheme,
41
+ pptx: extractPptxTheme,
42
+ xlsx: extractXlsxTheme
43
+ };
44
+ async function inferThemeFromFile(data, options = {}) {
45
+ if (await looksLikePdf(data)) {
46
+ throw new ConversionError(
47
+ "unsupported-input",
48
+ "PDF theme inference is not supported \u2014 PDF files carry no theme color/font tables.",
49
+ { format: "pdf" }
50
+ );
51
+ }
52
+ let pkg;
53
+ try {
54
+ pkg = await openPackage(data, options);
55
+ } catch (err) {
56
+ throw new ConversionError(
57
+ "invalid-input",
58
+ "Could not read this file as a Word, PowerPoint, or Excel document.",
59
+ { cause: err }
60
+ );
61
+ }
62
+ const format = options.format ?? sniffOoxmlFormat(pkg);
63
+ if (!format) {
64
+ throw new ConversionError(
65
+ "invalid-input",
66
+ "Not a Word, PowerPoint, or Excel document \u2014 no theme to infer."
67
+ );
68
+ }
69
+ const extraction = await EXTRACTORS[format](pkg);
70
+ if (!extraction) {
71
+ throw new ConversionError("invalid-input", "No theme part found in this file.", { format });
72
+ }
73
+ const { theme, warnings: mapWarnings } = compileExtractedTheme(extraction, {
74
+ nameHint: options.nameHint
75
+ });
76
+ const warnings = [...extraction.warnings, ...mapWarnings];
77
+ let layouts;
78
+ if (options.inferLayouts) {
79
+ if (format !== "pptx") {
80
+ warnings.push("Layout inference is only available for PowerPoint files; skipped.");
81
+ } else {
82
+ const { analyzePptxLayouts } = await import("./layouts-7OUUVAGU.js");
83
+ const analysis = await analyzePptxLayouts(pkg, {
84
+ colors: colorHintsFromExtraction(extraction)
85
+ });
86
+ warnings.push(...analysis.warnings);
87
+ const defs = analysis.layouts.map((l) => l.verdict.kind === "custom" ? l.verdict.def : null).filter((d) => d !== null);
88
+ if (defs.length > 0) layouts = defs;
89
+ }
90
+ }
91
+ return { theme, extraction, ...layouts ? { layouts } : {}, warnings };
92
+ }
93
+
94
+ export {
95
+ inferThemeFromFile
96
+ };
97
+ //# sourceMappingURL=chunk-ZZ7D4DN6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/infer/index.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats Infer Module\n *\n * \"Infer theme and layouts from a file import\": read the OOXML theme\n * (colors + fonts) out of a DOCX/PPTX/XLSX file and compile it into a\n * Squisq `Theme`; for PPTX, optionally derive custom layout templates\n * from the deck's slide layouts/masters.\n *\n * Consumers:\n * - the editor's theme customizer surfaces (upload / drag-drop a file)\n * - the PPTX importer (theme + layouts ride along as frontmatter)\n *\n * PDF is deliberately unsupported: PDF files carry no theme tables, so\n * inference would produce a mostly-default theme. `inferThemeFromFile`\n * rejects PDF bytes with a clear `ConversionError`.\n */\n\nimport type { CustomTemplateDefinition, Theme } from '@bendyline/squisq/schemas';\nimport { openPackage } from '../ooxml/reader.js';\nimport type { OoxmlOpenOptions } from '../ooxml/reader.js';\nimport type { OoxmlPackage } from '../ooxml/types.js';\nimport { ConversionError } from '../registry/errors.js';\nimport type { ExtractedFileTheme, InferSourceFormat } from './types.js';\nimport { extractDocxTheme, extractPptxTheme, extractXlsxTheme } from './extract.js';\nimport { colorHintsFromExtraction, compileExtractedTheme } from './mapTheme.js';\n\nexport type { ExtractedFileTheme, InferSourceFormat, SchemeSlot } from './types.js';\nexport { extractDocxTheme, extractPptxTheme, extractXlsxTheme } from './extract.js';\nexport type { CompileExtractedOptions, MappedThemePartial } from './mapTheme.js';\nexport {\n colorHintsFromExtraction,\n compileExtractedTheme,\n extractedThemeToPartial,\n} from './mapTheme.js';\n\nexport interface InferThemeOptions extends OoxmlOpenOptions {\n /** Skip sniffing when the caller already knows the format (e.g. from the extension). */\n format?: InferSourceFormat;\n /** PPTX only: also derive custom layout templates from slide layouts/masters. */\n inferLayouts?: boolean;\n /** Preferred display name for the compiled theme (e.g. the file's basename). */\n nameHint?: string;\n}\n\nexport interface InferredFileTheme {\n /** Compiled, validated Squisq theme (`custom-<slug>` id, seeds recorded). */\n theme: Theme;\n /** Raw extraction, for callers mapping into their own editing model. */\n extraction: ExtractedFileTheme;\n /** Present only for PPTX with `inferLayouts` and ≥1 non-redundant layout. */\n layouts?: CustomTemplateDefinition[];\n /** Non-fatal notes: sysClr fallbacks, dropped accents, skipped layouts, … */\n warnings: string[];\n}\n\nasync function looksLikePdf(data: ArrayBuffer | Blob): Promise<boolean> {\n // Duck-typed (not instanceof): buffers routinely cross realms in test\n // environments and web workers. Blobs carry `size`; ArrayBuffers carry\n // `byteLength`.\n let head: Uint8Array | null = null;\n const blob = data as Blob;\n if (\n typeof blob.size === 'number' &&\n typeof blob.slice === 'function' &&\n typeof blob.arrayBuffer === 'function'\n ) {\n head = new Uint8Array(await blob.slice(0, 5).arrayBuffer());\n } else if (typeof (data as ArrayBuffer).byteLength === 'number') {\n const buf = data as ArrayBuffer;\n head = new Uint8Array(buf, 0, Math.min(5, buf.byteLength));\n }\n if (!head || head.length < 4) return false;\n // '%PDF'\n return head[0] === 0x25 && head[1] === 0x50 && head[2] === 0x44 && head[3] === 0x46;\n}\n\n/**\n * Classify an opened package from its already-parsed content types.\n * (Deliberately not the registry's byte sniffer — that re-parses the zip\n * and carries `dbk` semantics this path doesn't want.)\n */\nfunction sniffOoxmlFormat(pkg: OoxmlPackage): InferSourceFormat | undefined {\n const values = [...pkg.contentTypes.overrides.values(), ...pkg.contentTypes.defaults.values()];\n for (const ct of values) {\n if (ct.includes('wordprocessingml')) return 'docx';\n if (ct.includes('presentationml')) return 'pptx';\n if (ct.includes('spreadsheetml')) return 'xlsx';\n }\n return undefined;\n}\n\nconst EXTRACTORS: Record<\n InferSourceFormat,\n (pkg: OoxmlPackage) => Promise<ExtractedFileTheme | null>\n> = {\n docx: extractDocxTheme,\n pptx: extractPptxTheme,\n xlsx: extractXlsxTheme,\n};\n\n/**\n * Infer a Squisq theme (and, for PPTX, optionally custom layout templates)\n * from an office file's bytes.\n *\n * Throws `ConversionError`:\n * - `unsupported-input` for PDF bytes (no theme tables to read)\n * - `invalid-input` for non-OOXML data or files with no theme part\n */\nexport async function inferThemeFromFile(\n data: ArrayBuffer | Blob,\n options: InferThemeOptions = {},\n): Promise<InferredFileTheme> {\n if (await looksLikePdf(data)) {\n throw new ConversionError(\n 'unsupported-input',\n 'PDF theme inference is not supported — PDF files carry no theme color/font tables.',\n { format: 'pdf' },\n );\n }\n\n let pkg: OoxmlPackage;\n try {\n pkg = await openPackage(data, options);\n } catch (err: unknown) {\n throw new ConversionError(\n 'invalid-input',\n 'Could not read this file as a Word, PowerPoint, or Excel document.',\n { cause: err },\n );\n }\n\n const format = options.format ?? sniffOoxmlFormat(pkg);\n if (!format) {\n throw new ConversionError(\n 'invalid-input',\n 'Not a Word, PowerPoint, or Excel document — no theme to infer.',\n );\n }\n\n const extraction = await EXTRACTORS[format](pkg);\n if (!extraction) {\n throw new ConversionError('invalid-input', 'No theme part found in this file.', { format });\n }\n\n const { theme, warnings: mapWarnings } = compileExtractedTheme(extraction, {\n nameHint: options.nameHint,\n });\n const warnings = [...extraction.warnings, ...mapWarnings];\n\n let layouts: CustomTemplateDefinition[] | undefined;\n if (options.inferLayouts) {\n if (format !== 'pptx') {\n warnings.push('Layout inference is only available for PowerPoint files; skipped.');\n } else {\n const { analyzePptxLayouts } = await import('../pptx/layouts.js');\n const analysis = await analyzePptxLayouts(pkg, {\n colors: colorHintsFromExtraction(extraction),\n });\n warnings.push(...analysis.warnings);\n const defs = analysis.layouts\n .map((l) => (l.verdict.kind === 'custom' ? l.verdict.def : null))\n .filter((d): d is CustomTemplateDefinition => d !== null);\n if (defs.length > 0) layouts = defs;\n }\n }\n\n return { theme, extraction, ...(layouts ? { layouts } : {}), warnings };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,eAAe,aAAa,MAA4C;AAItE,MAAI,OAA0B;AAC9B,QAAM,OAAO;AACb,MACE,OAAO,KAAK,SAAS,YACrB,OAAO,KAAK,UAAU,cACtB,OAAO,KAAK,gBAAgB,YAC5B;AACA,WAAO,IAAI,WAAW,MAAM,KAAK,MAAM,GAAG,CAAC,EAAE,YAAY,CAAC;AAAA,EAC5D,WAAW,OAAQ,KAAqB,eAAe,UAAU;AAC/D,UAAM,MAAM;AACZ,WAAO,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI,GAAG,IAAI,UAAU,CAAC;AAAA,EAC3D;AACA,MAAI,CAAC,QAAQ,KAAK,SAAS,EAAG,QAAO;AAErC,SAAO,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM;AACjF;AAOA,SAAS,iBAAiB,KAAkD;AAC1E,QAAM,SAAS,CAAC,GAAG,IAAI,aAAa,UAAU,OAAO,GAAG,GAAG,IAAI,aAAa,SAAS,OAAO,CAAC;AAC7F,aAAW,MAAM,QAAQ;AACvB,QAAI,GAAG,SAAS,kBAAkB,EAAG,QAAO;AAC5C,QAAI,GAAG,SAAS,gBAAgB,EAAG,QAAO;AAC1C,QAAI,GAAG,SAAS,eAAe,EAAG,QAAO;AAAA,EAC3C;AACA,SAAO;AACT;AAEA,IAAM,aAGF;AAAA,EACF,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAUA,eAAsB,mBACpB,MACA,UAA6B,CAAC,GACF;AAC5B,MAAI,MAAM,aAAa,IAAI,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,YAAY,MAAM,OAAO;AAAA,EACvC,SAAS,KAAc;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,OAAO,IAAI;AAAA,IACf;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,UAAU,iBAAiB,GAAG;AACrD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,WAAW,MAAM,EAAE,GAAG;AAC/C,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,gBAAgB,iBAAiB,qCAAqC,EAAE,OAAO,CAAC;AAAA,EAC5F;AAEA,QAAM,EAAE,OAAO,UAAU,YAAY,IAAI,sBAAsB,YAAY;AAAA,IACzE,UAAU,QAAQ;AAAA,EACpB,CAAC;AACD,QAAM,WAAW,CAAC,GAAG,WAAW,UAAU,GAAG,WAAW;AAExD,MAAI;AACJ,MAAI,QAAQ,cAAc;AACxB,QAAI,WAAW,QAAQ;AACrB,eAAS,KAAK,mEAAmE;AAAA,IACnF,OAAO;AACL,YAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,uBAAoB;AAChE,YAAM,WAAW,MAAM,mBAAmB,KAAK;AAAA,QAC7C,QAAQ,yBAAyB,UAAU;AAAA,MAC7C,CAAC;AACD,eAAS,KAAK,GAAG,SAAS,QAAQ;AAClC,YAAM,OAAO,SAAS,QACnB,IAAI,CAAC,MAAO,EAAE,QAAQ,SAAS,WAAW,EAAE,QAAQ,MAAM,IAAK,EAC/D,OAAO,CAAC,MAAqC,MAAM,IAAI;AAC1D,UAAI,KAAK,SAAS,EAAG,WAAU;AAAA,IACjC;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,YAAY,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,GAAI,SAAS;AACxE;","names":[]}
@@ -1,4 +1,6 @@
1
1
  import { ContentContainer, MemoryContentContainer } from '@bendyline/squisq/storage';
2
+ import { c as ZipSafetyLimits } from '../zipLimits-bGYMS9cI.js';
3
+ export { Z as ZipSafetyError, a as ZipSafetyErrorCode, b as ZipSafetyErrorOptions } from '../zipLimits-bGYMS9cI.js';
2
4
 
3
5
  /**
4
6
  * Container ZIP serialization — convert between ContentContainer and ZIP archives.
@@ -13,6 +15,8 @@ import { ContentContainer, MemoryContentContainer } from '@bendyline/squisq/stor
13
15
  * timing.json
14
16
  */
15
17
 
18
+ type ZipToContainerOptions = ZipSafetyLimits;
19
+
16
20
  /**
17
21
  * Serialize a ContentContainer to a ZIP blob.
18
22
  *
@@ -33,6 +37,6 @@ declare function containerToZip(container: ContentContainer): Promise<Blob>;
33
37
  * @param zipData — The ZIP archive as ArrayBuffer, Uint8Array, or Blob
34
38
  * @returns A MemoryContentContainer populated with the ZIP's contents
35
39
  */
36
- declare function zipToContainer(zipData: ArrayBuffer | Uint8Array | Blob): Promise<MemoryContentContainer>;
40
+ declare function zipToContainer(zipData: ArrayBuffer | Uint8Array | Blob, options?: ZipToContainerOptions): Promise<MemoryContentContainer>;
37
41
 
38
- export { containerToZip, zipToContainer };
42
+ export { ZipSafetyLimits, type ZipToContainerOptions, containerToZip, zipToContainer };
@@ -1,3 +1,9 @@
1
+ import {
2
+ ZipSafetyError,
3
+ assertSafeZipPath,
4
+ openBoundedZipArchive
5
+ } from "../chunk-H7T5DXUU.js";
6
+
1
7
  // src/container/index.ts
2
8
  import JSZip from "jszip";
3
9
  import { MemoryContentContainer } from "@bendyline/squisq/storage";
@@ -5,6 +11,7 @@ async function containerToZip(container) {
5
11
  const zip = new JSZip();
6
12
  const entries = await container.listFiles();
7
13
  for (const entry of entries) {
14
+ assertSafeZipPath(entry.path);
8
15
  const data = await container.readFile(entry.path);
9
16
  if (data) {
10
17
  zip.file(entry.path, new Uint8Array(data));
@@ -16,27 +23,18 @@ async function containerToZip(container) {
16
23
  compressionOptions: { level: 6 }
17
24
  });
18
25
  }
19
- async function zipToContainer(zipData) {
20
- const zip = await JSZip.loadAsync(zipData);
26
+ async function zipToContainer(zipData, options = {}) {
27
+ const archive = await openBoundedZipArchive(zipData, options);
21
28
  const container = new MemoryContentContainer();
22
- const filePromises = [];
23
- zip.forEach((relativePath, zipEntry) => {
24
- if (zipEntry.dir) return;
25
- const entryPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
26
- if (!entryPath) return;
27
- if (entryPath.startsWith("/") || entryPath.includes("\\") || entryPath.split("/").some((seg) => seg === "..")) {
28
- return;
29
- }
30
- filePromises.push(
31
- zipEntry.async("arraybuffer").then((data) => {
32
- return container.writeFile(entryPath, data);
33
- })
34
- );
35
- });
36
- await Promise.all(filePromises);
29
+ for (const { path } of archive.entries) {
30
+ const data = await archive.read(path);
31
+ if (data) await container.writeFile(path, data);
32
+ archive.release(path);
33
+ }
37
34
  return container;
38
35
  }
39
36
  export {
37
+ ZipSafetyError,
40
38
  containerToZip,
41
39
  zipToContainer
42
40
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/container/index.ts"],"sourcesContent":["/**\n * Container ZIP serialization — convert between ContentContainer and ZIP archives.\n *\n * Uses JSZip (already a formats dependency) to serialize a ContentContainer to\n * a ZIP blob and to deserialize a ZIP blob into a MemoryContentContainer.\n *\n * ZIP structure mirrors the container's flat path hierarchy directly:\n * index.md\n * images/hero.jpg\n * audio/narration.mp3\n * timing.json\n */\n\nimport JSZip from 'jszip';\nimport type { ContentContainer } from '@bendyline/squisq/storage';\nimport { MemoryContentContainer } from '@bendyline/squisq/storage';\n\n/**\n * Serialize a ContentContainer to a ZIP blob.\n *\n * All files in the container are written to the ZIP archive preserving\n * their path structure. The resulting blob can be saved as a .zip file.\n *\n * @param container — The container to serialize\n * @returns A Blob containing the ZIP archive\n */\nexport async function containerToZip(container: ContentContainer): Promise<Blob> {\n const zip = new JSZip();\n const entries = await container.listFiles();\n\n for (const entry of entries) {\n const data = await container.readFile(entry.path);\n if (data) {\n zip.file(entry.path, new Uint8Array(data));\n }\n }\n\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n/**\n * Deserialize a ZIP archive into a MemoryContentContainer.\n *\n * Reads all files from the ZIP and writes them into a new MemoryContentContainer.\n * Directory entries are skipped. The resulting container can be used immediately\n * for rendering, editing, or saving to persistent storage.\n *\n * @param zipData — The ZIP archive as ArrayBuffer, Uint8Array, or Blob\n * @returns A MemoryContentContainer populated with the ZIP's contents\n */\nexport async function zipToContainer(\n zipData: ArrayBuffer | Uint8Array | Blob,\n): Promise<MemoryContentContainer> {\n const zip = await JSZip.loadAsync(zipData);\n const container = new MemoryContentContainer();\n\n const filePromises: Promise<void>[] = [];\n\n zip.forEach((relativePath, zipEntry) => {\n // Skip directories\n if (zipEntry.dir) return;\n\n // Strip leading slash if present\n const entryPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;\n if (!entryPath) return;\n\n // Path-traversal protection: reject absolute paths, backslashes, and .. segments\n if (\n entryPath.startsWith('/') ||\n entryPath.includes('\\\\') ||\n entryPath.split('/').some((seg) => seg === '..')\n ) {\n return;\n }\n\n filePromises.push(\n zipEntry.async('arraybuffer').then((data) => {\n return container.writeFile(entryPath, data);\n }),\n );\n });\n\n await Promise.all(filePromises);\n return container;\n}\n"],"mappings":";AAaA,OAAO,WAAW;AAElB,SAAS,8BAA8B;AAWvC,eAAsB,eAAe,WAA4C;AAC/E,QAAM,MAAM,IAAI,MAAM;AACtB,QAAM,UAAU,MAAM,UAAU,UAAU;AAE1C,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAO,MAAM,UAAU,SAAS,MAAM,IAAI;AAChD,QAAI,MAAM;AACR,UAAI,KAAK,MAAM,MAAM,IAAI,WAAW,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,IAAI,cAAc;AAAA,IACvB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,oBAAoB,EAAE,OAAO,EAAE;AAAA,EACjC,CAAC;AACH;AAYA,eAAsB,eACpB,SACiC;AACjC,QAAM,MAAM,MAAM,MAAM,UAAU,OAAO;AACzC,QAAM,YAAY,IAAI,uBAAuB;AAE7C,QAAM,eAAgC,CAAC;AAEvC,MAAI,QAAQ,CAAC,cAAc,aAAa;AAEtC,QAAI,SAAS,IAAK;AAGlB,UAAM,YAAY,aAAa,WAAW,GAAG,IAAI,aAAa,MAAM,CAAC,IAAI;AACzE,QAAI,CAAC,UAAW;AAGhB,QACE,UAAU,WAAW,GAAG,KACxB,UAAU,SAAS,IAAI,KACvB,UAAU,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,QAAQ,IAAI,GAC/C;AACA;AAAA,IACF;AAEA,iBAAa;AAAA,MACX,SAAS,MAAM,aAAa,EAAE,KAAK,CAAC,SAAS;AAC3C,eAAO,UAAU,UAAU,WAAW,IAAI;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/container/index.ts"],"sourcesContent":["/**\n * Container ZIP serialization — convert between ContentContainer and ZIP archives.\n *\n * Uses JSZip (already a formats dependency) to serialize a ContentContainer to\n * a ZIP blob and to deserialize a ZIP blob into a MemoryContentContainer.\n *\n * ZIP structure mirrors the container's flat path hierarchy directly:\n * index.md\n * images/hero.jpg\n * audio/narration.mp3\n * timing.json\n */\n\nimport JSZip from 'jszip';\nimport type { ContentContainer } from '@bendyline/squisq/storage';\nimport { MemoryContentContainer } from '@bendyline/squisq/storage';\nimport {\n assertSafeZipPath,\n openBoundedZipArchive,\n ZipSafetyError,\n type ZipSafetyLimits,\n type ZipSafetyErrorCode,\n type ZipSafetyErrorOptions,\n} from '../shared/zipSafety.js';\n\nexport type ZipToContainerOptions = ZipSafetyLimits;\nexport { ZipSafetyError };\nexport type { ZipSafetyLimits, ZipSafetyErrorCode, ZipSafetyErrorOptions };\n\n/**\n * Serialize a ContentContainer to a ZIP blob.\n *\n * All files in the container are written to the ZIP archive preserving\n * their path structure. The resulting blob can be saved as a .zip file.\n *\n * @param container — The container to serialize\n * @returns A Blob containing the ZIP archive\n */\nexport async function containerToZip(container: ContentContainer): Promise<Blob> {\n const zip = new JSZip();\n const entries = await container.listFiles();\n\n for (const entry of entries) {\n assertSafeZipPath(entry.path);\n const data = await container.readFile(entry.path);\n if (data) {\n zip.file(entry.path, new Uint8Array(data));\n }\n }\n\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n/**\n * Deserialize a ZIP archive into a MemoryContentContainer.\n *\n * Reads all files from the ZIP and writes them into a new MemoryContentContainer.\n * Directory entries are skipped. The resulting container can be used immediately\n * for rendering, editing, or saving to persistent storage.\n *\n * @param zipData — The ZIP archive as ArrayBuffer, Uint8Array, or Blob\n * @returns A MemoryContentContainer populated with the ZIP's contents\n */\nexport async function zipToContainer(\n zipData: ArrayBuffer | Uint8Array | Blob,\n options: ZipToContainerOptions = {},\n): Promise<MemoryContentContainer> {\n const archive = await openBoundedZipArchive(zipData, options);\n const container = new MemoryContentContainer();\n for (const { path } of archive.entries) {\n const data = await archive.read(path);\n if (data) await container.writeFile(path, data);\n archive.release(path);\n }\n return container;\n}\n"],"mappings":";;;;;;;AAaA,OAAO,WAAW;AAElB,SAAS,8BAA8B;AAuBvC,eAAsB,eAAe,WAA4C;AAC/E,QAAM,MAAM,IAAI,MAAM;AACtB,QAAM,UAAU,MAAM,UAAU,UAAU;AAE1C,aAAW,SAAS,SAAS;AAC3B,sBAAkB,MAAM,IAAI;AAC5B,UAAM,OAAO,MAAM,UAAU,SAAS,MAAM,IAAI;AAChD,QAAI,MAAM;AACR,UAAI,KAAK,MAAM,MAAM,IAAI,WAAW,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,IAAI,cAAc;AAAA,IACvB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,oBAAoB,EAAE,OAAO,EAAE;AAAA,EACjC,CAAC;AACH;AAYA,eAAsB,eACpB,SACA,UAAiC,CAAC,GACD;AACjC,QAAM,UAAU,MAAM,sBAAsB,SAAS,OAAO;AAC5D,QAAM,YAAY,IAAI,uBAAuB;AAC7C,aAAW,EAAE,KAAK,KAAK,QAAQ,SAAS;AACtC,UAAM,OAAO,MAAM,QAAQ,KAAK,IAAI;AACpC,QAAI,KAAM,OAAM,UAAU,UAAU,MAAM,IAAI;AAC9C,YAAQ,QAAQ,IAAI;AAAA,EACtB;AACA,SAAO;AACT;","names":[]}
@@ -1,6 +1,8 @@
1
- import { Doc } from '@bendyline/squisq/schemas';
1
+ import { ThemeRegistry, Doc } from '@bendyline/squisq/schemas';
2
2
  import { MarkdownDocument } from '@bendyline/squisq/markdown';
3
+ import { O as OoxmlOpenOptions } from '../reader-Gof6OrJX.js';
3
4
  import { ContentContainer } from '@bendyline/squisq/storage';
5
+ import '../zipLimits-bGYMS9cI.js';
4
6
 
5
7
  /**
6
8
  * DOCX Export
@@ -41,6 +43,8 @@ interface DocxExportOptions {
41
43
  * the theme's primary color to headings.
42
44
  */
43
45
  themeId?: string;
46
+ /** Explicit caller-owned registry for non-document custom themes. */
47
+ themeRegistry?: ThemeRegistry;
44
48
  /**
45
49
  * Pre-resolved image data keyed by image URL/path as it appears in the
46
50
  * markdown source. When provided, images are embedded in the .docx file
@@ -94,7 +98,7 @@ declare function docToDocx(doc: Doc, options?: DocxExportOptions): Promise<Array
94
98
  /**
95
99
  * Options for DOCX import.
96
100
  */
97
- interface DocxImportOptions {
101
+ interface DocxImportOptions extends OoxmlOpenOptions {
98
102
  /**
99
103
  * Whether to extract embedded images as base64 data URIs.
100
104
  * When false, images are represented as `[Image]` placeholders.
@@ -4,13 +4,15 @@ import {
4
4
  docxToDoc,
5
5
  docxToMarkdownDoc,
6
6
  markdownDocToDocx
7
- } from "../chunk-WWBIKRNP.js";
7
+ } from "../chunk-2CSZJEAL.js";
8
8
  import "../chunk-6M7Z25LA.js";
9
9
  import "../chunk-PN52A5AA.js";
10
- import "../chunk-SHLPSADL.js";
10
+ import "../chunk-XTLQXR4U.js";
11
11
  import "../chunk-U4MRIFKL.js";
12
+ import "../chunk-4HJ6AK2Q.js";
13
+ import "../chunk-H7T5DXUU.js";
12
14
  import "../chunk-SSUPBUF5.js";
13
- import "../chunk-DQAZR57U.js";
15
+ import "../chunk-O3GVVND4.js";
14
16
  export {
15
17
  docToDocx,
16
18
  docxToContainer,
@@ -1,4 +1,4 @@
1
- import { AudioSegment, Doc } from '@bendyline/squisq/schemas';
1
+ import { ThemeRegistry, AudioSegment, Doc } from '@bendyline/squisq/schemas';
2
2
  import { MarkdownDocument } from '@bendyline/squisq/markdown';
3
3
 
4
4
  /**
@@ -37,6 +37,8 @@ interface EpubExportOptions {
37
37
  publisher?: string;
38
38
  /** Squisq theme ID for CSS styling */
39
39
  themeId?: string;
40
+ /** Explicit caller-owned registry for non-document custom themes. */
41
+ themeRegistry?: ThemeRegistry;
40
42
  /** Pre-resolved image data keyed by relative path as it appears in the markdown */
41
43
  images?: Map<string, ArrayBuffer>;
42
44
  /** Cover image data (JPEG or PNG) */
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  docToEpub,
3
3
  markdownDocToEpub
4
- } from "../chunk-5LSSWZPU.js";
4
+ } from "../chunk-4VUWTSGM.js";
5
5
  import "../chunk-PN52A5AA.js";
6
6
  import "../chunk-U4MRIFKL.js";
7
- import "../chunk-MJGRI6XR.js";
8
- import "../chunk-DQAZR57U.js";
7
+ import "../chunk-MLX2BOJC.js";
8
+ import "../chunk-O3GVVND4.js";
9
9
  export {
10
10
  docToEpub,
11
11
  markdownDocToEpub
@@ -0,0 +1,69 @@
1
+ import { MarkdownDocument } from '@bendyline/squisq/markdown';
2
+ import { O as OoxmlOpenOptions } from './reader-Gof6OrJX.js';
3
+ import { Doc } from '@bendyline/squisq/schemas';
4
+
5
+ /**
6
+ * XLSX import — SpreadsheetML (.xlsx) → MarkdownDocument.
7
+ *
8
+ * Reuses the shared ooxml/ reader (zip + DOMParser). Reads the workbook's
9
+ * sheet list, resolves each sheet part via relationships, pulls shared strings,
10
+ * and turns each worksheet's cell grid into a markdown table (first row treated
11
+ * as the header). By default every sheet is imported, each preceded by an H1 of
12
+ * the sheet name; pass `options.sheet` (index or name) to import just one.
13
+ */
14
+
15
+ interface XlsxImportOptions extends OoxmlOpenOptions {
16
+ /** Which sheet to import (0-based index or sheet name). Default: all sheets. */
17
+ sheet?: number | string;
18
+ }
19
+ declare function xlsxToMarkdownDoc(data: ArrayBuffer | Blob, options?: XlsxImportOptions): Promise<MarkdownDocument>;
20
+
21
+ /**
22
+ * XLSX export — MarkdownDocument → SpreadsheetML (.xlsx).
23
+ *
24
+ * Tables-only fidelity (honestly documented): every `table` node in the
25
+ * markdown AST becomes one worksheet; all other content (headings, prose,
26
+ * lists, images, …) is dropped except that the nearest preceding heading is
27
+ * used to name the sheet. This mirrors the import side (`xlsxToMarkdownDoc`),
28
+ * which turns each worksheet grid back into a markdown table.
29
+ *
30
+ * Cells are emitted as inline strings (`t="inlineStr"`) so no sharedStrings
31
+ * part is needed, except values matching a plain-number pattern, which are
32
+ * emitted as numeric cells. The package is assembled with the shared ooxml/
33
+ * writer (auto-generates `[Content_Types].xml` + `_rels`), so only the
34
+ * SpreadsheetML-specific parts (workbook, worksheets, styles) are written here.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { parseMarkdown } from '@bendyline/squisq/markdown';
39
+ * import { markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';
40
+ *
41
+ * const md = parseMarkdown('# Metrics\n\n| A | B |\n| - | - |\n| 1 | 2 |');
42
+ * const buffer = await markdownDocToXlsx(md);
43
+ * ```
44
+ */
45
+
46
+ /**
47
+ * Options for XLSX export.
48
+ */
49
+ interface XlsxExportOptions {
50
+ /** Workbook title (written to core properties). */
51
+ title?: string;
52
+ /** Workbook author (written to core properties). */
53
+ author?: string;
54
+ /** Prefix used for auto-named sheets when no heading precedes a table. Default: "Sheet". */
55
+ sheetNamePrefix?: string;
56
+ }
57
+ /**
58
+ * Convert a MarkdownDocument to a .xlsx file (tables-only fidelity).
59
+ *
60
+ * Each markdown `table` becomes one worksheet; a document with no tables
61
+ * yields a single empty sheet (a valid, openable file — never throws).
62
+ */
63
+ declare function markdownDocToXlsx(doc: MarkdownDocument, options?: XlsxExportOptions): Promise<ArrayBuffer>;
64
+ /**
65
+ * Convert a squisq Doc to a .xlsx file (via the markdown table model).
66
+ */
67
+ declare function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<ArrayBuffer>;
68
+
69
+ export { type XlsxExportOptions as X, type XlsxImportOptions as a, docToXlsx as d, markdownDocToXlsx as m, xlsxToMarkdownDoc as x };
@@ -0,0 +1,15 @@
1
+ import {
2
+ extractDocxTheme,
3
+ extractPptxTheme,
4
+ extractXlsxTheme
5
+ } from "./chunk-CX3LH47J.js";
6
+ import "./chunk-X4USWOQ2.js";
7
+ import "./chunk-YRT7GQ5Y.js";
8
+ import "./chunk-4HJ6AK2Q.js";
9
+ import "./chunk-H7T5DXUU.js";
10
+ export {
11
+ extractDocxTheme,
12
+ extractPptxTheme,
13
+ extractXlsxTheme
14
+ };
15
+ //# sourceMappingURL=extract-LNOA7DBK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,52 +1,8 @@
1
- import { Doc, Theme } from '@bendyline/squisq/schemas';
1
+ import { Theme, ThemeRegistry, Doc } from '@bendyline/squisq/schemas';
2
+ import { H as HtmlExportOptions } from '../import-DTkDxHmZ.js';
3
+ export { a as HtmlImportOptions, c as collectImagePaths, h as htmlToMarkdown, b as htmlToMarkdownDoc, d as htmlToMarkdownDocSync } from '../import-DTkDxHmZ.js';
2
4
  import { HtmlPolicy, MarkdownDocument } from '@bendyline/squisq/markdown';
3
5
 
4
- /**
5
- * HTML Template Generation for SquisqPlayer Exports
6
- *
7
- * Generates complete, self-contained HTML documents that load the SquisqPlayer
8
- * IIFE bundle and render a Doc as either an interactive slideshow or a static
9
- * scrollable document.
10
- *
11
- * Two variants:
12
- * 1. **Inline** — All JS, CSS, and images are embedded in the HTML (single file).
13
- * 2. **External** — JS is referenced via `<script src>`, images via relative paths.
14
- */
15
-
16
- interface HtmlExportOptions {
17
- /** The IIFE player bundle source code (from @bendyline/squisq-react/standalone-source) */
18
- playerScript: string;
19
- /**
20
- * Map of relative image paths (as they appear in the Doc) to binary image data.
21
- * For inline HTML export, these are converted to base64 data URIs.
22
- * For ZIP export, these are written as separate files.
23
- */
24
- images?: Map<string, ArrayBuffer>;
25
- /**
26
- * Map of audio segment identifiers to binary audio data.
27
- * Keys should match the audio segment `name` or `url` fields in the Doc.
28
- * Only used in ZIP exports — single HTML uses timer-based playback.
29
- */
30
- audio?: Map<string, ArrayBuffer>;
31
- /** Rendering mode: 'slideshow' (interactive, default) or 'static' (scrollable) */
32
- mode?: 'slideshow' | 'static';
33
- /** HTML page title (default: 'Squisq Document') */
34
- title?: string;
35
- /** Auto-play slideshow on load (default: false) */
36
- autoPlay?: boolean;
37
- /**
38
- * Squisq theme ID to apply (e.g., 'documentary', 'cinematic').
39
- * When set, the theme is assigned to the Doc before rendering,
40
- * so the SquisqPlayer renders with that theme's colors and typography.
41
- */
42
- themeId?: string;
43
- }
44
- /**
45
- * Collect all relative image paths referenced in a Doc's layers and template blocks.
46
- * Returns a Set of unique relative paths that need to be resolved.
47
- */
48
- declare function collectImagePaths(doc: Doc): Set<string>;
49
-
50
6
  /**
51
7
  * Image Utilities for HTML Export
52
8
  *
@@ -134,6 +90,8 @@ interface PlainHtmlExportOptions {
134
90
  * When both `theme` and `themeId` are provided, `theme` wins.
135
91
  */
136
92
  themeId?: string;
93
+ /** Explicit caller-owned registry for non-document custom themes. */
94
+ themeRegistry?: ThemeRegistry;
137
95
  /**
138
96
  * Optional FontAwesome CSS text to inline into the rendered page,
139
97
  * replacing the default cross-origin `<link>` to cdnjs. Required for
@@ -191,13 +149,14 @@ interface PlainHtmlBundleOptions {
191
149
  /** Optional theme applied uniformly to every page. Overrides {@link themeId}. */
192
150
  theme?: Theme;
193
151
  /**
194
- * Optional theme id (e.g. `'warm-earth'`, `'gezellig'`). Resolved via
195
- * `resolveTheme` and applied to every page. Convenient for callers
196
- * that track themes by id (like the host export dialog) without
197
- * having to resolve to a `Theme` object first. When both `theme` and
198
- * `themeId` are supplied, `theme` wins.
152
+ * Optional theme id (e.g. `'warm-earth'`, `'gezellig'`) applied to every
153
+ * page. Each document resolves its own inline definition before consulting
154
+ * {@link themeRegistry}. When both `theme` and `themeId` are supplied,
155
+ * `theme` wins.
199
156
  */
200
157
  themeId?: string;
158
+ /** Explicit caller-owned registry for non-document custom themes. */
159
+ themeRegistry?: ThemeRegistry;
201
160
  /** Maximum recursion depth (default: unlimited; cycles always handled). */
202
161
  maxDepth?: number;
203
162
  /**
@@ -242,6 +201,7 @@ declare function collectLinkRefs(doc: MarkdownDocument): Set<string>;
242
201
  * `<a href>` at runtime — there is no post-render link rewrite hook in
243
202
  * the player path.
244
203
  */
204
+
245
205
  interface HtmlBundleOptions {
246
206
  /** Entry document path relative to the container root (e.g. `'home.md'`). */
247
207
  entryPath: string;
@@ -255,6 +215,8 @@ interface HtmlBundleOptions {
255
215
  title?: string;
256
216
  /** Theme id applied uniformly to every page. */
257
217
  themeId?: string;
218
+ /** Explicit caller-owned registry for non-document custom themes. */
219
+ themeRegistry?: ThemeRegistry;
258
220
  /** Rendering mode for every page (default: 'static' — scrollable, link-friendly). */
259
221
  mode?: 'slideshow' | 'static';
260
222
  /** Maximum recursion depth (default: unlimited; cycles always handled). */
@@ -278,38 +240,6 @@ interface HtmlBundleOptions {
278
240
  */
279
241
  declare function markdownDocsToHtmlBundle(options: HtmlBundleOptions): Promise<Blob>;
280
242
 
281
- /**
282
- * HTML Import Module — @bendyline/squisq-formats/html
283
- *
284
- * Converts an HTML document (or fragment) into a squisq `MarkdownDocument`.
285
- * Built on the existing `parseHtmlToNodes` (parse5-backed) + `sanitizeHtmlNodes`
286
- * from `@bendyline/squisq/markdown`, so no new dependency is introduced.
287
- *
288
- * The primary consumer is email: HTML mail bodies are hostile input, so the
289
- * parse is sanitized by default (scripts/styles/event handlers/dangerous URLs
290
- * stripped) before the tree is walked into markdown nodes.
291
- *
292
- * Two entry points mirror the docx/pdf importers:
293
- * - `htmlToMarkdownDoc(data, options?)` → `MarkdownDocument`
294
- * - `htmlToMarkdown(html, options?)` → markdown string (convenience for the
295
- * string-in/string-out path email body conversion needs)
296
- */
297
-
298
- interface HtmlImportOptions {
299
- /**
300
- * Strip scripts / styles / event handlers / dangerous URLs before walking
301
- * the tree. Default `true` — HTML email is untrusted; only disable for
302
- * trusted input where you want raw fidelity.
303
- */
304
- sanitize?: boolean;
305
- }
306
- /** Parse HTML into a squisq `MarkdownDocument`. */
307
- declare function htmlToMarkdownDocSync(html: string, options?: HtmlImportOptions): MarkdownDocument;
308
- /** Async, ArrayBuffer-accepting entry mirroring docx/pdf importers. */
309
- declare function htmlToMarkdownDoc(data: ArrayBuffer | Uint8Array | string, options?: HtmlImportOptions): Promise<MarkdownDocument>;
310
- /** Convenience string→string conversion (used for email HTML bodies). */
311
- declare function htmlToMarkdown(html: string, options?: HtmlImportOptions): string;
312
-
313
243
  /**
314
244
  * HTML Export Module — @bendyline/squisq-formats/html
315
245
  *
@@ -404,4 +334,4 @@ declare function docToHtml(doc: Doc, options: HtmlExportOptions): string;
404
334
  */
405
335
  declare function docToHtmlZip(doc: Doc, options: HtmlZipExportOptions): Promise<Blob>;
406
336
 
407
- export { type HtmlBundleOptions, type HtmlExportOptions, type HtmlImportOptions, type HtmlZipExportOptions, type PlainHtmlBundleOptions, type PlainHtmlExportOptions, arrayBufferToBase64DataUrl, collectImagePaths, collectLinkRefs, docToHtml, docToHtmlZip, extractFilename, htmlToMarkdown, htmlToMarkdownDoc, htmlToMarkdownDocSync, inferMimeType, markdownDocToPlainHtml, markdownDocsToHtmlBundle, markdownDocsToPlainHtmlBundle };
337
+ export { type HtmlBundleOptions, HtmlExportOptions, type HtmlZipExportOptions, type PlainHtmlBundleOptions, type PlainHtmlExportOptions, arrayBufferToBase64DataUrl, collectLinkRefs, docToHtml, docToHtmlZip, extractFilename, inferMimeType, markdownDocToPlainHtml, markdownDocsToHtmlBundle, markdownDocsToPlainHtmlBundle };
@@ -9,13 +9,13 @@ import {
9
9
  markdownDocToPlainHtml,
10
10
  markdownDocsToHtmlBundle,
11
11
  markdownDocsToPlainHtmlBundle
12
- } from "../chunk-46FMDOWZ.js";
12
+ } from "../chunk-LXYLOOST.js";
13
13
  import {
14
14
  arrayBufferToBase64DataUrl,
15
15
  extractFilename,
16
16
  inferMimeType
17
- } from "../chunk-MJGRI6XR.js";
18
- import "../chunk-DQAZR57U.js";
17
+ } from "../chunk-MLX2BOJC.js";
18
+ import "../chunk-O3GVVND4.js";
19
19
  export {
20
20
  arrayBufferToBase64DataUrl,
21
21
  collectImagePaths,
@@ -0,0 +1,7 @@
1
+ import {
2
+ extToMime
3
+ } from "./chunk-O3GVVND4.js";
4
+ export {
5
+ extToMime
6
+ };
7
+ //# sourceMappingURL=images-7FBWPKE3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,84 @@
1
+ import { ThemeRegistry, Doc } from '@bendyline/squisq/schemas';
2
+ import { MarkdownDocument } from '@bendyline/squisq/markdown';
3
+
4
+ /**
5
+ * HTML Template Generation for SquisqPlayer Exports
6
+ *
7
+ * Generates complete, self-contained HTML documents that load the SquisqPlayer
8
+ * IIFE bundle and render a Doc as either an interactive slideshow or a static
9
+ * scrollable document.
10
+ *
11
+ * Two variants:
12
+ * 1. **Inline** — All JS, CSS, and images are embedded in the HTML (single file).
13
+ * 2. **External** — JS is referenced via `<script src>`, images via relative paths.
14
+ */
15
+
16
+ interface HtmlExportOptions {
17
+ /** The IIFE player bundle source code (from @bendyline/squisq-react/standalone-source) */
18
+ playerScript: string;
19
+ /**
20
+ * Map of relative image paths (as they appear in the Doc) to binary image data.
21
+ * For inline HTML export, these are converted to base64 data URIs.
22
+ * For ZIP export, these are written as separate files.
23
+ */
24
+ images?: Map<string, ArrayBuffer>;
25
+ /**
26
+ * Map of audio segment identifiers to binary audio data.
27
+ * Keys should match the audio segment `name` or `url` fields in the Doc.
28
+ * Only used in ZIP exports — single HTML uses timer-based playback.
29
+ */
30
+ audio?: Map<string, ArrayBuffer>;
31
+ /** Rendering mode: 'slideshow' (interactive, default) or 'static' (scrollable) */
32
+ mode?: 'slideshow' | 'static';
33
+ /** HTML page title (default: 'Squisq Document') */
34
+ title?: string;
35
+ /** Auto-play slideshow on load (default: false) */
36
+ autoPlay?: boolean;
37
+ /**
38
+ * Squisq theme ID to apply (e.g., 'documentary', 'cinematic').
39
+ * When set, the theme is assigned to the Doc before rendering,
40
+ * so the SquisqPlayer renders with that theme's colors and typography.
41
+ */
42
+ themeId?: string;
43
+ /** Explicit caller-owned registry for non-document custom themes. */
44
+ themeRegistry?: ThemeRegistry;
45
+ }
46
+ /**
47
+ * Collect all relative image paths referenced in a Doc's layers and template blocks.
48
+ * Returns a Set of unique relative paths that need to be resolved.
49
+ */
50
+ declare function collectImagePaths(doc: Doc): Set<string>;
51
+
52
+ /**
53
+ * HTML Import Module — @bendyline/squisq-formats/html
54
+ *
55
+ * Converts an HTML document (or fragment) into a squisq `MarkdownDocument`.
56
+ * Built on the existing `parseHtmlToNodes` (parse5-backed) + `sanitizeHtmlNodes`
57
+ * from `@bendyline/squisq/markdown`, so no new dependency is introduced.
58
+ *
59
+ * The primary consumer is email: HTML mail bodies are hostile input, so the
60
+ * parse is sanitized by default (scripts/styles/event handlers/dangerous URLs
61
+ * stripped) before the tree is walked into markdown nodes.
62
+ *
63
+ * Two entry points mirror the docx/pdf importers:
64
+ * - `htmlToMarkdownDoc(data, options?)` → `MarkdownDocument`
65
+ * - `htmlToMarkdown(html, options?)` → markdown string (convenience for the
66
+ * string-in/string-out path email body conversion needs)
67
+ */
68
+
69
+ interface HtmlImportOptions {
70
+ /**
71
+ * Strip scripts / styles / event handlers / dangerous URLs before walking
72
+ * the tree. Default `true` — HTML email is untrusted; only disable for
73
+ * trusted input where you want raw fidelity.
74
+ */
75
+ sanitize?: boolean;
76
+ }
77
+ /** Parse HTML into a squisq `MarkdownDocument`. */
78
+ declare function htmlToMarkdownDocSync(html: string, options?: HtmlImportOptions): MarkdownDocument;
79
+ /** Async, ArrayBuffer-accepting entry mirroring docx/pdf importers. */
80
+ declare function htmlToMarkdownDoc(data: ArrayBuffer | Uint8Array | string, options?: HtmlImportOptions): Promise<MarkdownDocument>;
81
+ /** Convenience string→string conversion (used for email HTML bodies). */
82
+ declare function htmlToMarkdown(html: string, options?: HtmlImportOptions): string;
83
+
84
+ export { type HtmlExportOptions as H, type HtmlImportOptions as a, htmlToMarkdownDoc as b, collectImagePaths as c, htmlToMarkdownDocSync as d, htmlToMarkdown as h };