@markdstage/markdstage 3.4.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +1 -1
- package/shared/README.md +40 -4
- package/shared/architecture-editor/editor.css +9 -5
- package/shared/architecture-editor/editor.js +440 -75
- package/shared/architecture-editor/index.html +2 -2
- package/shared/docs/custom-theme-authoring.md +61 -4
- package/shared/markdown-deck.mjs +9 -5
- package/shared/renderer/architecture-document.mjs +169 -10
- package/shared/renderer/index.html +24 -1
- package/shared/renderer/mermaid-scene.mjs +6725 -197
- package/shared/renderer/renderer.js +245 -53
- package/shared/renderer/scene-graph.mjs +83 -13
- package/shared/renderer/scene-pptx.mjs +154 -1
- package/shared/renderer/scene-svg.mjs +227 -11
- package/shared/renderer/slide-background.mjs +22 -0
- package/shared/renderer/slides.css +32 -6
- package/shared/renderer/theme.mjs +328 -12
- package/shared/runtime/browser.mjs +75 -5
- package/shared/runtime/deck-session.mjs +12 -17
- package/shared/runtime/output-paths.mjs +7 -0
- package/shared/runtime/output.mjs +4 -2
- package/shared/runtime/pptx-package.mjs +103 -22
- package/shared/runtime/presentation-server.mjs +44 -2
- package/shared/runtime/slide-backgrounds.mjs +44 -0
- package/shared/schema/theme-metadata-v1.schema.json +25 -0
- package/shared/schema/theme-v1.json +3 -3
- package/src/cli.mjs +9 -2
- package/src/commands/export.mjs +2 -0
|
@@ -15,18 +15,28 @@ export const MAX_STRING_LENGTH = 8192;
|
|
|
15
15
|
|
|
16
16
|
const SCENE_VERSION = 1;
|
|
17
17
|
const METRIC_PRECISION = 10;
|
|
18
|
+
const DRAWINGML_ANGLE_UNITS_PER_DEGREE = 60000;
|
|
19
|
+
const DRAWINGML_HALF_TURN = 180 * DRAWINGML_ANGLE_UNITS_PER_DEGREE;
|
|
20
|
+
const DRAWINGML_FULL_TURN = 360 * DRAWINGML_ANGLE_UNITS_PER_DEGREE;
|
|
18
21
|
const NODE_KINDS = new Set(["group", "shape", "text", "image", "connector", "fallback"]);
|
|
19
22
|
const SOURCE_KINDS = new Set(["architecture", "mermaid"]);
|
|
20
23
|
const SHAPE_PRESETS = new Set([
|
|
21
24
|
"rect",
|
|
22
25
|
"roundedRect",
|
|
26
|
+
"topRoundedRect",
|
|
23
27
|
"ellipse",
|
|
24
28
|
"diamond",
|
|
25
29
|
"triangle",
|
|
26
30
|
"hexagon",
|
|
31
|
+
"quarterHeightHexagon",
|
|
27
32
|
"parallelogram",
|
|
33
|
+
"reverseParallelogram",
|
|
34
|
+
"trapezoid",
|
|
35
|
+
"invertedTrapezoid",
|
|
36
|
+
"sequenceTab",
|
|
28
37
|
]);
|
|
29
38
|
const DASH_STYLES = new Set(["", "solid", "dash", "dashDot", "dot"]);
|
|
39
|
+
const LINE_CAPS = new Set(["butt", "round", "square"]);
|
|
30
40
|
const IMAGE_FITS = new Set(["contain", "cover", "fill", "none"]);
|
|
31
41
|
const ARROWS = new Set([null, "none", "triangle", "arrow", "stealth", "diamond", "oval"]);
|
|
32
42
|
const ALIGNMENTS = new Set(["left", "center", "right", "justify"]);
|
|
@@ -44,8 +54,8 @@ const COMMON_NODE_KEYS = new Set([
|
|
|
44
54
|
]);
|
|
45
55
|
const NODE_KEYS = {
|
|
46
56
|
group: new Set(["children", "style", "text", "textLayout"]),
|
|
47
|
-
shape: new Set(["preset", "style", "text", "textLayout"]),
|
|
48
|
-
text: new Set(["text", "textLayout"]),
|
|
57
|
+
shape: new Set(["preset", "style", "text", "textLayout", "rotation"]),
|
|
58
|
+
text: new Set(["text", "textLayout", "rotation"]),
|
|
49
59
|
image: new Set(["src", "alt", "fit", "opacity"]),
|
|
50
60
|
connector: new Set(["points", "style", "arrowStart", "arrowEnd", "label"]),
|
|
51
61
|
fallback: new Set(["reason"]),
|
|
@@ -63,11 +73,22 @@ function roundedMetric(value) {
|
|
|
63
73
|
return Math.round(Math.max(0, Number(value) || 0) * METRIC_PRECISION) / METRIC_PRECISION;
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
function roundedCoordinate(value) {
|
|
77
|
+
return Math.round((Number(value) || 0) * METRIC_PRECISION) / METRIC_PRECISION;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
function roundedExtent(value) {
|
|
67
81
|
const number = Number(value);
|
|
68
82
|
return Number.isFinite(number) && number >= 0 ? roundedMetric(number) : value;
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
export function normalizeRotationAngle(value) {
|
|
86
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
87
|
+
let units = Math.round((((value % 360) + 360) % 360) * DRAWINGML_ANGLE_UNITS_PER_DEGREE);
|
|
88
|
+
if (units >= DRAWINGML_HALF_TURN) units -= DRAWINGML_FULL_TURN;
|
|
89
|
+
return units === 0 ? 0 : units / DRAWINGML_ANGLE_UNITS_PER_DEGREE;
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
function finiteNumber(value, path) {
|
|
72
93
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
73
94
|
fail(`${path} must be a finite number`);
|
|
@@ -108,6 +129,14 @@ function exactKeys(value, allowed, path) {
|
|
|
108
129
|
}
|
|
109
130
|
}
|
|
110
131
|
|
|
132
|
+
function rejectInheritedKeys(value, allowed, path) {
|
|
133
|
+
for (const key of allowed) {
|
|
134
|
+
if (!Object.hasOwn(value, key) && key in value) {
|
|
135
|
+
fail(`${path}.${key} must be an own property`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
111
140
|
function requiredObject(value, path) {
|
|
112
141
|
if (!isPlainObject(value)) fail(`${path} must be an object`);
|
|
113
142
|
return value;
|
|
@@ -178,8 +207,8 @@ function normalizeBounds(value, offsetX, offsetY) {
|
|
|
178
207
|
return null;
|
|
179
208
|
}
|
|
180
209
|
return {
|
|
181
|
-
x:
|
|
182
|
-
y:
|
|
210
|
+
x: roundedCoordinate(offsetX + x),
|
|
211
|
+
y: roundedCoordinate(offsetY + y),
|
|
183
212
|
width: roundedMetric(width),
|
|
184
213
|
height: roundedMetric(height),
|
|
185
214
|
};
|
|
@@ -191,8 +220,8 @@ function normalizePoint(value, offsetX, offsetY) {
|
|
|
191
220
|
const y = Number(value.y);
|
|
192
221
|
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
193
222
|
return {
|
|
194
|
-
x:
|
|
195
|
-
y:
|
|
223
|
+
x: roundedCoordinate(offsetX + x),
|
|
224
|
+
y: roundedCoordinate(offsetY + y),
|
|
196
225
|
};
|
|
197
226
|
}
|
|
198
227
|
|
|
@@ -202,8 +231,8 @@ function boundsFromPoints(points) {
|
|
|
202
231
|
const left = Math.min(...xs);
|
|
203
232
|
const top = Math.min(...ys);
|
|
204
233
|
return {
|
|
205
|
-
x:
|
|
206
|
-
y:
|
|
234
|
+
x: roundedCoordinate(left),
|
|
235
|
+
y: roundedCoordinate(top),
|
|
207
236
|
width: roundedMetric(Math.max(...xs) - left),
|
|
208
237
|
height: roundedMetric(Math.max(...ys) - top),
|
|
209
238
|
};
|
|
@@ -272,18 +301,34 @@ function validateCapability(value, path) {
|
|
|
272
301
|
function validateStyle(value, path) {
|
|
273
302
|
if (value === undefined) return;
|
|
274
303
|
requiredObject(value, path);
|
|
275
|
-
|
|
304
|
+
const keys = new Set([
|
|
305
|
+
"fill",
|
|
306
|
+
"stroke",
|
|
307
|
+
"strokeWidth",
|
|
308
|
+
"dash",
|
|
309
|
+
"lineCap",
|
|
310
|
+
"opacity",
|
|
311
|
+
"fillOpacity",
|
|
312
|
+
"strokeOpacity",
|
|
313
|
+
"cornerRadius",
|
|
314
|
+
]);
|
|
315
|
+
exactKeys(value, keys, path);
|
|
316
|
+
rejectInheritedKeys(value, keys, path);
|
|
276
317
|
if (value.fill !== undefined) validateColor(value.fill, `${path}.fill`);
|
|
277
318
|
if (value.stroke !== undefined) validateColor(value.stroke, `${path}.stroke`);
|
|
278
319
|
if (value.strokeWidth !== undefined) nonNegativeNumber(value.strokeWidth, `${path}.strokeWidth`);
|
|
279
320
|
if (value.cornerRadius !== undefined) nonNegativeNumber(value.cornerRadius, `${path}.cornerRadius`);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
321
|
+
for (const key of ["opacity", "fillOpacity", "strokeOpacity"]) {
|
|
322
|
+
if (value[key] === undefined) continue;
|
|
323
|
+
const opacity = finiteNumber(value[key], `${path}.${key}`);
|
|
324
|
+
if (opacity < 0 || opacity > 1) fail(`${path}.${key} must be between 0 and 1`);
|
|
283
325
|
}
|
|
284
326
|
if (value.dash !== undefined && !DASH_STYLES.has(value.dash)) {
|
|
285
327
|
fail(`${path}.dash must be "", "solid", "dash", "dashDot", or "dot"`);
|
|
286
328
|
}
|
|
329
|
+
if (value.lineCap !== undefined && !LINE_CAPS.has(value.lineCap)) {
|
|
330
|
+
fail(`${path}.lineCap must be "butt", "round", or "square"`);
|
|
331
|
+
}
|
|
287
332
|
}
|
|
288
333
|
|
|
289
334
|
function validateText(value, path) {
|
|
@@ -386,6 +431,16 @@ function validateNode(node, path, state, depth) {
|
|
|
386
431
|
validateCapability(node.capability, `${path}.capability`);
|
|
387
432
|
validateAccessibility(node.accessibility, `${path}.accessibility`);
|
|
388
433
|
validateMeta(node.meta, `${path}.meta`);
|
|
434
|
+
if (node.kind === "shape" || node.kind === "text") {
|
|
435
|
+
rejectInheritedKeys(node, new Set(["rotation"]), path);
|
|
436
|
+
if (node.rotation !== undefined) {
|
|
437
|
+
const rotation = finiteNumber(node.rotation, `${path}.rotation`);
|
|
438
|
+
const normalized = normalizeRotationAngle(rotation);
|
|
439
|
+
if (normalized !== rotation || Object.is(rotation, -0)) {
|
|
440
|
+
fail(`${path}.rotation must be normalized to [-180, 180) degrees`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
389
444
|
if (node.kind === "group") {
|
|
390
445
|
if (!Array.isArray(node.children)) fail(`${path}.children must be an array`);
|
|
391
446
|
validateStyle(node.style, `${path}.style`);
|
|
@@ -453,7 +508,9 @@ function normalizeStyle(value) {
|
|
|
453
508
|
return Object.fromEntries(
|
|
454
509
|
Object.entries(value).map(([key, entry]) => {
|
|
455
510
|
if (key === "strokeWidth" || key === "cornerRadius") return [key, roundedMetric(entry)];
|
|
456
|
-
if (key === "opacity"
|
|
511
|
+
if (key === "opacity" || key === "fillOpacity" || key === "strokeOpacity") {
|
|
512
|
+
return [key, clampOpacity(entry)];
|
|
513
|
+
}
|
|
457
514
|
return [key, entry];
|
|
458
515
|
}),
|
|
459
516
|
);
|
|
@@ -538,6 +595,7 @@ function normalizeKnownNode(node, path, bounds, offsetX, offsetY, diagnostics, d
|
|
|
538
595
|
};
|
|
539
596
|
}
|
|
540
597
|
if (node.kind === "shape") {
|
|
598
|
+
const rotation = node.rotation === undefined ? 0 : normalizeRotationAngle(node.rotation);
|
|
541
599
|
if (!SHAPE_PRESETS.has(node.preset)) {
|
|
542
600
|
const reason = `unsupported shape preset: ${String(node.preset)}`;
|
|
543
601
|
unsupported(path, "fallback", reason, diagnostics);
|
|
@@ -546,16 +604,19 @@ function normalizeKnownNode(node, path, bounds, offsetX, offsetY, diagnostics, d
|
|
|
546
604
|
return {
|
|
547
605
|
...base,
|
|
548
606
|
preset: node.preset,
|
|
607
|
+
...(rotation ? { rotation } : {}),
|
|
549
608
|
...(node.style !== undefined ? { style: normalizeStyle(node.style) } : {}),
|
|
550
609
|
...(node.text !== undefined ? { text: normalizeText(node.text) } : {}),
|
|
551
610
|
...(node.textLayout !== undefined ? { textLayout: normalizeTextLayout(node.textLayout) } : {}),
|
|
552
611
|
};
|
|
553
612
|
}
|
|
554
613
|
if (node.kind === "text") {
|
|
614
|
+
const rotation = node.rotation === undefined ? 0 : normalizeRotationAngle(node.rotation);
|
|
555
615
|
return {
|
|
556
616
|
...base,
|
|
557
617
|
text: normalizeText(node.text),
|
|
558
618
|
...(node.textLayout !== undefined ? { textLayout: normalizeTextLayout(node.textLayout) } : {}),
|
|
619
|
+
...(rotation ? { rotation } : {}),
|
|
559
620
|
};
|
|
560
621
|
}
|
|
561
622
|
if (node.kind === "image") {
|
|
@@ -645,6 +706,15 @@ function normalizeNode(node, path, offsetX, offsetY, depth, output, diagnostics)
|
|
|
645
706
|
output.push(normalized);
|
|
646
707
|
return;
|
|
647
708
|
}
|
|
709
|
+
if (["text", "shape"].includes(node.kind) && "rotation" in node &&
|
|
710
|
+
(!Object.hasOwn(node, "rotation") || normalizeRotationAngle(node.rotation) === null)) {
|
|
711
|
+
const reason = `${node.kind} rotation is not a finite number`;
|
|
712
|
+
unsupported(path, "fallback", reason, diagnostics);
|
|
713
|
+
const normalized = fallbackNode(node, path, reason, bounds);
|
|
714
|
+
normalized.__sortZ = Number.isFinite(Number(node.z)) ? Number(node.z) : output.length;
|
|
715
|
+
output.push(normalized);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
648
718
|
const normalized = normalizeKnownNode(node, path, bounds, offsetX, offsetY, diagnostics, depth);
|
|
649
719
|
normalized.__sortZ = Number.isFinite(Number(node.z)) ? Number(node.z) : output.length;
|
|
650
720
|
output.push(normalized);
|
|
@@ -61,7 +61,15 @@ function copyTextLayout(target, textLayout = {}, keys) {
|
|
|
61
61
|
|
|
62
62
|
function styleFields(style = {}) {
|
|
63
63
|
const mapped = {};
|
|
64
|
-
copyDefined(mapped, style, [
|
|
64
|
+
copyDefined(mapped, style, [
|
|
65
|
+
"fill",
|
|
66
|
+
"stroke",
|
|
67
|
+
"lineCap",
|
|
68
|
+
"opacity",
|
|
69
|
+
"fillOpacity",
|
|
70
|
+
"strokeOpacity",
|
|
71
|
+
"cornerRadius",
|
|
72
|
+
]);
|
|
65
73
|
if (style.strokeWidth !== undefined && style.strokeWidth > 0) {
|
|
66
74
|
mapped.strokeWidth = style.strokeWidth;
|
|
67
75
|
} else if (style.strokeWidth === 0) {
|
|
@@ -153,12 +161,141 @@ function shapeElement(node, index, options, shape) {
|
|
|
153
161
|
shape,
|
|
154
162
|
...geometryFields(node),
|
|
155
163
|
...styleFields(node.style),
|
|
164
|
+
...(node.rotation !== undefined ? { rotation: node.rotation } : {}),
|
|
156
165
|
};
|
|
157
166
|
if (node.text !== undefined) element.text = cloneText(node.text);
|
|
158
167
|
copyTextLayout(element, node.textLayout, ["verticalAlignment", "textWrap", "textInsets"]);
|
|
159
168
|
return element;
|
|
160
169
|
}
|
|
161
170
|
|
|
171
|
+
function sameValue(left, right) {
|
|
172
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sameMetric(left, right) {
|
|
176
|
+
return Math.abs(left - right) <= 0.1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function samePoints(actual, expected) {
|
|
180
|
+
return Array.isArray(actual) &&
|
|
181
|
+
actual.length === expected.length &&
|
|
182
|
+
actual.every((point, index) =>
|
|
183
|
+
sameMetric(point.x, expected[index].x) &&
|
|
184
|
+
sameMetric(point.y, expected[index].y));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function stadiumMapping(scene, group, index, options) {
|
|
188
|
+
if (
|
|
189
|
+
group.kind !== "group" ||
|
|
190
|
+
group.meta?.mermaid?.shape !== "stadium" ||
|
|
191
|
+
typeof group.sourcePath !== "string"
|
|
192
|
+
) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const prefix = `${group.sourcePath}.`;
|
|
196
|
+
const descendants = scene.nodes.filter((node) =>
|
|
197
|
+
node.sourcePath?.startsWith(prefix));
|
|
198
|
+
const part = (partIndex) => descendants.find((node) =>
|
|
199
|
+
node.sourcePath === `${group.sourcePath}.parts[${partIndex}]`);
|
|
200
|
+
const left = part(0);
|
|
201
|
+
const right = part(1);
|
|
202
|
+
const center = part(2);
|
|
203
|
+
const top = part(3);
|
|
204
|
+
const bottom = part(4);
|
|
205
|
+
const label = descendants.find((node) =>
|
|
206
|
+
node.sourcePath === `${group.sourcePath}.label`);
|
|
207
|
+
if (
|
|
208
|
+
!left ||
|
|
209
|
+
!right ||
|
|
210
|
+
!center ||
|
|
211
|
+
!top ||
|
|
212
|
+
!bottom ||
|
|
213
|
+
descendants.length !== (label ? 6 : 5)
|
|
214
|
+
) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const unsupported = [group, ...descendants].find((node) =>
|
|
218
|
+
node.kind === "fallback" ||
|
|
219
|
+
node.capability?.pptx === "fallback");
|
|
220
|
+
if (unsupported) {
|
|
221
|
+
return {
|
|
222
|
+
prefix,
|
|
223
|
+
fallback: fallbackFor(
|
|
224
|
+
group,
|
|
225
|
+
index,
|
|
226
|
+
options,
|
|
227
|
+
fallbackReason(unsupported, "stadium-rendered-as-artwork"),
|
|
228
|
+
),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
left.kind !== "shape" ||
|
|
233
|
+
left.preset !== "ellipse" ||
|
|
234
|
+
right.kind !== "shape" ||
|
|
235
|
+
right.preset !== "ellipse" ||
|
|
236
|
+
center.kind !== "shape" ||
|
|
237
|
+
center.preset !== "rect" ||
|
|
238
|
+
top.kind !== "connector" ||
|
|
239
|
+
bottom.kind !== "connector" ||
|
|
240
|
+
(label && (label.kind !== "text" || label.rotation !== undefined))
|
|
241
|
+
) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const { x, y, width, height } = group.bounds;
|
|
245
|
+
const radius = height / 2;
|
|
246
|
+
const expectedCenterStyle = { ...left.style, stroke: null, strokeWidth: 0 };
|
|
247
|
+
const expectedOutlineStyle = { ...left.style, fill: null };
|
|
248
|
+
const expectedGeometry = (
|
|
249
|
+
sameMetric(left.bounds.x, x) &&
|
|
250
|
+
sameMetric(left.bounds.y, y) &&
|
|
251
|
+
sameMetric(left.bounds.width, height) &&
|
|
252
|
+
sameMetric(left.bounds.height, height) &&
|
|
253
|
+
sameMetric(right.bounds.x, x + width - height) &&
|
|
254
|
+
sameMetric(right.bounds.y, y) &&
|
|
255
|
+
sameMetric(right.bounds.width, height) &&
|
|
256
|
+
sameMetric(right.bounds.height, height) &&
|
|
257
|
+
sameMetric(center.bounds.x, x + radius) &&
|
|
258
|
+
sameMetric(center.bounds.y, y) &&
|
|
259
|
+
sameMetric(center.bounds.width, width - height) &&
|
|
260
|
+
sameMetric(center.bounds.height, height) &&
|
|
261
|
+
samePoints(top.points, [
|
|
262
|
+
{ x: x + radius, y },
|
|
263
|
+
{ x: x + width - radius, y },
|
|
264
|
+
]) &&
|
|
265
|
+
samePoints(bottom.points, [
|
|
266
|
+
{ x: x + radius, y: y + height },
|
|
267
|
+
{ x: x + width - radius, y: y + height },
|
|
268
|
+
])
|
|
269
|
+
);
|
|
270
|
+
if (
|
|
271
|
+
!expectedGeometry ||
|
|
272
|
+
!sameValue(left.style, right.style) ||
|
|
273
|
+
!sameValue(center.style, expectedCenterStyle) ||
|
|
274
|
+
!sameValue(top.style, expectedOutlineStyle) ||
|
|
275
|
+
!sameValue(bottom.style, expectedOutlineStyle) ||
|
|
276
|
+
top.arrowStart !== "none" ||
|
|
277
|
+
top.arrowEnd !== "none" ||
|
|
278
|
+
bottom.arrowStart !== "none" ||
|
|
279
|
+
bottom.arrowEnd !== "none"
|
|
280
|
+
) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
prefix,
|
|
285
|
+
element: shapeElement({
|
|
286
|
+
...group,
|
|
287
|
+
kind: "shape",
|
|
288
|
+
style: left.style,
|
|
289
|
+
...(label
|
|
290
|
+
? {
|
|
291
|
+
text: label.text,
|
|
292
|
+
textLayout: label.textLayout,
|
|
293
|
+
}
|
|
294
|
+
: {}),
|
|
295
|
+
}, index, options, "stadium"),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
162
299
|
function groupElement(node, index, options) {
|
|
163
300
|
if (!hasVisibleStyle(node.style) && node.text === undefined) return null;
|
|
164
301
|
return shapeElement(node, index, options, options.groupPreset);
|
|
@@ -173,6 +310,10 @@ function textElement(node, index, options) {
|
|
|
173
310
|
paragraphs: text.paragraphs,
|
|
174
311
|
};
|
|
175
312
|
copyDefined(element, node.textLayout || {}, ["textInsets", "textWrap"]);
|
|
313
|
+
if (node.rotation !== undefined) {
|
|
314
|
+
element.rotation = node.rotation;
|
|
315
|
+
copyDefined(element, node.textLayout || {}, ["verticalAlignment"]);
|
|
316
|
+
}
|
|
176
317
|
return element;
|
|
177
318
|
}
|
|
178
319
|
|
|
@@ -249,7 +390,19 @@ export function sceneToPptxElements(scene, options = {}) {
|
|
|
249
390
|
const normalizedOptions = normalizeOptions(scene, options);
|
|
250
391
|
const elements = [];
|
|
251
392
|
const fallbacks = [];
|
|
393
|
+
const collapsedPrefixes = [];
|
|
252
394
|
scene.nodes.forEach((node, index) => {
|
|
395
|
+
if (collapsedPrefixes.some((prefix) =>
|
|
396
|
+
node.sourcePath?.startsWith(prefix))) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const stadium = stadiumMapping(scene, node, index, normalizedOptions);
|
|
400
|
+
if (stadium) {
|
|
401
|
+
if (stadium.fallback) fallbacks.push(stadium.fallback);
|
|
402
|
+
else elements.push(stadium.element);
|
|
403
|
+
collapsedPrefixes.push(stadium.prefix);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
253
406
|
const mapped = mappedNode(node, index, normalizedOptions);
|
|
254
407
|
if (!mapped) return;
|
|
255
408
|
if (mapped.reason !== undefined && !["shape", "text", "image", "connector"].includes(mapped.type)) {
|