@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,14 +1,12 @@
1
- import {
2
- baseDirOf,
3
- findRelByType,
4
- resolveTarget
5
- } from "./chunk-YRT7GQ5Y.js";
6
1
  import {
7
2
  NS_DRAWINGML,
8
3
  REL_THEME,
4
+ baseDirOf,
5
+ findRelByType,
9
6
  getPartRelationships,
10
- getPartXml
11
- } from "./chunk-7ARKUCQT.js";
7
+ getPartXml,
8
+ resolveTarget
9
+ } from "./chunk-X2DEAXNK.js";
12
10
 
13
11
  // src/ooxml/themeReader.ts
14
12
  var HEX6_RE = /^[0-9a-fA-F]{6}$/;
@@ -128,4 +126,3 @@ export {
128
126
  parseThemeXml,
129
127
  readThemePart
130
128
  };
131
- //# sourceMappingURL=chunk-SC67HYQJ.js.map
@@ -0,0 +1,121 @@
1
+ import {
2
+ stripHtmlTags
3
+ } from "./chunk-SBUW7NHR.js";
4
+
5
+ // src/ooxml/relIds.ts
6
+ var RelIdAllocator = class {
7
+ constructor() {
8
+ /** part path → next free numeric suffix */
9
+ this.next = /* @__PURE__ */ new Map();
10
+ }
11
+ /**
12
+ * Allocate the next free relationship id for `part`'s rels file.
13
+ *
14
+ * @param part - Zip path of the part that owns the relationship
15
+ * (e.g. `"word/document.xml"`). Use `""` for the package root.
16
+ * Ids are only unique per `part`, which is exactly the OOXML rule.
17
+ */
18
+ alloc(part = "") {
19
+ const n = this.next.get(part) ?? 1;
20
+ this.next.set(part, n + 1);
21
+ return `rId${n}`;
22
+ }
23
+ };
24
+
25
+ // src/shared/ooxmlColor.ts
26
+ var HEX3_RE = /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/;
27
+ var HEX6_RE = /^#?([0-9a-fA-F]{6})$/;
28
+ var HEX8_RE = /^#?([0-9a-fA-F]{6})[0-9a-fA-F]{2}$/;
29
+ function toOoxmlHex(color, fallback = "000000") {
30
+ const normalized = normalizeOoxmlHex(color);
31
+ if (normalized) return normalized;
32
+ return normalizeOoxmlHex(fallback) ?? "000000";
33
+ }
34
+ function normalizeOoxmlHex(color) {
35
+ if (typeof color !== "string") return void 0;
36
+ const trimmed = color.trim();
37
+ const six = HEX6_RE.exec(trimmed);
38
+ if (six) return six[1].toUpperCase();
39
+ const eight = HEX8_RE.exec(trimmed);
40
+ if (eight) return eight[1].toUpperCase();
41
+ const three = HEX3_RE.exec(trimmed);
42
+ if (three) {
43
+ const [, r, g, b] = three;
44
+ return `${r}${r}${g}${g}${b}${b}`.toUpperCase();
45
+ }
46
+ return void 0;
47
+ }
48
+
49
+ // src/shared/officeHyperlinks.ts
50
+ import { sanitizeUrl } from "@bendyline/squisq/markdown";
51
+ function sanitizeOfficeHyperlink(input, policy = {}) {
52
+ if (typeof input !== "string") return null;
53
+ const value = input.trim();
54
+ if (!value || hasControls(value)) return null;
55
+ if (value.startsWith("#")) return /^#[^\s]*$/.test(value) ? value : null;
56
+ if (/^(?:\\\\|\/\/|\/|[a-z]:[\\/])/i.test(value)) return null;
57
+ const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(value);
58
+ if (hasScheme) return sanitizeUrl(value);
59
+ if (!policy.allowRelative || value.includes("\\")) return null;
60
+ let decoded;
61
+ try {
62
+ decoded = decodeURIComponent(value);
63
+ } catch {
64
+ return null;
65
+ }
66
+ if (hasControls(decoded) || decoded.includes("\\") || decoded.startsWith("//")) return null;
67
+ return value;
68
+ }
69
+ function hasControls(value) {
70
+ for (const char of value) {
71
+ const code = char.charCodeAt(0);
72
+ if (code <= 31 || code === 127) return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ // src/shared/inlineRuns.ts
78
+ function inlineNodesToRuns(nodes, handlers, format = {}) {
79
+ let out = "";
80
+ for (const node of nodes) {
81
+ out += inlineNodeToRuns(node, handlers, format);
82
+ }
83
+ return out;
84
+ }
85
+ function inlineNodeToRuns(node, handlers, format) {
86
+ switch (node.type) {
87
+ case "text":
88
+ return handlers.run(node.value, format);
89
+ case "strong":
90
+ return inlineNodesToRuns(node.children, handlers, { ...format, bold: true });
91
+ case "emphasis":
92
+ return inlineNodesToRuns(node.children, handlers, { ...format, italic: true });
93
+ case "delete":
94
+ return inlineNodesToRuns(node.children, handlers, { ...format, strike: true });
95
+ case "inlineCode":
96
+ return handlers.run(node.value, { ...format, code: true });
97
+ case "inlineMath":
98
+ return handlers.run(node.value, { ...format, code: true });
99
+ case "htmlInline":
100
+ return handlers.run(stripHtmlTags(node.rawHtml), format);
101
+ case "link":
102
+ return handlers.link(node, format);
103
+ case "image":
104
+ return handlers.image(node, format);
105
+ case "break":
106
+ return handlers.lineBreak();
107
+ case "footnoteReference":
108
+ return handlers.footnoteRef ? handlers.footnoteRef(node) : "";
109
+ default:
110
+ return "";
111
+ }
112
+ }
113
+
114
+ export {
115
+ RelIdAllocator,
116
+ toOoxmlHex,
117
+ normalizeOoxmlHex,
118
+ sanitizeOfficeHyperlink,
119
+ inlineNodesToRuns,
120
+ inlineNodeToRuns
121
+ };
@@ -12,4 +12,3 @@ async function buildContainer(markdown, images) {
12
12
  export {
13
13
  buildContainer
14
14
  };
15
- //# sourceMappingURL=chunk-SSUPBUF5.js.map
@@ -1,23 +1,21 @@
1
1
  import {
2
+ RelIdAllocator,
2
3
  inlineNodeToRuns,
3
- inlineNodesToRuns
4
- } from "./chunk-6M7Z25LA.js";
4
+ inlineNodesToRuns,
5
+ sanitizeOfficeHyperlink,
6
+ toOoxmlHex
7
+ } from "./chunk-GX7RAUME.js";
5
8
  import {
6
9
  extractPlainText,
7
10
  stripHtmlTags
8
- } from "./chunk-PN52A5AA.js";
9
- import {
10
- attrNS,
11
- baseDirOf,
12
- resolveTarget
13
- } from "./chunk-YRT7GQ5Y.js";
11
+ } from "./chunk-SBUW7NHR.js";
14
12
  import {
15
13
  createPackage
16
- } from "./chunk-KURGXM4I.js";
14
+ } from "./chunk-4V3KCHAP.js";
17
15
  import {
18
16
  escapeXml,
19
17
  xmlDeclaration
20
- } from "./chunk-U4MRIFKL.js";
18
+ } from "./chunk-JU2RHXUB.js";
21
19
  import {
22
20
  CONTENT_TYPE_PPTX_PRESENTATION,
23
21
  CONTENT_TYPE_PPTX_SLIDE,
@@ -36,20 +34,24 @@ import {
36
34
  REL_SLIDE_LAYOUT,
37
35
  REL_SLIDE_MASTER,
38
36
  REL_THEME,
37
+ attrNS,
38
+ baseDirOf,
39
39
  getPartBinary,
40
40
  getPartRelationships,
41
41
  getPartXml,
42
- openPackage
43
- } from "./chunk-7ARKUCQT.js";
42
+ openPackage,
43
+ requireMainPartPath,
44
+ resolveTarget
45
+ } from "./chunk-X2DEAXNK.js";
44
46
  import {
45
47
  buildContainer
46
- } from "./chunk-SSUPBUF5.js";
48
+ } from "./chunk-IIQYS2YH.js";
47
49
  import {
48
50
  inferMimeType
49
- } from "./chunk-MLX2BOJC.js";
51
+ } from "./chunk-6RQOV3B3.js";
50
52
  import {
51
53
  extToMime
52
- } from "./chunk-O3GVVND4.js";
54
+ } from "./chunk-AONELFLA.js";
53
55
 
