@bendyline/squisq-formats 2.8.16 → 2.8.18

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 (33) hide show
  1. package/dist/{chunk-MF4ZQQJ7.js → chunk-42ETCBAK.js} +14 -5
  2. package/dist/{chunk-5HUHSLGA.js → chunk-6OB2DSGW.js} +1 -1
  3. package/dist/{chunk-ATWVNF7D.js → chunk-6QVOHRRL.js} +1 -1
  4. package/dist/{chunk-YRFBWYAD.js → chunk-AQZ2CAET.js} +1 -1
  5. package/dist/{chunk-S5PCVMKU.js → chunk-BB3BLYLM.js} +8 -0
  6. package/dist/{chunk-OK622ILP.js → chunk-BGVT5I55.js} +1 -1
  7. package/dist/{chunk-WATAOG4Y.js → chunk-FD7IDL5V.js} +2 -2
  8. package/dist/{chunk-3KXEE7KH.js → chunk-GJWBVR4X.js} +3 -3
  9. package/dist/{chunk-KZUP6TIV.js → chunk-HAGYYPFR.js} +2 -2
  10. package/dist/{chunk-4SPMLGOG.js → chunk-J6LR22RZ.js} +9 -1
  11. package/dist/{chunk-4A3EHQJP.js → chunk-KWZDYOCB.js} +3 -3
  12. package/dist/{chunk-LSUMEIQ7.js → chunk-O4UYRQX7.js} +4 -4
  13. package/dist/{chunk-SWLDHEZI.js → chunk-ONPR2LAD.js} +561 -68
  14. package/dist/{chunk-6N2J7C2B.js → chunk-RBKUII3V.js} +1 -1
  15. package/dist/{chunk-ILCJ3WFD.js → chunk-SUWCBXZH.js} +1 -1
  16. package/dist/{chunk-ZJERBWTB.js → chunk-WPLCWAN3.js} +10 -4
  17. package/dist/data/index.js +4 -4
  18. package/dist/docx/index.js +3 -3
  19. package/dist/epub/index.js +1 -1
  20. package/dist/{extract-UTAOG7LV.js → extract-27YYYYBZ.js} +3 -3
  21. package/dist/html/index.js +1 -1
  22. package/dist/html/prepare.js +1 -1
  23. package/dist/index.js +14 -14
  24. package/dist/infer/index.js +4 -4
  25. package/dist/{layouts-2A7QNF6Y.js → layouts-5YNFCOMS.js} +2 -2
  26. package/dist/{materialize-ICXNO4S4.js → materialize-2MS3SXA6.js} +4 -4
  27. package/dist/ooxml/index.js +3 -3
  28. package/dist/outside-in/index.js +2 -2
  29. package/dist/pptx/index.js +4 -4
  30. package/dist/{readers-3P5A6A2A.js → readers-TAWBODNF.js} +3 -3
  31. package/dist/registry/index.js +1 -1
  32. package/dist/xlsx/index.js +4 -4
  33. package/package.json +2 -2
@@ -19,13 +19,15 @@ import {
19
19
  } from "./chunk-BZ5SHMOY.js";
20
20
  import {
21
21
  createPackage
22
- } from "./chunk-ILCJ3WFD.js";
22
+ } from "./chunk-SUWCBXZH.js";
23
23
  import {
24
24
  escapeXml,
25
25
  xmlDeclaration
26
26
  } from "./chunk-JU2RHXUB.js";
