@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.
- package/dist/{chunk-MF4ZQQJ7.js → chunk-42ETCBAK.js} +14 -5
- package/dist/{chunk-5HUHSLGA.js → chunk-6OB2DSGW.js} +1 -1
- package/dist/{chunk-ATWVNF7D.js → chunk-6QVOHRRL.js} +1 -1
- package/dist/{chunk-YRFBWYAD.js → chunk-AQZ2CAET.js} +1 -1
- package/dist/{chunk-S5PCVMKU.js → chunk-BB3BLYLM.js} +8 -0
- package/dist/{chunk-OK622ILP.js → chunk-BGVT5I55.js} +1 -1
- package/dist/{chunk-WATAOG4Y.js → chunk-FD7IDL5V.js} +2 -2
- package/dist/{chunk-3KXEE7KH.js → chunk-GJWBVR4X.js} +3 -3
- package/dist/{chunk-KZUP6TIV.js → chunk-HAGYYPFR.js} +2 -2
- package/dist/{chunk-4SPMLGOG.js → chunk-J6LR22RZ.js} +9 -1
- package/dist/{chunk-4A3EHQJP.js → chunk-KWZDYOCB.js} +3 -3
- package/dist/{chunk-LSUMEIQ7.js → chunk-O4UYRQX7.js} +4 -4
- package/dist/{chunk-SWLDHEZI.js → chunk-ONPR2LAD.js} +561 -68
- package/dist/{chunk-6N2J7C2B.js → chunk-RBKUII3V.js} +1 -1
- package/dist/{chunk-ILCJ3WFD.js → chunk-SUWCBXZH.js} +1 -1
- package/dist/{chunk-ZJERBWTB.js → chunk-WPLCWAN3.js} +10 -4
- package/dist/data/index.js +4 -4
- package/dist/docx/index.js +3 -3
- package/dist/epub/index.js +1 -1
- package/dist/{extract-UTAOG7LV.js → extract-27YYYYBZ.js} +3 -3
- package/dist/html/index.js +1 -1
- package/dist/html/prepare.js +1 -1
- package/dist/index.js +14 -14
- package/dist/infer/index.js +4 -4
- package/dist/{layouts-2A7QNF6Y.js → layouts-5YNFCOMS.js} +2 -2
- package/dist/{materialize-ICXNO4S4.js → materialize-2MS3SXA6.js} +4 -4
- package/dist/ooxml/index.js +3 -3
- package/dist/outside-in/index.js +2 -2
- package/dist/pptx/index.js +4 -4
- package/dist/{readers-3P5A6A2A.js → readers-TAWBODNF.js} +3 -3
- package/dist/registry/index.js +1 -1
- package/dist/xlsx/index.js +4 -4
- 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-
|
|
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-
|
|
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
|
|
453
|
-
|
|
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}"
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
|
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
|
|
1672
|
-
const
|
|
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 =
|
|
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(
|
|
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
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const
|
|
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:
|
|
1926
|
-
y:
|
|
1927
|
-
width: Math.max(1,
|
|
1928
|
-
height: Math.max(1,
|
|
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
|
|
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-
|
|
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-
|
|
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) : {}
|