54
56
  // src/pptx/export.ts
55
57
  import { resolveFontFamily } from "@bendyline/squisq/schemas";
@@ -94,6 +96,7 @@ function buildThemeXml() {
94
96
 
95
97
  // src/pptx/export.ts
96
98
  async function markdownDocToPptx(doc, options = {}) {
99
+ options.signal?.throwIfAborted();
97
100
  const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);
98
101
  const style = resolveSlideStyle(themeId, options, doc);
99
102
  const slides = segmentIntoSlides(doc.children, options.slideBreak ?? "h2");
@@ -103,7 +106,14 @@ async function markdownDocToPptx(doc, options = {}) {
103
106
  const slideXmls = [];
104
107
  const slideContexts = [];
105
108
  for (let i = 0; i < slides.length; i++) {
106
- const ctx = new SlideContext(style, options.images, i);
109
+ if ((i & 31) === 0) options.signal?.throwIfAborted();
110
+ const ctx = new SlideContext(
111
+ style,
112
+ options.images,
113
+ i,
114
+ options.allowRelativeHyperlinks ?? false,
115
+ options.signal
116
+ );
107
117
  const xml = buildSlideXml(slides[i], ctx);
108
118
  slideXmls.push(xml);
109
119
  slideContexts.push(ctx);
@@ -134,20 +144,17 @@ function resolveSlideStyle(themeId, options, doc) {
134
144
  const theme = resolveThemeForDoc(doc, themeId, options.themeRegistry);
135
145
  const c = theme.colors;
136
146
  return {
137
- background: stripHash(c.background),
138
- text: stripHash(c.text),
139
- titleColor: stripHash(c.highlight || c.secondary || c.text),
140
- mutedColor: stripHash(c.textMuted || c.text),
147
+ background: toOoxmlHex(c.background, "FFFFFF"),
148
+ text: toOoxmlHex(c.text, "333333"),
149
+ titleColor: toOoxmlHex(c.highlight || c.secondary || c.text, "333333"),
150
+ mutedColor: toOoxmlHex(c.textMuted || c.text, "666666"),
141
151
  titleFont: resolveFontFamily(theme.typography?.titleFont, DEFAULT_TITLE_FONT),
142
152
  bodyFont: resolveFontFamily(theme.typography?.bodyFont, options.defaultFont || DEFAULT_FONT),
143
153
  codeFont: resolveFontFamily(theme.typography?.monoFont, DEFAULT_CODE_FONT),
144
- codeColor: stripHash(c.textMuted || c.text),
154
+ codeColor: toOoxmlHex(c.textMuted || c.text, "333333"),
145
155
  hasTheme: true
146
156
  };
147
157
  }
148
- function stripHash(color) {
149
- return color.startsWith("#") ? color.slice(1) : color;
150
- }
151
158
  function segmentIntoSlides(children, slideBreak) {
152
159
  const maxDepth = slideBreak === "h1" ? 1 : slideBreak === "h2" ? 2 : 6;
153
160
  const slides = [];
@@ -172,28 +179,44 @@ function segmentIntoSlides(children, slideBreak) {
172
179
  return slides;
173
180
  }
174
181
  var SlideContext = class {
175
- // 1=group, 2=title, 3=body
176
- constructor(style, images, slideIndex) {
177
- this.nextRelId = 1;
182
+ constructor(style, images, slideIndex, allowRelativeHyperlinks, signal) {
183
+ /**
184
+ * One id source for this slide's rels part — see `RelIdAllocator`. There is
185
+ * exactly one SlideContext per `ppt/slides/slideN.xml`, and EVERY rel on
186
+ * that part (layout, hyperlinks, images) is allocated here, so duplicate
187
+ * `Id`s are impossible by construction.
188
+ */
189
+ this.relIds = new RelIdAllocator();
178
190
  this.relationships = [];
179
191
  this.embeddedImages = [];
180
192
  this.nextShapeId = 4;
181
193
  this.style = style;
182
194
  this.images = images;
183
195
  this.slideIndex = slideIndex;
196
+ this.allowRelativeHyperlinks = allowRelativeHyperlinks;
197
+ this.signal = signal;
198
+ this.relationships.push({
199
+ id: this.allocRelId(),
200
+ type: REL_SLIDE_LAYOUT,
201
+ target: "../slideLayouts/slideLayout1.xml"
202
+ });
184
203
  }
185
204
  allocRelId() {
186
- return `rId${this.nextRelId++ + 1}`;
205
+ return this.relIds.alloc();
187
206
  }
188
207
  allocShapeId() {
189
208
  return this.nextShapeId++;
190
209
  }
191
210
  addHyperlink(url) {
211
+ const target = sanitizeOfficeHyperlink(url, {
212
+ allowRelative: this.allowRelativeHyperlinks
213
+ });
214
+ if (!target) return null;
192
215
  const id = this.allocRelId();
193
216
  this.relationships.push({
194
217
  id,
195
218
  type: REL_HYPERLINK,
196
- target: url,
219
+ target,
197
220
  targetMode: "External"
198
221
  });
199
222
  return id;
@@ -556,7 +579,7 @@ function convertCodeBlock(node, ctx) {
556
579
  const parts = [];
557
580
  for (const line of lines) {
558
581
  parts.push(
559
- `<a:p><a:r><a:rPr lang="en-US" sz="${DEFAULT_CODE_SIZE}" dirty="0"><a:latin typeface="${escapeXml(ctx.style.codeFont)}"/><a:solidFill><a:srgbClr val="${ctx.style.codeColor}"/></a:solidFill></a:rPr><a:t>${escapeXml(line || " ")}</a:t></a:r></a:p>`
582
+ `<a:p><a:r><a:rPr lang="en-US" sz="${DEFAULT_CODE_SIZE}" dirty="0"><a:solidFill><a:srgbClr val="${ctx.style.codeColor}"/></a:solidFill><a:latin typeface="${escapeXml(ctx.style.codeFont)}"/></a:rPr><a:t>${escapeXml(line || " ")}</a:t></a:r></a:p>`
560
583
  );
561
584
  }
562
585
  return parts.join("");
@@ -611,8 +634,8 @@ function makeRun(text, format, style) {
611
634
  if (format.strike) rPrParts.push(`strike="sngStrike"`);
612
635
  let innerParts = "";
613
636
  if (format.code) {
614
- innerParts += `<a:latin typeface="${escapeXml(style.codeFont)}"/>`;
615
637
  innerParts += `<a:solidFill><a:srgbClr val="${style.codeColor}"/></a:solidFill>`;
638
+ innerParts += `<a:latin typeface="${escapeXml(style.codeFont)}"/>`;
616
639
  rPrParts.push(`sz="${DEFAULT_CODE_SIZE}"`);
617
640
  } else if (style.hasTheme) {
618
641
  innerParts += `<a:solidFill><a:srgbClr val="${style.text}"/></a:solidFill>`;
@@ -621,6 +644,7 @@ function makeRun(text, format, style) {
621
644
  }
622
645
  function convertLink(node, ctx, format) {
623
646
  const rId = ctx.addHyperlink(node.url);
647
+ if (!rId) return convertInlines(node.children, ctx, format);
624
648
  const parts = [];
625
649
  for (const child of node.children) {
626
650
  if (child.type === "text") {
@@ -652,12 +676,16 @@ function convertImage(node, format, ctx) {
652
676
  async function buildPptxPackage(slideXmls, slideContexts, options) {
653
677
  const pkg = createPackage();
654
678
  const slideCount = slideXmls.length;
655
- const slideMasterRelId = "rId100";
656
- const themeRelId = "rId101";
679
+ const relIds = new RelIdAllocator();
680
+ const PRESENTATION_PART = "ppt/presentation.xml";
681
+ const SLIDE_MASTER_PART = "ppt/slideMasters/slideMaster1.xml";
682
+ const SLIDE_LAYOUT_PART = "ppt/slideLayouts/slideLayout1.xml";
657
683
  const slideRelIds = [];
658
684
  for (let i = 0; i < slideCount; i++) {
659
- slideRelIds.push(`rId${i + 1}`);
685
+ slideRelIds.push(relIds.alloc(PRESENTATION_PART));
660
686
  }
687
+ const slideMasterRelId = relIds.alloc(PRESENTATION_PART);
688
+ const themeRelId = relIds.alloc(PRESENTATION_PART);
661
689
  const presentationXml = buildPresentationXml(
662
690
  slideCount,
663
691
  slideRelIds,
@@ -668,11 +696,6 @@ async function buildPptxPackage(slideXmls, slideContexts, options) {
668
696
  for (let i = 0; i < slideCount; i++) {
669
697
  const slidePath = `ppt/slides/slide${i + 1}.xml`;
670
698
  pkg.addPart(slidePath, slideXmls[i], CONTENT_TYPE_PPTX_SLIDE);
671
- pkg.addRelationship(slidePath, {
672
- id: "rId1",
673
- type: REL_SLIDE_LAYOUT,
674
- target: "../slideLayouts/slideLayout1.xml"
675
- });
676
699
  const ctx = slideContexts[i];
677
700
  for (const rel of ctx.relationships) {
678
701
  pkg.addRelationship(slidePath, {
@@ -686,24 +709,24 @@ async function buildPptxPackage(slideXmls, slideContexts, options) {
686
709
  pkg.addBinaryPart(img.mediaPath, img.data, img.contentType);
687
710
  }
688
711
  }
689
- const layoutMasterRelId = "rId1";
712
+ const layoutMasterRelId = relIds.alloc(SLIDE_LAYOUT_PART);
690
713
  const slideLayoutXml = buildSlideLayoutXml(layoutMasterRelId);
691
- pkg.addPart("ppt/slideLayouts/slideLayout1.xml", slideLayoutXml, CONTENT_TYPE_PPTX_SLIDE_LAYOUT);
692
- pkg.addRelationship("ppt/slideLayouts/slideLayout1.xml", {
714
+ pkg.addPart(SLIDE_LAYOUT_PART, slideLayoutXml, CONTENT_TYPE_PPTX_SLIDE_LAYOUT);
715
+ pkg.addRelationship(SLIDE_LAYOUT_PART, {
693
716
  id: layoutMasterRelId,
694
717
  type: REL_SLIDE_MASTER,
695
718
  target: "../slideMasters/slideMaster1.xml"
696
719
  });
697
- const masterLayoutRelId = "rId1";
698
- const masterThemeRelId = "rId2";
720
+ const masterLayoutRelId = relIds.alloc(SLIDE_MASTER_PART);
721
+ const masterThemeRelId = relIds.alloc(SLIDE_MASTER_PART);
699
722
  const slideMasterXml = buildSlideMasterXml(masterLayoutRelId);
700
- pkg.addPart("ppt/slideMasters/slideMaster1.xml", slideMasterXml, CONTENT_TYPE_PPTX_SLIDE_MASTER);
701
- pkg.addRelationship("ppt/slideMasters/slideMaster1.xml", {
723
+ pkg.addPart(SLIDE_MASTER_PART, slideMasterXml, CONTENT_TYPE_PPTX_SLIDE_MASTER);
724
+ pkg.addRelationship(SLIDE_MASTER_PART, {
702
725
  id: masterLayoutRelId,
703
726
  type: REL_SLIDE_LAYOUT,
704
727
  target: "../slideLayouts/slideLayout1.xml"
705
728
  });
706
- pkg.addRelationship("ppt/slideMasters/slideMaster1.xml", {
729
+ pkg.addRelationship(SLIDE_MASTER_PART, {
707
730
  id: masterThemeRelId,
708
731
  type: REL_THEME,
709
732
  target: "../theme/theme1.xml"
@@ -755,17 +778,20 @@ import { markdownToDoc } from "@bendyline/squisq/doc";
755
778
 
756
779
  // src/pptx/import.ts
757
780
  import { stringifyMarkdown } from "@bendyline/squisq/markdown";
758
- async function orderedSlidePaths(pkg) {
759
- const pres = await getPartXml(pkg, "ppt/presentation.xml");
760
- if (!pres) return [];
761
- const rels = await getPartRelationships(pkg, "ppt/presentation.xml");
781
+ var PPTX_MAIN_PART = "ppt/presentation.xml";
782
+ async function orderedSlidePaths(pkg, mainPart) {
783
+ const pres = await getPartXml(pkg, mainPart);
784
+ if (!pres) {
785
+ throw new Error(`Invalid PPTX package: presentation part "${mainPart}" could not be parsed.`);
786
+ }
787
+ const rels = await getPartRelationships(pkg, mainPart);
762
788
  const relById = new Map(rels.map((r) => [r.id, r.target]));
763
789
  const out = [];
764
790
  const ids = pres.getElementsByTagNameNS(NS_PML, "sldId");
765
791
  for (let i = 0; i < ids.length; i++) {
766
792
  const rid = attrNS(ids[i], NS_R, "id", "r:id");
767
793
  const target = rid ? relById.get(rid) : void 0;
768
- if (target) out.push(resolveTarget("ppt", target));
794
+ if (target) out.push(resolveTarget(baseDirOf(mainPart), target));
769
795
  }
770
796
  return out;
771
797
  }
@@ -959,7 +985,7 @@ async function importDocument(pkg, options) {
959
985
  let extraction = null;
960
986
  if (inferTheme || inferLayouts) {
961
987
  try {
962
- const { extractPptxTheme } = await import("./extract-OJ7ZQV6P.js");
988
+ const { extractPptxTheme } = await import("./extract-MN7LA3NL.js");
963
989
  extraction = await extractPptxTheme(pkg);
964
990
  } catch (err) {
965
991
  warnInferenceFailure("theme extraction", err);
@@ -968,7 +994,7 @@ async function importDocument(pkg, options) {
968
994
  let theme;
969
995
  if (inferTheme && extraction) {
970
996
  try {
971
- const { compileExtractedTheme } = await import("./mapTheme-IR27S6IV.js");
997
+ const { compileExtractedTheme } = await import("./mapTheme-4TWH25FT.js");
972
998
  theme = compileExtractedTheme(extraction).theme;
973
999
  } catch (err) {
974
1000
  warnInferenceFailure("theme compilation", err);
@@ -976,8 +1002,8 @@ async function importDocument(pkg, options) {
976
1002
  }
977
1003
  if (inferLayouts) {
978
1004
  try {
979
- const { analyzePptxLayouts: analyzePptxLayouts2 } = await import("./layouts-5VDIRPIJ.js");
980
- const { colorHintsFromExtraction } = await import("./mapTheme-IR27S6IV.js");
1005
+ const { analyzePptxLayouts: analyzePptxLayouts2 } = await import("./layouts-DRWZGSPD.js");
1006
+ const { colorHintsFromExtraction } = await import("./mapTheme-4TWH25FT.js");
981
1007
  ctx.inference = await analyzePptxLayouts2(pkg, {
982
1008
  colors: extraction ? colorHintsFromExtraction(extraction) : {}
983
1009
  });
@@ -985,7 +1011,7 @@ async function importDocument(pkg, options) {
985
1011
  warnInferenceFailure("layout inference", err);
986
1012
  }
987
1013
  }
988
- const paths = await orderedSlidePaths(pkg);
1014
+ const paths = await orderedSlidePaths(pkg, requireMainPartPath(pkg, PPTX_MAIN_PART, "PPTX"));
989
1015
  const children = [];
990
1016
  for (let i = 0; i < paths.length; i++) {
991
1017
  children.push(...await convertSlide(paths[i], i, ctx));
@@ -1037,4 +1063,3 @@ export {
1037
1063
  pptxToContainer,
1038
1064
  pptxToDoc
1039
1065
  };
1040
- //# sourceMappingURL=chunk-RLU7UFYU.js.map
@@ -2,7 +2,7 @@ import {
2
2
  arrayBufferToBase64DataUrl,
3
3
  extractFilename,
4
4
  inferMimeType
5
- } from "./chunk-MLX2BOJC.js";
5
+ } from "./chunk-6RQOV3B3.js";
6
6
 
7
7
  // src/html/index.ts
8
8
  import JSZip3 from "jszip";
@@ -119,7 +119,23 @@ function scanTemplateImageFields(obj, paths) {
119
119
  }
120
120
  }
121
121
  function escapeForScript(str) {
122
- return str.replace(/<\/(script)/gi, "<\\/$1");
122
+ return str.replace(/<(?=!--|\/?script)/gi, "\\u003c");
123
+ }
124
+ function buildInlineImageMaps(images) {
125
+ const imageMap = {};
126
+ const imageAliases = {};
127
+ if (!images) return { imageMap, imageAliases };
128
+ const canonicalKeyFor = /* @__PURE__ */ new Map();
129
+ for (const [path, buffer] of images.entries()) {
130
+ const canonical = canonicalKeyFor.get(buffer);
131
+ if (canonical !== void 0) {
132
+ imageAliases[path] = canonical;
133
+ continue;
134
+ }
135
+ canonicalKeyFor.set(buffer, path);
136
+ imageMap[path] = arrayBufferToBase64DataUrl(buffer, inferMimeType(path));
137
+ }
138
+ return { imageMap, imageAliases };
123
139
  }
124
140
  function generateInlineHtml(doc, options) {
125
141
  const {
@@ -132,15 +148,10 @@ function generateInlineHtml(doc, options) {
132
148
  themeRegistry
133
149
  } = options;
134
150
  doc = applyThemeSelection(doc, themeId, themeRegistry);
135
- const imageMap = {};
136
- if (images) {
137
- for (const [path, buffer] of images.entries()) {
138
- const mimeType = inferMimeType(path);
139
- imageMap[path] = arrayBufferToBase64DataUrl(buffer, mimeType);
140
- }
141
- }
151
+ const { imageMap, imageAliases } = buildInlineImageMaps(images);
142
152
  const docJson = escapeForScript(JSON.stringify(doc));
143
153
  const imageMapJson = escapeForScript(JSON.stringify(imageMap));
154
+ const imageAliasJson = escapeForScript(JSON.stringify(imageAliases));
144
155
  return `<!DOCTYPE html>
145
156
  <html lang="en">
146
157
  <head>
@@ -161,6 +172,14 @@ ${mode === "static" ? "#squisq-root{display:block}" : ""}
161
172
  (function(){
162
173
  var doc = JSON.parse(${JSON.stringify(docJson)});
163
174
  var images = JSON.parse(${JSON.stringify(imageMapJson)});
175
+ // Re-expand path/basename aliases that share a payload (see
176
+ // buildInlineImageMaps) so \`images\` has every key the resolver may ask for.
177
+ var aliases = JSON.parse(${JSON.stringify(imageAliasJson)});
178
+ for (var alias in aliases) {
179
+ if (Object.prototype.hasOwnProperty.call(aliases, alias)) {
180
+ images[alias] = images[aliases[alias]];
181
+ }
182
+ }
164
183
  SquisqPlayer.mount(document.getElementById("squisq-root"), doc, {
165
184
  mode: ${JSON.stringify(mode)},
166
185
  images: images,
@@ -248,21 +267,31 @@ import {
248
267
  import { resolveFontFamily, buildGoogleFontsUrl } from "@bendyline/squisq/schemas";
249
268
  import { resolveThemeForDoc } from "@bendyline/squisq/doc";
250
269
  function markdownDocToPlainHtml(doc, options = {}) {
251
- const { title = "Document", images, links, themeId, iconsCss, htmlPolicy = "sanitize" } = options;
270
+ const {
271
+ title = "Document",
272
+ images,
273
+ links,
274
+ themeId,
275
+ iconsCss,
276
+ htmlPolicy = "sanitize",
277
+ externalResources = "deny"
278
+ } = options;
252
279
  const resolveId = themeId ?? readFrontmatterThemeId2(doc.frontmatter);
253
280
  const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId, options.themeRegistry) : void 0);
254
281
  const ctx = { images, links, htmlPolicy };
255
282
  const body = renderTopLevel(doc.children, ctx);
256
- const fontsLink = theme ? renderFontsLink(theme) : "";
283
+ const fontsLink = theme && externalResources === "allow" ? renderFontsLink(theme) : "";
257
284
  const usesIcons = docUsesIcons(doc);
258
285
  let iconsLink = "";
259
286
  if (usesIcons) {
260
287
  iconsLink = iconsCss ? `<style data-fa-inline>
261
- ${iconsCss}
288
+ ${escapeStyleText(iconsCss)}
262
289
  </style>
263
- ` : FONT_AWESOME_LINK;
290
+ ` : externalResources === "allow" ? FONT_AWESOME_LINK : "";
264
291
  }
265
292
  const themedCss = theme ? renderThemedCss(theme) : DEFAULT_CSS;
293
+ const safeCss = escapeStyleText(`${themedCss}
294
+ ${FEATURE_CSS}`);
266
295
  return `<!DOCTYPE html>
267
296
  <html lang="en">
268
297
  <head>
@@ -270,8 +299,7 @@ ${iconsCss}
270
299
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
271
300
  <title>${escapeHtml2(title)}</title>
272
301
  ${fontsLink}${iconsLink}<style>
273
- ${themedCss}
274
- ${FEATURE_CSS}
302
+ ${safeCss}
275
303
  </style>
276
304
  </head>
277
305
  <body>
@@ -279,7 +307,10 @@ ${body}
279
307
  </body>
280
308
  </html>`;
281
309
  }
282
- var FONT_AWESOME_LINK = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
310
+ function escapeStyleText(css) {
311
+ return css.replace(/</g, "\\3c ");
312
+ }
313
+ var FONT_AWESOME_LINK = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.2.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
283
314
  `;
284
315
  function docUsesIcons(doc) {
285
316
  function visit(node) {
@@ -1379,6 +1410,14 @@ function toHtmlString(data) {
1379
1410
  return new TextDecoder("utf-8").decode(bytes);
1380
1411
  }
1381
1412
  function htmlToMarkdownDocSync(html, options = {}) {
1413
+ options.signal?.throwIfAborted();
1414
+ const maxInputChars = options.maxInputChars ?? 16 * 1024 * 1024;
1415
+ if (!Number.isSafeInteger(maxInputChars) || maxInputChars < 0) {
1416
+ throw new RangeError("maxInputChars must be a non-negative safe integer");
1417
+ }
1418
+ if (html.length > maxInputChars) {
1419
+ throw new RangeError(`HTML exceeds the ${maxInputChars}-character safety limit`);
1420
+ }
1382
1421
  let nodes = parseHtmlToNodes(html);
1383
1422
  if (options.sanitize !== false) nodes = sanitizeHtmlNodes2(nodes);
1384
1423
  return { type: "document", children: blocksFromNodes(nodes) };
@@ -1387,7 +1426,7 @@ async function htmlToMarkdownDoc(data, options = {}) {
1387
1426
  return htmlToMarkdownDocSync(toHtmlString(data), options);
1388
1427
  }
1389
1428
  function htmlToMarkdown(html, options = {}) {
1390
- return stringifyMarkdown(htmlToMarkdownDocSync(html, options));
1429
+ return stringifyMarkdown(htmlToMarkdownDocSync(html, options), { signal: options.signal });
1391
1430
  }
1392
1431
 
1393
1432
  // src/html/index.ts
@@ -1397,10 +1436,21 @@ function sanitizeZipPath3(path) {
1397
1436
  if (normalized.split("/").some((seg) => seg === "..")) return null;
1398
1437
  return normalized;
1399
1438
  }
1439
+ function uniqueFilename(filename, used) {
1440
+ if (!used.has(filename)) return filename;
1441
+ const dot = filename.lastIndexOf(".");
1442
+ const base = dot > 0 ? filename.slice(0, dot) : filename;
1443
+ const ext = dot > 0 ? filename.slice(dot) : "";
1444
+ let suffix = 2;
1445
+ while (used.has(`${base}-${suffix}${ext}`)) suffix++;
1446
+ return `${base}-${suffix}${ext}`;
1447
+ }
1400
1448
  function docToHtml(doc, options) {
1449
+ options.signal?.throwIfAborted();
1401
1450
  return generateInlineHtml(doc, options);
1402
1451
  }
1403
1452
  async function docToHtmlZip(doc, options) {
1453
+ options.signal?.throwIfAborted();
1404
1454
  const {
1405
1455
  playerScript,
1406
1456
  images,
@@ -1415,18 +1465,30 @@ async function docToHtmlZip(doc, options) {
1415
1465
  zip.file("squisq-player.js", playerScript);
1416
1466
  const imagePathMap = {};
1417
1467
  if (images) {
1468
+ let imageIndex = 0;
1418
1469
  for (const [originalPath, buffer] of images.entries()) {
1470
+ if ((imageIndex++ & 63) === 0) options.signal?.throwIfAborted();
1419
1471
  const zipPath = sanitizeZipPath3(originalPath);
1420
- if (!zipPath) continue;
1472
+ if (!zipPath) {
1473
+ console.warn(
1474
+ `[squisq] docToHtmlZip: skipped image with unsafe path "${originalPath}" \u2014 the exported document will have a broken reference to it.`
1475
+ );
1476
+ continue;
1477
+ }
1421
1478
  zip.file(zipPath, buffer);
1422
1479
  imagePathMap[originalPath] = zipPath;
1423
1480
  }
1424
1481
  }
1425
1482
  const audioPathMap = {};
1426
1483
  if (audio) {
1484
+ let audioIndex = 0;
1485
+ const usedAudioNames = /* @__PURE__ */ new Set();
1427
1486
  for (const [segmentKey, buffer] of audio.entries()) {
1487
+ if ((audioIndex++ & 63) === 0) options.signal?.throwIfAborted();
1428
1488
  const filename = extractFilename(segmentKey);
1429
- const finalName = filename.includes(".") ? filename : `${filename}.mp3`;
1489
+ const named = filename.includes(".") ? filename : `${filename}.mp3`;
1490
+ const finalName = uniqueFilename(named, usedAudioNames);
1491
+ usedAudioNames.add(finalName);
1430
1492
  const zipPath = `audio/${finalName}`;
1431
1493
  zip.file(zipPath, buffer);
1432
1494
  audioPathMap[segmentKey] = zipPath;
@@ -1462,4 +1524,3 @@ export {
1462
1524
  docToHtml,
1463
1525
  docToHtmlZip
1464
1526
  };
1465
- //# sourceMappingURL=chunk-DTDF6QDP.js.map
@@ -40,4 +40,3 @@ export {
40
40
  xmlElement,
41
41
  textElement
42
42
  };
43
- //# sourceMappingURL=chunk-U4MRIFKL.js.map