27
27
  import {
28
28
  CONTENT_TYPE_PPTX_FONT,
29
+ CONTENT_TYPE_PPTX_NOTES_MASTER,
30
+ CONTENT_TYPE_PPTX_NOTES_SLIDE,
29
31
  CONTENT_TYPE_PPTX_PRESENTATION,
30
32
  CONTENT_TYPE_PPTX_SLIDE,
31
33
  CONTENT_TYPE_PPTX_SLIDE_LAYOUT,
@@ -39,6 +41,8 @@ import {
39
41
  REL_FONT,
40
42
  REL_HYPERLINK,
41
43
  REL_IMAGE,
44
+ REL_NOTES_MASTER,
45
+ REL_NOTES_SLIDE,
42
46
  REL_OFFICE_DOCUMENT,
43
47
  REL_SLIDE,
44
48
  REL_SLIDE_LAYOUT,
@@ -52,7 +56,7 @@ import {
52
56
  openPackage,
53
57
  requireMainPartPath,
54
58
  resolveTarget
55
- } from "./chunk-S5PCVMKU.js";
59
+ } from "./chunk-BB3BLYLM.js";
56
60
  import {
57
61
  buildContainer
58
62
  } from "./chunk-IIQYS2YH.js";
@@ -79,7 +83,8 @@ import {
79
83
  isGeometryBoundTemplate,
80
84
  isTemplateBlock,
81
85
  resolvePersistentLayers,
82
- resolveThemeForDoc
86
+ resolveThemeForDoc,
87
+ shapePath
83
88
  } from "@bendyline/squisq/doc";
84
89
  import { splitIconMarkers as splitIconMarkers3 } from "@bendyline/squisq/icon-marker";
85
90
  import {
@@ -88,13 +93,105 @@ import {
88
93
  readFrontmatterThemeId
89
94
  } from "@bendyline/squisq/markdown";
90
95
 
96
+ // src/pptx/speakerNotes.ts
97
+ function childNodes(node) {
98
+ return Array.isArray(node.children) ? node.children : [];
99
+ }
100
+ function collectFootnoteDefinitions(nodes, into = /* @__PURE__ */ new Map()) {
101
+ const visit = (node) => {
102
+ if (node.type === "footnoteDefinition") {
103
+ const def = node;
104
+ if (!into.has(def.identifier)) into.set(def.identifier, definitionText(def));
105
+ return;
106
+ }
107
+ for (const child of childNodes(node)) visit(child);
108
+ };
109
+ for (const node of nodes) visit(node);
110
+ return into;
111
+ }
112
+ function definitionText(def) {
113
+ const parts = [];
114
+ const visit = (node) => {
115
+ const kids = childNodes(node);
116
+ if (kids.length > 0 && kids.every((kid) => !isBlockLike(kid))) {
117
+ parts.push(extractPlainText(kids));
118
+ return;
119
+ }
120
+ for (const kid of kids) visit(kid);
121
+ };
122
+ for (const child of def.children) visit(child);
123
+ return parts.map((part) => part.replace(/\s+/g, " ").trim()).filter(Boolean).join(" ");
124
+ }
125
+ var BLOCK_TYPES = /* @__PURE__ */ new Set([
126
+ "paragraph",
127
+ "heading",
128
+ "list",
129
+ "listItem",
130
+ "blockquote",
131
+ "table",
132
+ "tableRow",
133
+ "tableCell",
134
+ "code"
135
+ ]);
136
+ function isBlockLike(node) {
137
+ return BLOCK_TYPES.has(node.type);
138
+ }
139
+ function footnoteIdentifiersIn(nodes) {
140
+ const seen = [];
141
+ const visit = (node) => {
142
+ if (node.type === "footnoteDefinition") return;
143
+ if (node.type === "footnoteReference") {
144
+ const id = String(node.identifier ?? "");
145
+ if (id && !seen.includes(id)) seen.push(id);
146
+ return;
147
+ }
148
+ for (const child of childNodes(node)) visit(child);
149
+ };
150
+ for (const node of nodes) visit(node);
151
+ return seen;
152
+ }
153
+ function withoutFootnoteDefinitions(nodes) {
154
+ return nodes.filter((node) => node.type !== "footnoteDefinition");
155
+ }
156
+ function speakerNoteLines(identifiers, definitions) {
157
+ const lines = [];
158
+ for (const id of identifiers) {
159
+ const text = definitions.get(id);
160
+ if (text) lines.push(`[${lines.length + 1}] ${text}`);
161
+ }
162
+ return lines;
163
+ }
164
+ function buildNotesSlideXml(lines) {
165
+ const paragraphs = lines.map(
166
+ (line) => `<a:p><a:r><a:rPr lang="en-US" dirty="0"/><a:t>${escapeXml(line)}</a:t></a:r></a:p>`
167
+ ).join("");
168
+ return xmlDeclaration() + `<p:notes xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/><p:sp><p:nvSpPr><p:cNvPr id="2" name="Slide Image Placeholder 1"/><p:cNvSpPr><a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldImg"/></p:nvPr></p:nvSpPr><p:spPr/></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Notes Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="body" idx="1"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/>${paragraphs}</p:txBody></p:sp></p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:notes>`;
169
+ }
170
+ function buildNotesMasterXml() {
171
+ const level = (n) => `<a:lvl${n}pPr marL="${(n - 1) * 457200}" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl${n}pPr>`;
172
+ return xmlDeclaration() + `<p:notesMaster xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr><p:sp><p:nvSpPr><p:cNvPr id="2" name="Slide Image Placeholder 1"/><p:cNvSpPr><a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldImg" idx="2"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="685800" y="1143000"/><a:ext cx="5486400" cy="3086100"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln w="12700"><a:solidFill><a:prstClr val="black"/></a:solidFill></a:ln></p:spPr></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Notes Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="body" sz="quarter" idx="3"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="685800" y="4400550"/><a:ext cx="5486400" cy="3600450"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/><a:lstStyle/><a:p><a:pPr lvl="0"/><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp></p:spTree></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/><p:notesStyle>${[1, 2, 3, 4, 5, 6, 7, 8, 9].map(level).join("")}</p:notesStyle></p:notesMaster>`;
173
+ }
174
+
91
175
  // src/pptx/pictureLayers.ts
92
176
  import { svgSourceFromBytes } from "@bendyline/squisq/markdown";
93
- function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId, svgRelId) {
177
+ function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId, svgRelId, srcRect = "") {
94
178
  const name = escapeXml(img.alt || "Picture");
95
179
  const descr = img.alt ? ` descr="${escapeXml(img.alt)}"` : "";
96
180
  const link = hyperlinkRelId ? `<a:hlinkClick r:id="${hyperlinkRelId}"/>` : "";
97
- return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill>` + blipXml(img.relId, svgRelId) + `<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
181
+ return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill>` + blipXml(img.relId, svgRelId) + srcRect + `<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
182
+ }
183
+ function fitPicture(box, size, fit) {
184
+ if (!size || size.width <= 0 || size.height <= 0 || fit === "fill" || box.height <= 0)
185
+ return { rect: box, srcRect: "" };
186
+ if (fit !== "cover") return { rect: fitCentered(box, size.width, size.height), srcRect: "" };
187
+ const image = size.width / size.height;
188
+ const frame = box.width / box.height;
189
+ if (Math.abs(image - frame) < 1e-3) return { rect: box, srcRect: "" };
190
+ const edge = Math.round((image > frame ? 1 - frame / image : 1 - image / frame) / 2 * 1e5);
191
+ return {
192
+ rect: box,
193
+ srcRect: image > frame ? `<a:srcRect l="${edge}" r="${edge}"/>` : `<a:srcRect t="${edge}" b="${edge}"/>`
194
+ };
98
195
  }
99
196
  function fitCentered(area, width, height) {
100
197
  const scale = Math.min(area.width / width, area.height / height);
@@ -449,9 +546,30 @@ function cssGradientToString(gradient) {
449
546
  // src/pptx/drawingXml.ts
450
547
  function officeTypeface(family, fallback) {
451
548
  const resolved = resolveFontFamily(family, fallback);
452
- const first = resolved.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "");
453
- return first || fallback;
454
- }
549
+ const names = resolved.split(",").map((name) => name.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
550
+ const named = names.find((name) => !GENERIC_FONT_FAMILIES.has(name.toLowerCase()));
551
+ if (named) return named;
552
+ const kind = names.map((name) => name.toLowerCase());
553
+ if (kind.includes("monospace") || kind.includes("ui-monospace")) return "Consolas";
554
+ if (kind.includes("serif") || kind.includes("ui-serif")) return "Georgia";
555
+ return names.length ? "Segoe UI" : fallback;
556
+ }
557
+ var GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
558
+ "system-ui",
559
+ "-apple-system",
560
+ "blinkmacsystemfont",
561
+ "ui-sans-serif",
562
+ "ui-serif",
563
+ "ui-monospace",
564
+ "ui-rounded",
565
+ "sans-serif",
566
+ "serif",
567
+ "monospace",
568
+ "cursive",
569
+ "fantasy",
570
+ "emoji",
571
+ "math"
572
+ ]);
455
573
  function solidColorXml(color) {
456
574
  const alpha = color.alpha < 1e5 ? `<a:alpha val="${color.alpha}"/>` : "";
457
575
  return `<a:solidFill><a:srgbClr val="${color.hex}">${alpha}</a:srgbClr></a:solidFill>`;
@@ -507,13 +625,17 @@ function patternFillXml(pattern, ground) {
507
625
  function buildLayerShape(layer, shapeId) {
508
626
  const rect = toEmuRect(resolveLayerRect(layer.position, 100, 100));
509
627
  const geometry = layer.content.shape === "circle" ? "ellipse" : layer.content.shape === "line" ? "line" : layer.content.borderRadius ? "roundRect" : "rect";
628
+ const box = resolveLayerRect(layer.position, 100, 100);
629
+ const shorter = Math.min(box.width, box.height);
630
+ const radius = Math.min(shorter / 2, Math.max(0, layer.content.borderRadius ?? 0));
631
+ const adjust = geometry === "roundRect" && shorter > 0 ? `<a:avLst><a:gd name="adj" fmla="val ${Math.round(radius / shorter * 1e5)}"/></a:avLst>` : "<a:avLst/>";
510
632
  const fill = layer.content.shape === "line" ? "<a:noFill/>" : layer.content.pattern ? patternFillXml(layer.content.pattern) : fillXml(layer.content.fill, layer.content.fillOpacity, layer.content.gradient);
511
633
  const stroke = lineXml(
512
634
  layer.content.stroke ?? (layer.content.shape === "line" ? "#ffffff" : void 0),
513
635
  layer.content.strokeWidth ?? (layer.content.shape === "line" ? 2 : void 0),
514
636
  layer.content.borderStyle
515
637
  );
516
- return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="${geometry}"><a:avLst/></a:prstGeom>` + fill + stroke + `</p:spPr></p:sp>`;
638
+ return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="${geometry}">${adjust}</a:prstGeom>` + fill + stroke + `</p:spPr></p:sp>`;
517
639
  }
518
640
 
519
641
  // src/pptx/backdrop.ts
@@ -752,6 +874,235 @@ function planDeckBackdrops(blocks) {
752
874
  return { master: { base: masterBase, overlays: masterOverlays }, layouts, slides };
753
875
  }
754
876
 
877
+ // src/pptx/pathGeometry.ts
878
+ var ARITY = {
879
+ M: 2,
880
+ L: 2,
881
+ H: 1,
882
+ V: 1,
883
+ C: 6,
884
+ S: 4,
885
+ Q: 4,
886
+ T: 2,
887
+ A: 7,
888
+ Z: 0
889
+ };
890
+ function arcToCubics(x0, y0, rx, ry, rotation, largeArc, sweep, x, y) {
891
+ if (x0 === x && y0 === y || !Number.isFinite(rx + ry)) return [];
892
+ rx = Math.abs(rx);
893
+ ry = Math.abs(ry);
894
+ if (rx === 0 || ry === 0) return [{ op: "L", x, y }];
895
+ const phi = rotation * Math.PI / 180;
896
+ const [cos, sin] = [Math.cos(phi), Math.sin(phi)];
897
+ const dx = (x0 - x) / 2;
898
+ const dy = (y0 - y) / 2;
899
+ const x1 = cos * dx + sin * dy;
900
+ const y1 = -sin * dx + cos * dy;
901
+ const lambda = x1 * x1 / (rx * rx) + y1 * y1 / (ry * ry);
902
+ if (lambda > 1) {
903
+ rx *= Math.sqrt(lambda);
904
+ ry *= Math.sqrt(lambda);
905
+ }
906
+ const numerator = rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1;
907
+ const denominator = rx * rx * y1 * y1 + ry * ry * x1 * x1;
908
+ const factor = (largeArc === sweep ? -1 : 1) * Math.sqrt(Math.max(0, numerator / (denominator || 1)));
909
+ const cxp = factor * rx * y1 / ry;
910
+ const cyp = -factor * ry * x1 / rx;
911
+ const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
912
+ const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
913
+ const angle = (ux, uy, vx, vy) => {
914
+ const turn = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
915
+ return turn;
916
+ };
917
+ const start = angle(1, 0, (x1 - cxp) / rx, (y1 - cyp) / ry);
918
+ let delta = angle((x1 - cxp) / rx, (y1 - cyp) / ry, (-x1 - cxp) / rx, (-y1 - cyp) / ry);
919
+ if (!sweep && delta > 0) delta -= 2 * Math.PI;
920
+ if (sweep && delta < 0) delta += 2 * Math.PI;
921
+ const pieces = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
922
+ const step = delta / pieces;
923
+ const handle = 4 / 3 * Math.tan(step / 4);
924
+ const point = (theta) => [
925
+ cx + rx * Math.cos(theta) * cos - ry * Math.sin(theta) * sin,
926
+ cy + rx * Math.cos(theta) * sin + ry * Math.sin(theta) * cos
927
+ ];
928
+ const tangent = (theta) => [
929
+ -rx * Math.sin(theta) * cos - ry * Math.cos(theta) * sin,
930
+ -rx * Math.sin(theta) * sin + ry * Math.cos(theta) * cos
931
+ ];
932
+ const segments = [];
933
+ for (let i = 0; i < pieces; i++) {
934
+ const from = start + step * i;
935
+ const to = from + step;
936
+ const [px, py] = point(from);
937
+ const [qx, qy] = point(to);
938
+ const [tx0, ty0] = tangent(from);
939
+ const [tx1, ty1] = tangent(to);
940
+ segments.push({
941
+ op: "C",
942
+ x1: px + handle * tx0,
943
+ y1: py + handle * ty0,
944
+ x2: qx - handle * tx1,
945
+ y2: qy - handle * ty1,
946
+ // The last piece ends exactly on the authored endpoint.
947
+ x: i === pieces - 1 ? x : qx,
948
+ y: i === pieces - 1 ? y : qy
949
+ });
950
+ }
951
+ return segments;
952
+ }
953
+ function parseSvgPath(d) {
954
+ const tokens = d.match(/[a-zA-Z]|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/gu);
955
+ if (!tokens || !/^[Mm]$/u.test(tokens[0])) return null;
956
+ const segments = [];
957
+ let [cx, cy] = [0, 0];
958
+ let [startX, startY] = [0, 0];
959
+ let control;
960
+ let index = 0;
961
+ while (index < tokens.length) {
962
+ const command = tokens[index++];
963
+ const upper = command.toUpperCase();
964
+ const arity = ARITY[upper];
965
+ if (arity === void 0 || !/^[a-zA-Z]$/u.test(command)) return null;
966
+ const relative = command !== upper;
967
+ if (upper === "Z") {
968
+ segments.push({ op: "Z" });
969
+ [cx, cy] = [startX, startY];
970
+ control = void 0;
971
+ continue;
972
+ }
973
+ let first = true;
974
+ while (first || index < tokens.length && !/^[a-zA-Z]$/u.test(tokens[index])) {
975
+ const numbers = tokens.slice(index, index + arity).map(Number);
976
+ if (numbers.length < arity || numbers.some((value) => !Number.isFinite(value))) return null;
977
+ index += arity;
978
+ const [ox, oy] = relative ? [cx, cy] : [0, 0];
979
+ let next;
980
+ switch (upper) {
981
+ case "M": {
982
+ [cx, cy] = [ox + numbers[0], oy + numbers[1]];
983
+ if (first) {
984
+ segments.push({ op: "M", x: cx, y: cy });
985
+ [startX, startY] = [cx, cy];
986
+ } else segments.push({ op: "L", x: cx, y: cy });
987
+ break;
988
+ }
989
+ case "L":
990
+ [cx, cy] = [ox + numbers[0], oy + numbers[1]];
991
+ segments.push({ op: "L", x: cx, y: cy });
992
+ break;
993
+ case "H":
994
+ cx = ox + numbers[0];
995
+ segments.push({ op: "L", x: cx, y: cy });
996
+ break;
997
+ case "V":
998
+ cy = oy + numbers[0];
999
+ segments.push({ op: "L", x: cx, y: cy });
1000
+ break;
1001
+ case "C":
1002
+ case "S": {
1003
+ const smooth = upper === "S";
1004
+ const x1 = smooth ? control?.kind === "C" ? 2 * cx - control.x : cx : ox + numbers[0];
1005
+ const y1 = smooth ? control?.kind === "C" ? 2 * cy - control.y : cy : oy + numbers[1];
1006
+ const rest = numbers.slice(smooth ? 0 : 2);
1007
+ const [x2, y2, x, y] = [ox + rest[0], oy + rest[1], ox + rest[2], oy + rest[3]];
1008
+ segments.push({ op: "C", x1, y1, x2, y2, x, y });
1009
+ next = { kind: "C", x: x2, y: y2 };
1010
+ [cx, cy] = [x, y];
1011
+ break;
1012
+ }
1013
+ case "Q":
1014
+ case "T": {
1015
+ const smooth = upper === "T";
1016
+ const x1 = smooth ? control?.kind === "Q" ? 2 * cx - control.x : cx : ox + numbers[0];
1017
+ const y1 = smooth ? control?.kind === "Q" ? 2 * cy - control.y : cy : oy + numbers[1];
1018
+ const rest = numbers.slice(smooth ? 0 : 2);
1019
+ const [x, y] = [ox + rest[0], oy + rest[1]];
1020
+ segments.push({ op: "Q", x1, y1, x, y });
1021
+ next = { kind: "Q", x: x1, y: y1 };
1022
+ [cx, cy] = [x, y];
1023
+ break;
1024
+ }
1025
+ case "A": {
1026
+ const [x, y] = [ox + numbers[5], oy + numbers[6]];
1027
+ segments.push(
1028
+ ...arcToCubics(
1029
+ cx,
1030
+ cy,
1031
+ numbers[0],
1032
+ numbers[1],
1033
+ numbers[2],
1034
+ numbers[3] !== 0,
1035
+ numbers[4] !== 0,
1036
+ x,
1037
+ y
1038
+ )
1039
+ );
1040
+ [cx, cy] = [x, y];
1041
+ break;
1042
+ }
1043
+ }
1044
+ control = next;
1045
+ first = false;
1046
+ }
1047
+ }
1048
+ return segments;
1049
+ }
1050
+ function pathBounds(segments) {
1051
+ const xs = [];
1052
+ const ys = [];
1053
+ for (const segment of segments) {
1054
+ if (segment.op === "Z") continue;
1055
+ xs.push(segment.x);
1056
+ ys.push(segment.y);
1057
+ if (segment.op === "C" || segment.op === "Q") {
1058
+ xs.push(segment.x1);
1059
+ ys.push(segment.y1);
1060
+ }
1061
+ if (segment.op === "C") {
1062
+ xs.push(segment.x2);
1063
+ ys.push(segment.y2);
1064
+ }
1065
+ }
1066
+ if (!xs.length) return null;
1067
+ const [x, y] = [Math.min(...xs), Math.min(...ys)];
1068
+ return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
1069
+ }
1070
+ function isStraightLine(segments) {
1071
+ const points = segments.filter((segment) => segment.op !== "Z");
1072
+ if (points.length < 2 || points.length !== segments.length) return false;
1073
+ if (points.some((segment) => segment.op === "C" || segment.op === "Q")) return false;
1074
+ const first = points[0];
1075
+ const last = points[points.length - 1];
1076
+ const [dx, dy] = [last.x - first.x, last.y - first.y];
1077
+ const length = Math.hypot(dx, dy);
1078
+ if (length === 0) return false;
1079
+ return points.every((segment) => {
1080
+ const point = segment;
1081
+ return Math.abs((point.x - first.x) * dy - (point.y - first.y) * dx) / length < 0.5;
1082
+ });
1083
+ }
1084
+ function customGeometryXml(segments, bounds, scale, filled) {
1085
+ const unit = (value, origin) => Math.round((value - origin) * scale);
1086
+ const pt = (x, y) => `<a:pt x="${unit(x, bounds.x)}" y="${unit(y, bounds.y)}"/>`;
1087
+ const body = segments.map((segment) => {
1088
+ switch (segment.op) {
1089
+ case "M":
1090
+ return `<a:moveTo>${pt(segment.x, segment.y)}</a:moveTo>`;
1091
+ case "L":
1092
+ return `<a:lnTo>${pt(segment.x, segment.y)}</a:lnTo>`;
1093
+ case "C":
1094
+ return `<a:cubicBezTo>${pt(segment.x1, segment.y1)}${pt(segment.x2, segment.y2)}${pt(segment.x, segment.y)}</a:cubicBezTo>`;
1095
+ case "Q":
1096
+ return `<a:quadBezTo>${pt(segment.x1, segment.y1)}${pt(segment.x, segment.y)}</a:quadBezTo>`;
1097
+ case "Z":
1098
+ return "<a:close/>";
1099
+ }
1100
+ }).join("");
1101
+ const width = Math.max(1, Math.round(bounds.width * scale));
1102
+ const height = Math.max(1, Math.round(bounds.height * scale));
1103
+ return `<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="0" t="0" r="r" b="b"/><a:pathLst><a:path w="${width}" h="${height}"${filled ? "" : ' fill="none"'}>${body}</a:path></a:pathLst></a:custGeom>`;
1104
+ }
1105
+
755
1106
  // src/pptx/layerText.ts
756
1107
  import { splitIconMarkers as splitIconMarkers2 } from "@bendyline/squisq/icon-marker";
757
1108
 
@@ -992,6 +1343,9 @@ function estimateSlideTextScale(layers) {
992
1343
  }
993
1344
 
994
1345
  // src/pptx/textLists.ts
1346
+ import {
1347
+ taskMarker
1348
+ } from "@bendyline/squisq/schemas";
995
1349
  function mergeTextFlows(layers, textScale = 1) {
996
1350
  const flows = /* @__PURE__ */ new Map();
997
1351
  for (const layer of layers) {
@@ -1060,6 +1414,10 @@ function planListParagraphs(paragraphs) {
1060
1414
  }
1061
1415
  const level = Math.min(8, Math.max(0, Math.floor(list.level)));
1062
1416
  runs.length = Math.min(runs.length, level + 1);
1417
+ if (list.checked !== void 0) {
1418
+ runs[level] = void 0;
1419
+ return { ordered: false, level, checked: list.checked };
1420
+ }
1063
1421
  if (!list.ordered) {
1064
1422
  runs[level] = void 0;
1065
1423
  return { ordered: false, level };
@@ -1076,7 +1434,7 @@ function listParagraphXml(plan, indentPx) {
1076
1434
  const startAt = plan.startAt && plan.startAt !== 1 ? ` startAt="${plan.startAt}"` : "";
1077
1435
  return {
1078
1436
  attributes: ` marL="${hang * (plan.level + 1)}" lvl="${plan.level}" indent="${-hang}"`,
1079
- bullet: plan.ordered ? `<a:buAutoNum type="arabicPeriod"${startAt}/>` : `<a:buChar char="\u2022"/>`
1437
+ bullet: plan.ordered ? `<a:buAutoNum type="arabicPeriod"${startAt}/>` : plan.checked !== void 0 ? `<a:buChar char="${taskMarker(plan.checked)}"/>` : `<a:buChar char="\u2022"/>`
1080
1438
  };
1081
1439
  }
1082
1440
 
@@ -1131,7 +1489,8 @@ function buildLayerTextShape(layer, shapeId, maxBottom, listIndentPx, textScale
1131
1489
  const rect = toEmuRect(virtualRect);
1132
1490
  const align = style.textAlign === "center" ? "ctr" : style.textAlign === "right" ? "r" : "l";
1133
1491
  const inset = Math.max(0, Math.round(padding * EMU_PER_CANVAS_PX));
1134
- const bodyPr = `<a:bodyPr wrap="square" anchor="${vertical}" vertOverflow="clip" horzOverflow="clip" lIns="${inset}" rIns="${inset}" tIns="${inset}" bIns="${inset}"><a:noAutofit/></a:bodyPr>`;
1492
+ const unbreakable = !visibleBox && sourceText.trim() !== "" && sourceText.split("\n").every((line) => !/\s/u.test(line.trim()));
1493
+ const bodyPr = `<a:bodyPr wrap="${unbreakable ? "none" : "square"}" anchor="${vertical}" vertOverflow="clip" horzOverflow="${unbreakable ? "overflow" : "clip"}" lIns="${inset}" rIns="${inset}" tIns="${inset}" bIns="${inset}"><a:noAutofit/></a:bodyPr>`;
1135
1494
  const paragraphs = layer.content.paragraphs || blockGaps ? buildStructuredParagraphs(paragraphSource, renderedStyle, align, {
1136
1495
  // A list with no marker column of its own hangs its bullets by the
1137
1496
  // player's list indent.
@@ -1272,7 +1631,7 @@ function buildDeckThemeXml(theme) {
1272
1631
  }
1273
1632
 
1274
1633
  // src/pptx/templates.ts
1275
- function buildPresentationXml(slideCount, slideRelIds, slideMasterRelId, _themeRelId, embeddedFonts = []) {
1634
+ function buildPresentationXml(slideCount, slideRelIds, slideMasterRelId, _themeRelId, embeddedFonts = [], notesMasterRelId) {
1276
1635
  const sldIdList = [];
1277
1636
  for (let i = 0; i < slideCount; i++) {
1278
1637
  sldIdList.push(`<p:sldId id="${256 + i}" r:id="${slideRelIds[i]}"/>`);
@@ -1280,7 +1639,7 @@ function buildPresentationXml(slideCount, slideRelIds, slideMasterRelId, _themeR
1280
1639
  const embeddedFontList = embeddedFonts.length > 0 ? `<p:embeddedFontLst>${embeddedFonts.map(
1281
1640
  ({ typeface, relId }) => `<p:embeddedFont><p:font typeface="${escapeXml(typeface)}" charset="0" pitchFamily="34"/><p:regular r:id="${escapeXml(relId)}"/></p:embeddedFont>`
1282
1641
  ).join("")}</p:embeddedFontLst>` : "";
1283
- return xmlDeclaration() + `<p:presentation xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}" saveSubsetFonts="1"><p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="${slideMasterRelId}"/></p:sldMasterIdLst><p:sldIdLst>${sldIdList.join("")}</p:sldIdLst><p:sldSz cx="${SLIDE_WIDTH}" cy="${SLIDE_HEIGHT}" type="screen16x9"/><p:notesSz cx="${SLIDE_HEIGHT}" cy="${SLIDE_WIDTH}"/>` + embeddedFontList + `</p:presentation>`;
1642
+ return xmlDeclaration() + `<p:presentation xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}" saveSubsetFonts="1"><p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="${slideMasterRelId}"/></p:sldMasterIdLst>` + (notesMasterRelId ? `<p:notesMasterIdLst><p:notesMasterId r:id="${notesMasterRelId}"/></p:notesMasterIdLst>` : "") + `<p:sldIdLst>${sldIdList.join("")}</p:sldIdLst><p:sldSz cx="${SLIDE_WIDTH}" cy="${SLIDE_HEIGHT}" type="screen16x9"/><p:notesSz cx="${SLIDE_HEIGHT}" cy="${SLIDE_WIDTH}"/>` + embeddedFontList + `</p:presentation>`;
1284
1643
  }
1285
1644
  function buildSlideMasterXml(layoutRelId) {
1286
1645
  return xmlDeclaration() + `<p:sldMaster xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/><p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="${layoutRelId}"/></p:sldLayoutIdLst></p:sldMaster>`;
@@ -1316,7 +1675,14 @@ async function markdownDocToPptx(doc, options = {}) {
1316
1675
  slideXmls.push(xml);
1317
1676
  slideContexts.push(ctx);
1318
1677
  }
1319
- return buildPptxPackage(slideXmls, slideContexts, options);
1678
+ const definitions = collectFootnoteDefinitions(doc.children);
1679
+ const slideNotes = slides.map(
1680
+ (slide) => speakerNoteLines(
1681
+ footnoteIdentifiersIn([...slide.title ?? [], ...slide.bodyNodes]),
1682
+ definitions
1683
+ )
1684
+ );
1685
+ return buildPptxPackage(slideXmls, slideContexts, options, void 0, slideNotes);
1320
1686
  }
1321
1687
  function needsSlideshowProjection(doc) {
1322
1688
  return (doc.blocks ?? []).some((block) => !!block.sourceHeading);
@@ -1324,7 +1690,9 @@ function needsSlideshowProjection(doc) {
1324
1690
  async function docToPptx(doc, options = {}) {
1325
1691
  options.signal?.throwIfAborted();
1326
1692
  const effectiveOptions = !options.themeId && doc.themeId ? { ...options, themeId: doc.themeId } : options;
1327
- const slideshowDoc = needsSlideshowProjection(doc) ? buildPreviewDoc(doc) : doc;
1693
+ const definitions = collectDocFootnoteDefinitions(doc.blocks);
1694
+ const visualDoc = definitions.size > 0 ? withoutDocFootnoteDefinitions(doc) : doc;
1695
+ const slideshowDoc = needsSlideshowProjection(visualDoc) ? buildPreviewDoc(visualDoc) : visualDoc;
1328
1696
  const flatBlocks = flattenRenderableBlocks(slideshowDoc.blocks);
1329
1697
  if (!flatBlocks.some(isTemplateBlock)) {
1330
1698
  return markdownDocToPptx(docToMarkdown(doc), effectiveOptions);
@@ -1410,16 +1778,58 @@ async function docToPptx(doc, options = {}) {
1410
1778
  slideXmls.push(buildLayerSlideXml(slideBlocks[index], ctx, plan));
1411
1779
  slideContexts.push(ctx);
1412
1780
  }
1413
- return buildPptxPackage(slideXmls, slideContexts, effectiveOptions, {
1414
- layoutXmls: backdrops.layouts.map(buildDeckLayoutXml),
1415
- masterXml: (layoutRelIds) => buildDeckMasterXml({
1416
- backgroundXml: backdrops.master.base ? paintBackgroundXml(backdrops.master.base) : colorBackgroundXml(style.background),
1417
- overlays: backdrops.master.overlays,
1418
- layoutRelIds,
1419
- dark: isDarkTheme(theme)
1420
- }),
1421
- themeXml: buildDeckThemeXml(theme)
1781
+ const authored = authoredBlocksById(doc.blocks);
1782
+ const slideNotes = slideBlocks.map((slide) => {
1783
+ const source = authored.get(slide.id);
1784
+ if (!source) return [];
1785
+ const nodes = [
1786
+ ...source.sourceHeading ? [source.sourceHeading] : [],
1787
+ ...source.contents ?? []
1788
+ ];
1789
+ return speakerNoteLines(footnoteIdentifiersIn(nodes), definitions);
1422
1790
  });
1791
+ return buildPptxPackage(
1792
+ slideXmls,
1793
+ slideContexts,
1794
+ effectiveOptions,
1795
+ {
1796
+ layoutXmls: backdrops.layouts.map(buildDeckLayoutXml),
1797
+ masterXml: (layoutRelIds) => buildDeckMasterXml({
1798
+ backgroundXml: backdrops.master.base ? paintBackgroundXml(backdrops.master.base) : colorBackgroundXml(style.background),
1799
+ overlays: backdrops.master.overlays,
1800
+ layoutRelIds,
1801
+ dark: isDarkTheme(theme)
1802
+ }),
1803
+ themeXml: buildDeckThemeXml(theme)
1804
+ },
1805
+ slideNotes
1806
+ );
1807
+ }
1808
+ function authoredBlocksById(blocks) {
1809
+ const byId = /* @__PURE__ */ new Map();
1810
+ const visit = (list) => {
1811
+ for (const block of list ?? []) {
1812
+ if (!byId.has(block.id)) byId.set(block.id, block);
1813
+ visit(block.children);
1814
+ }
1815
+ };
1816
+ visit(blocks);
1817
+ return byId;
1818
+ }
1819
+ function collectDocFootnoteDefinitions(blocks) {
1820
+ const definitions = /* @__PURE__ */ new Map();
1821
+ for (const block of authoredBlocksById(blocks).values()) {
1822
+ if (block.contents) collectFootnoteDefinitions(block.contents, definitions);
1823
+ }
1824
+ return definitions;
1825
+ }
1826
+ function withoutDocFootnoteDefinitions(doc) {
1827
+ const strip = (list) => list?.map((block) => ({
1828
+ ...block,
1829
+ ...block.contents ? { contents: withoutFootnoteDefinitions(block.contents) } : {},
1830
+ ...block.children ? { children: strip(block.children) } : {}
1831
+ }));
1832
+ return { ...doc, blocks: strip(doc.blocks) ?? [] };
1423
1833
  }
1424
1834
  function resolveSlideStyle(themeId, options, doc) {
1425
1835
  if (!themeId) {
@@ -1658,7 +2068,7 @@ function buildLayerSlideXml(block, ctx, plan) {
1658
2068
  }
1659
2069
  const textScale = isGeometryBoundTemplate(block.template) ? 1 : estimateSlideTextScale(layers);
1660
2070
  const units = mergeTextFlows(layers, textScale);
1661
- const textLayerY = units.filter(({ layer }) => layer.type === "text").map(({ layer }) => resolvePositionValue(layer.position.y, 1080));
2071
+ const textBoxes = units.filter(({ layer }) => layer.type === "text").map(({ layer }) => resolveLayerRect(layer.position, 1920, 1));
1662
2072
  for (const layer of layers) {
1663
2073
  if (layer.type !== "text") continue;
1664
2074
  for (const run of splitIconMarkers3(layer.content.text ?? "")) {
@@ -1668,8 +2078,11 @@ function buildLayerSlideXml(block, ctx, plan) {
1668
2078
  for (let index = 0; index < units.length; index++) {
1669
2079
  if ((index & 31) === 0) ctx.signal?.throwIfAborted();
1670
2080
  const { layer, listIndentPx } = units[index];
1671
- const currentY = layer.type === "text" ? resolvePositionValue(layer.position.y, 1080) : void 0;
1672
- const nextTextY = currentY === void 0 ? void 0 : textLayerY.filter((candidate) => candidate > currentY + 1).sort((a, b) => a - b)[0];
2081
+ const current = layer.type === "text" ? resolveLayerRect(layer.position, 1920, 1) : void 0;
2082
+ const currentY = current?.y;
2083
+ const nextTextY = current === void 0 ? void 0 : textBoxes.filter(
2084
+ (candidate) => candidate.y > current.y + 1 && candidate.x < current.x + current.width - TEXT_COLUMN_OVERLAP_PX && candidate.x + candidate.width > current.x + TEXT_COLUMN_OVERLAP_PX
2085
+ ).map((candidate) => candidate.y).sort((a, b) => a - b)[0];
1673
2086
  const textBottom = nextTextY === void 0 || currentY === void 0 ? void 0 : Math.max(currentY + 1, nextTextY - 8);
1674
2087
  shapes.push(
1675
2088
  ...layer.type === "text" ? [buildLayerTextShape(layer, ctx.allocShapeId(), textBottom, listIndentPx, textScale)] : convertLayerToShapes(layer, ctx, textBottom)
@@ -1694,10 +2107,24 @@ function convertLayerToShapes(layer, ctx, textBottom) {
1694
2107
  const data = svg ? void 0 : ctx.images?.get(layer.content.src);
1695
2108
  if (data) {
1696
2109
  const image = ctx.addImage(layer.content.src, data, layer.content.alt);
1697
- const rect = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
2110
+ const { rect, srcRect } = fitPicture(
2111
+ toEmuRect(resolveLayerRect(layer.position, 1920, 1080)),
2112
+ readImageDimensions(data),
2113
+ layer.content.fit
2114
+ );
1698
2115
  const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === image);
1699
2116
  return [
1700
- buildImageShape(embedded, ctx.allocShapeId(), rect.x, rect.y, rect.width, rect.height)
2117
+ buildImageShape(
2118
+ embedded,
2119
+ ctx.allocShapeId(),
2120
+ rect.x,
2121
+ rect.y,
2122
+ rect.width,
2123
+ rect.height,
2124
+ null,
2125
+ void 0,
2126
+ srcRect
2127
+ )
1701
2128
  ];
1702
2129
  }
1703
2130
  return [
@@ -1897,46 +2324,74 @@ function treeItemsToText(items, depth = 0) {
1897
2324
  }
1898
2325
  return lines.filter(Boolean).join("\n");
1899
2326
  }
2327
+ function pathLineXml(layer) {
2328
+ const { stroke, strokeWidth } = layer.content;
2329
+ if (stroke === "none" || stroke === "transparent" || strokeWidth === 0)
2330
+ return "<a:ln><a:noFill/></a:ln>";
2331
+ const color = parseCssColor(stroke, "FFFFFF");
2332
+ const width = Math.max(1, Math.round((strokeWidth ?? 2) * EMU_PER_CANVAS_PX));
2333
+ const head = markerXml("headEnd", layer.content.startMarker);
2334
+ const tail = markerXml("tailEnd", layer.content.endMarker);
2335
+ const dash = dashXml(layer.content.borderStyle, layer.content.dasharray);
2336
+ return `<a:ln w="${width}">${solidColorXml(color)}${dash}${head}${tail}</a:ln>`;
2337
+ }
2338
+ var TEXT_COLUMN_OVERLAP_PX = 8;
1900
2339
  function buildPathLayerShapes(layer, ctx) {
2340
+ let d = layer.content.d;
1901
2341
  if (layer.content.shapeKind) {
1902
- const shape = {
1903
- id: layer.id,
1904
- type: "shape",
1905
- position: layer.position,
1906
- content: {
1907
- shape: layer.content.shapeKind === "circle" ? "circle" : "rect",
1908
- fill: layer.content.fill,
1909
- fillOpacity: layer.content.fillOpacity,
1910
- gradient: layer.content.gradient,
1911
- stroke: layer.content.stroke,
1912
- strokeWidth: layer.content.strokeWidth,
1913
- borderStyle: layer.content.borderStyle
1914
- }
1915
- };
1916
- return [buildLayerShape(shape, ctx.allocShapeId())];
1917
- }
1918
- const numbers = layer.content.d.match(/-?\d+(?:\.\d+)?/g)?.map(Number);
1919
- if (!numbers || numbers.length < 4) return [];
1920
- const x1 = numbers[0];
1921
- const y1 = numbers[1];
1922
- const x2 = numbers[numbers.length - 2];
1923
- const y2 = numbers[numbers.length - 1];
2342
+ const box = resolveLayerRect(layer.position, 100, 100);
2343
+ const named = shapePath(layer.content.shapeKind, box.x, box.y, box.width, box.height);
2344
+ if (!named) {
2345
+ const shape = {
2346
+ id: layer.id,
2347
+ type: "shape",
2348
+ position: layer.position,
2349
+ content: {
2350
+ shape: layer.content.shapeKind === "circle" ? "circle" : "rect",
2351
+ fill: layer.content.fill,
2352
+ fillOpacity: layer.content.fillOpacity,
2353
+ gradient: layer.content.gradient,
2354
+ stroke: layer.content.stroke,
2355
+ strokeWidth: layer.content.strokeWidth,
2356
+ borderStyle: layer.content.borderStyle
2357
+ }
2358
+ };
2359
+ return [buildLayerShape(shape, ctx.allocShapeId())];
2360
+ }
2361
+ d = named;
2362
+ }
2363
+ const segments = parseSvgPath(d);
2364
+ const bounds = segments ? pathBounds(segments) : null;
2365
+ if (!segments || !bounds) return [];
2366
+ const { fill, gradient, fillOpacity } = layer.content;
2367
+ const filled = !!gradient || !!fill && fill !== "none" && fill !== "transparent";
2368
+ const name = `<p:nvSpPr><p:cNvPr id="${ctx.allocShapeId()}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>`;
2369
+ if (!filled && isStraightLine(segments)) {
2370
+ const points = segments.filter(
2371
+ (segment) => segment.op === "M" || segment.op === "L"
2372
+ );
2373
+ const from = points[0];
2374
+ const to = points[points.length - 1];
2375
+ const rect2 = toEmuRect({
2376
+ x: Math.min(from.x, to.x),
2377
+ y: Math.min(from.y, to.y),
2378
+ width: Math.max(1, Math.abs(to.x - from.x)),
2379
+ height: Math.max(1, Math.abs(to.y - from.y))
2380
+ });
2381
+ const flipH = to.x < from.x ? ' flipH="1"' : "";
2382
+ const flipV = to.y < from.y ? ' flipV="1"' : "";
2383
+ return [
2384
+ `<p:sp>${name}<p:spPr><a:xfrm${flipH}${flipV}><a:off x="${rect2.x}" y="${rect2.y}"/><a:ext cx="${rect2.width}" cy="${rect2.height}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:noFill/>` + pathLineXml(layer) + `</p:spPr></p:sp>`
2385
+ ];
2386
+ }
1924
2387
  const rect = toEmuRect({
1925
- x: Math.min(x1, x2),
1926
- y: Math.min(y1, y2),
1927
- width: Math.max(1, Math.abs(x2 - x1)),
1928
- height: Math.max(1, Math.abs(y2 - y1))
2388
+ x: bounds.x,
2389
+ y: bounds.y,
2390
+ width: Math.max(1, bounds.width),
2391
+ height: Math.max(1, bounds.height)
1929
2392
  });
1930
- const flipH = x2 < x1 ? ' flipH="1"' : "";
1931
- const flipV = y2 < y1 ? ' flipV="1"' : "";
1932
- const stroke = parseCssColor(layer.content.stroke, "FFFFFF");
1933
- const width = Math.max(1, Math.round((layer.content.strokeWidth ?? 2) * EMU_PER_CANVAS_PX));
1934
- const head = markerXml("headEnd", layer.content.startMarker);
1935
- const tail = markerXml("tailEnd", layer.content.endMarker);
1936
- const dash = dashXml(layer.content.borderStyle, layer.content.dasharray);
1937
- const shapeId = ctx.allocShapeId();
1938
2393
  return [
1939
- `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:noFill/><a:ln w="${width}">${solidColorXml(stroke)}${dash}${head}${tail}</a:ln></p:spPr></p:sp>`
2394
+ `<p:sp>${name}<p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm>` + customGeometryXml(segments, bounds, EMU_PER_CANVAS_PX, filled) + (filled ? fillXml(fill, fillOpacity, gradient) : "<a:noFill/>") + pathLineXml(layer) + `</p:spPr></p:sp>`
1940
2395
  ];
1941
2396
  }
1942
2397
  function textLayerForPlaceholder(position, text, ctx) {
@@ -2380,9 +2835,10 @@ var SEMANTIC_DECK_STRUCTURE = {
2380
2835
  masterXml: ([layoutRelId]) => buildSlideMasterXml(layoutRelId),
2381
2836
  themeXml: buildThemeXml()
2382
2837
  };
2383
- async function buildPptxPackage(slideXmls, slideContexts, options, structure = SEMANTIC_DECK_STRUCTURE) {
2838
+ async function buildPptxPackage(slideXmls, slideContexts, options, structure = SEMANTIC_DECK_STRUCTURE, slideNotes = []) {
2384
2839
  const pkg = createPackage();
2385
2840
  const slideCount = slideXmls.length;
2841
+ const hasNotes = slideNotes.some((lines) => (lines?.length ?? 0) > 0);
2386
2842
  const relIds = new RelIdAllocator();
2387
2843
  const PRESENTATION_PART = "ppt/presentation.xml";
2388
2844
  const SLIDE_MASTER_PART = "ppt/slideMasters/slideMaster1.xml";
@@ -2400,18 +2856,40 @@ async function buildPptxPackage(slideXmls, slideContexts, options, structure = S
2400
2856
  face,
2401
2857
  relId: relIds.alloc(PRESENTATION_PART)
2402
2858
  }));
2859
+ const notesMasterRelId = hasNotes ? relIds.alloc(PRESENTATION_PART) : void 0;
2403
2860
  const presentationXml = buildPresentationXml(
2404
2861
  slideCount,
2405
2862
  slideRelIds,
2406
2863
  slideMasterRelId,
2407
2864
  themeRelId,
2408
- embeddedIconFonts.map(({ face, relId }) => ({ typeface: face.typeface, relId }))
2865
+ embeddedIconFonts.map(({ face, relId }) => ({ typeface: face.typeface, relId })),
2866
+ notesMasterRelId
2409
2867
  );
2410
2868
  pkg.addPart("ppt/presentation.xml", presentationXml, CONTENT_TYPE_PPTX_PRESENTATION);
2411
2869
  for (let i = 0; i < slideCount; i++) {
2412
2870
  const slidePath = `ppt/slides/slide${i + 1}.xml`;
2413
2871
  pkg.addPart(slidePath, slideXmls[i], CONTENT_TYPE_PPTX_SLIDE);
2414
2872
  const ctx = slideContexts[i];
2873
+ const notes = slideNotes[i];
2874
+ if (notes && notes.length > 0) {
2875
+ const notesPart = `ppt/notesSlides/notesSlide${i + 1}.xml`;
2876
+ pkg.addPart(notesPart, buildNotesSlideXml(notes), CONTENT_TYPE_PPTX_NOTES_SLIDE);
2877
+ pkg.addRelationship(notesPart, {
2878
+ id: "rId1",
2879
+ type: REL_NOTES_MASTER,
2880
+ target: "../notesMasters/notesMaster1.xml"
2881
+ });
2882
+ pkg.addRelationship(notesPart, {
2883
+ id: "rId2",
2884
+ type: REL_SLIDE,
2885
+ target: `../slides/slide${i + 1}.xml`
2886
+ });
2887
+ ctx.relationships.push({
2888
+ id: ctx.allocRelId(),
2889
+ type: REL_NOTES_SLIDE,
2890
+ target: `../notesSlides/notesSlide${i + 1}.xml`
2891
+ });
2892
+ }
2415
2893
  for (const rel of ctx.relationships) {
2416
2894
  pkg.addRelationship(slidePath, {
2417
2895
  id: rel.id,
@@ -2454,6 +2932,21 @@ async function buildPptxPackage(slideXmls, slideContexts, options, structure = S
2454
2932
  target: "../theme/theme1.xml"
2455
2933
  });
2456
2934
  pkg.addPart("ppt/theme/theme1.xml", structure.themeXml, CONTENT_TYPE_PPTX_THEME);
2935
+ if (notesMasterRelId) {
2936
+ const NOTES_MASTER_PART = "ppt/notesMasters/notesMaster1.xml";
2937
+ pkg.addPart(NOTES_MASTER_PART, buildNotesMasterXml(), CONTENT_TYPE_PPTX_NOTES_MASTER);
2938
+ pkg.addPart("ppt/theme/theme2.xml", structure.themeXml, CONTENT_TYPE_PPTX_THEME);
2939
+ pkg.addRelationship(NOTES_MASTER_PART, {
2940
+ id: relIds.alloc(NOTES_MASTER_PART),
2941
+ type: REL_THEME,
2942
+ target: "../theme/theme2.xml"
2943
+ });
2944
+ pkg.addRelationship("ppt/presentation.xml", {
2945
+ id: notesMasterRelId,
2946
+ type: REL_NOTES_MASTER,
2947
+ target: "notesMasters/notesMaster1.xml"
2948
+ });
2949
+ }
2457
2950
  pkg.addRelationship("", {
2458
2951
  id: "rId1",
2459
2952
  type: REL_OFFICE_DOCUMENT,
@@ -2715,7 +3208,7 @@ async function importDocument(pkg, options) {
2715
3208
  let extraction = null;
2716
3209
  if (inferTheme || inferLayouts) {
2717
3210
  try {
2718
- const { extractPptxTheme } = await import("./extract-UTAOG7LV.js");
3211
+ const { extractPptxTheme } = await import("./extract-27YYYYBZ.js");
2719
3212
  extraction = await extractPptxTheme(pkg);
2720
3213
  } catch (err) {
2721
3214
  warnInferenceFailure("theme extraction", err);
@@ -2732,7 +3225,7 @@ async function importDocument(pkg, options) {
2732
3225
  }
2733
3226
  if (inferLayouts) {
2734
3227
  try {
2735
- const { analyzePptxLayouts: analyzePptxLayouts2 } = await import("./layouts-2A7QNF6Y.js");
3228
+ const { analyzePptxLayouts: analyzePptxLayouts2 } = await import("./layouts-5YNFCOMS.js");
2736
3229
  const { colorHintsFromExtraction } = await import("./mapTheme-4TWH25FT.js");
2737
3230
  ctx.inference = await analyzePptxLayouts2(pkg, {
2738
3231
  colors: extraction ? colorHintsFromExtraction(extraction) : {}