@markdstage/markdstage 3.3.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 +25 -13
- package/package.json +1 -1
- package/shared/README.md +47 -10
- 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 +260 -55
- 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 +35 -9
- 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 +410 -95
- 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 +101 -21
- package/src/commands/export.mjs +2 -0
- package/src/commands/present.mjs +133 -143
- package/src/deck.mjs +4 -0
- package/src/skills.mjs +12 -8
|
@@ -19,6 +19,9 @@ const NS_REL =
|
|
|
19
19
|
const JAPANESE_FONT_FACE = "Yu Gothic";
|
|
20
20
|
const HUNDREDTH_POINTS_PER_PIXEL = 75;
|
|
21
21
|
const SLIDE_LAYOUT_ID_BASE = 2147500000;
|
|
22
|
+
const DRAWINGML_ANGLE_UNITS_PER_DEGREE = 60000;
|
|
23
|
+
const DRAWINGML_HALF_TURN = 180 * DRAWINGML_ANGLE_UNITS_PER_DEGREE;
|
|
24
|
+
const DRAWINGML_FULL_TURN = 360 * DRAWINGML_ANGLE_UNITS_PER_DEGREE;
|
|
22
25
|
|
|
23
26
|
const REL = {
|
|
24
27
|
officeDocument: `${NS_R}/officeDocument`,
|
|
@@ -111,6 +114,33 @@ function optionalUnitInterval(value, path, fallback = 1) {
|
|
|
111
114
|
return number;
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
function optionalOwnUnitInterval(value, key, path, fallback = 1) {
|
|
118
|
+
if (!Object.hasOwn(value, key)) {
|
|
119
|
+
if (key in value) fail(`${path}.${key} must be an own property`);
|
|
120
|
+
return fallback;
|
|
121
|
+
}
|
|
122
|
+
return optionalUnitInterval(value[key], `${path}.${key}`, fallback);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function optionalOwnRotationUnits(value, path) {
|
|
126
|
+
if (!Object.hasOwn(value, "rotation")) {
|
|
127
|
+
if ("rotation" in value) fail(`${path}.rotation must be an own property`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
const rotation = finiteNumber(value.rotation, `${path}.rotation`);
|
|
131
|
+
let units = Math.round((((rotation % 360) + 360) % 360) * DRAWINGML_ANGLE_UNITS_PER_DEGREE);
|
|
132
|
+
if (units >= DRAWINGML_HALF_TURN) units -= DRAWINGML_FULL_TURN;
|
|
133
|
+
return units === 0 ? 0 : units;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function paintOpacities(element, path) {
|
|
137
|
+
return {
|
|
138
|
+
opacity: optionalOwnUnitInterval(element, "opacity", path),
|
|
139
|
+
fillOpacity: optionalOwnUnitInterval(element, "fillOpacity", path),
|
|
140
|
+
strokeOpacity: optionalOwnUnitInterval(element, "strokeOpacity", path),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
function boundsOf(value, path) {
|
|
115
145
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
116
146
|
fail(`${path} must be an object`);
|
|
@@ -127,8 +157,9 @@ function emu(value) {
|
|
|
127
157
|
return Math.round(value * PPTX_DIMENSIONS.emusPerPx);
|
|
128
158
|
}
|
|
129
159
|
|
|
130
|
-
function xfrmXml(bounds, tag = "a:xfrm") {
|
|
131
|
-
|
|
160
|
+
function xfrmXml(bounds, tag = "a:xfrm", rotationUnits = 0) {
|
|
161
|
+
const rotation = rotationUnits ? ` rot="${rotationUnits}"` : "";
|
|
162
|
+
return `<${tag}${rotation}><a:off x="${emu(bounds.x)}" y="${emu(bounds.y)}"/><a:ext cx="${emu(bounds.width)}" cy="${emu(bounds.height)}"/></${tag}>`;
|
|
132
163
|
}
|
|
133
164
|
|
|
134
165
|
function parseChannel(value, path) {
|
|
@@ -210,16 +241,25 @@ function colorXml(value, path, opacity = 1) {
|
|
|
210
241
|
return color ? `<a:solidFill>${color}</a:solidFill>` : "<a:noFill/>";
|
|
211
242
|
}
|
|
212
243
|
|
|
213
|
-
function
|
|
244
|
+
function lineCapXml(value, path) {
|
|
245
|
+
if (value === undefined) return "";
|
|
246
|
+
const caps = { butt: "flat", round: "rnd", square: "sq" };
|
|
247
|
+
if (typeof value !== "string" || !Object.hasOwn(caps, value)) fail(`${path} is not a supported line cap`);
|
|
248
|
+
return ` cap="${caps[value]}"`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function lineXml(element, path, opacities = paintOpacities(element, path)) {
|
|
214
252
|
const width = element.strokeWidth === undefined
|
|
215
253
|
? 1
|
|
216
254
|
: positiveNumber(element.strokeWidth, `${path}.strokeWidth`);
|
|
217
255
|
const color = colorOf(element.stroke, `${path}.stroke`);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const alpha = Math.round(
|
|
256
|
+
const cap = lineCapXml(element.lineCap, `${path}.lineCap`);
|
|
257
|
+
if (!color) return `<a:ln w="${emu(width)}"${cap}><a:noFill/></a:ln>`;
|
|
258
|
+
const alpha = Math.round(
|
|
259
|
+
color.alpha * opacities.opacity * opacities.strokeOpacity * 100000,
|
|
260
|
+
);
|
|
221
261
|
const dash = dashXml(element.dash, `${path}.dash`);
|
|
222
|
-
return `<a:ln w="${emu(width)}"><a:solidFill><a:srgbClr val="${color.hex}">${
|
|
262
|
+
return `<a:ln w="${emu(width)}"${cap}><a:solidFill><a:srgbClr val="${color.hex}">${
|
|
223
263
|
alpha < 100000 ? `<a:alpha val="${alpha}"/>` : ""
|
|
224
264
|
}</a:srgbClr></a:solidFill>${dash}</a:ln>`;
|
|
225
265
|
}
|
|
@@ -468,9 +508,9 @@ function runXml(run, path, relationships) {
|
|
|
468
508
|
.filter(Boolean)
|
|
469
509
|
.join(" ");
|
|
470
510
|
let properties = colorXml(
|
|
471
|
-
run.color
|
|
511
|
+
run.color === undefined ? "#000000" : run.color,
|
|
472
512
|
`${path}.color`,
|
|
473
|
-
|
|
513
|
+
optionalOwnUnitInterval(run, "opacity", path),
|
|
474
514
|
);
|
|
475
515
|
if (run.fontFace !== undefined) {
|
|
476
516
|
if (typeof run.fontFace !== "string" || !run.fontFace) {
|
|
@@ -548,12 +588,13 @@ function textBodyXml(
|
|
|
548
588
|
return `<${tag}>${textBodyPropertiesXml(bodyOptions, bodyPath)}<a:lstStyle/>${paragraphs}</${tag}>`;
|
|
549
589
|
}
|
|
550
590
|
|
|
551
|
-
function shapeBase(id, name, bounds, properties, text = "") {
|
|
552
|
-
return `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${xmlEscape(name)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr>${xfrmXml(bounds)}${properties}</p:spPr>${text}</p:sp>`;
|
|
591
|
+
function shapeBase(id, name, bounds, properties, text = "", rotationUnits = 0) {
|
|
592
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${xmlEscape(name)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr>${xfrmXml(bounds, "a:xfrm", rotationUnits)}${properties}</p:spPr>${text}</p:sp>`;
|
|
553
593
|
}
|
|
554
594
|
|
|
555
595
|
function textShapeXml(element, path, id, relationships) {
|
|
556
596
|
const bounds = boundsOf(element, path);
|
|
597
|
+
const rotationUnits = optionalOwnRotationUnits(element, path);
|
|
557
598
|
const text = { paragraphs: element.paragraphs };
|
|
558
599
|
const paragraphs = Array.isArray(text.paragraphs) ? text.paragraphs : [];
|
|
559
600
|
const bulletInsetPx = Math.max(
|
|
@@ -584,6 +625,7 @@ function textShapeXml(element, path, id, relationships) {
|
|
|
584
625
|
path,
|
|
585
626
|
bulletInsetPx,
|
|
586
627
|
),
|
|
628
|
+
rotationUnits,
|
|
587
629
|
);
|
|
588
630
|
}
|
|
589
631
|
|
|
@@ -604,7 +646,8 @@ function shapeTextOf(element, path) {
|
|
|
604
646
|
|
|
605
647
|
function nativeShapeXml(element, path, id, relationships) {
|
|
606
648
|
const bounds = boundsOf(element, path);
|
|
607
|
-
const
|
|
649
|
+
const rotationUnits = optionalOwnRotationUnits(element, path);
|
|
650
|
+
const presets = {
|
|
608
651
|
rect: "rect",
|
|
609
652
|
roundedRect: "roundRect",
|
|
610
653
|
ellipse: "ellipse",
|
|
@@ -612,18 +655,53 @@ function nativeShapeXml(element, path, id, relationships) {
|
|
|
612
655
|
triangle: "triangle",
|
|
613
656
|
hexagon: "hexagon",
|
|
614
657
|
parallelogram: "parallelogram",
|
|
615
|
-
}
|
|
616
|
-
|
|
658
|
+
};
|
|
659
|
+
const customGeometries = {
|
|
660
|
+
quarterHeightHexagon: '<a:custGeom><a:avLst/><a:gdLst><a:gd name="dx" fmla="*/ h 1 4"/><a:gd name="rx" fmla="+- w 0 dx"/><a:gd name="cy" fmla="*/ h 1 2"/></a:gdLst><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path><a:moveTo><a:pt x="dx" y="h"/></a:moveTo><a:lnTo><a:pt x="rx" y="h"/></a:lnTo><a:lnTo><a:pt x="w" y="cy"/></a:lnTo><a:lnTo><a:pt x="rx" y="0"/></a:lnTo><a:lnTo><a:pt x="dx" y="0"/></a:lnTo><a:lnTo><a:pt x="0" y="cy"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>',
|
|
661
|
+
sequenceTab: '<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path w="50000" h="20000"><a:moveTo><a:pt x="0" y="0"/></a:moveTo><a:lnTo><a:pt x="50000" y="0"/></a:lnTo><a:lnTo><a:pt x="50000" y="13000"/></a:lnTo><a:lnTo><a:pt x="41600" y="20000"/></a:lnTo><a:lnTo><a:pt x="0" y="20000"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>',
|
|
662
|
+
reverseParallelogram: '<a:custGeom><a:avLst/><a:gdLst><a:gd name="dx" fmla="*/ h 1 2"/><a:gd name="rx" fmla="+- w 0 dx"/></a:gdLst><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path><a:moveTo><a:pt x="dx" y="h"/></a:moveTo><a:lnTo><a:pt x="w" y="h"/></a:lnTo><a:lnTo><a:pt x="rx" y="0"/></a:lnTo><a:lnTo><a:pt x="0" y="0"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>',
|
|
663
|
+
trapezoid: '<a:custGeom><a:avLst/><a:gdLst><a:gd name="dx" fmla="*/ h 1 2"/><a:gd name="rx" fmla="+- w 0 dx"/></a:gdLst><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path><a:moveTo><a:pt x="0" y="h"/></a:moveTo><a:lnTo><a:pt x="w" y="h"/></a:lnTo><a:lnTo><a:pt x="rx" y="0"/></a:lnTo><a:lnTo><a:pt x="dx" y="0"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>',
|
|
664
|
+
invertedTrapezoid: '<a:custGeom><a:avLst/><a:gdLst><a:gd name="dx" fmla="*/ h 1 2"/><a:gd name="rx" fmla="+- w 0 dx"/></a:gdLst><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path><a:moveTo><a:pt x="dx" y="h"/></a:moveTo><a:lnTo><a:pt x="rx" y="h"/></a:lnTo><a:lnTo><a:pt x="w" y="0"/></a:lnTo><a:lnTo><a:pt x="0" y="0"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>',
|
|
665
|
+
};
|
|
666
|
+
const adjustedGeometries = {
|
|
667
|
+
stadium: '<a:prstGeom prst="roundRect"><a:avLst><a:gd name="adj" fmla="val 50000"/></a:avLst></a:prstGeom>',
|
|
668
|
+
};
|
|
669
|
+
if (element.shape === "topRoundedRect") {
|
|
670
|
+
const radius = Math.min(nonNegativeNumber(element.cornerRadius === undefined ? 5 : element.cornerRadius, `${path}.cornerRadius`),
|
|
671
|
+
bounds.width / 2, bounds.height / 2);
|
|
672
|
+
const r = Math.round(radius * 9525);
|
|
673
|
+
// This bounded preset retains Mermaid's two quadratic top corners and square bottom.
|
|
674
|
+
customGeometries.topRoundedRect = `<a:custGeom><a:avLst/><a:gdLst><a:gd name="r" fmla="val ${r}"/><a:gd name="rx" fmla="+- w 0 r"/></a:gdLst><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path><a:moveTo><a:pt x="0" y="h"/></a:moveTo><a:lnTo><a:pt x="0" y="r"/></a:lnTo><a:quadBezTo><a:pt x="0" y="0"/><a:pt x="r" y="0"/></a:quadBezTo><a:lnTo><a:pt x="rx" y="0"/></a:lnTo><a:quadBezTo><a:pt x="w" y="0"/><a:pt x="w" y="r"/></a:quadBezTo><a:lnTo><a:pt x="w" y="h"/></a:lnTo><a:close/></a:path></a:pathLst></a:custGeom>`;
|
|
675
|
+
}
|
|
676
|
+
if (element.shape === "roundedRect" && element.cornerRadius !== undefined) {
|
|
677
|
+
const radius = nonNegativeNumber(element.cornerRadius, `${path}.cornerRadius`);
|
|
678
|
+
const adjustment = Math.round(Math.min(50000,
|
|
679
|
+
radius / Math.min(bounds.width, bounds.height) * 100000));
|
|
680
|
+
adjustedGeometries.roundedRect =
|
|
681
|
+
`<a:prstGeom prst="roundRect"><a:avLst><a:gd name="adj" fmla="val ${adjustment}"/></a:avLst></a:prstGeom>`;
|
|
682
|
+
}
|
|
683
|
+
const shape = Object.hasOwn(element, "shape") ? element.shape : undefined;
|
|
684
|
+
const preset = typeof shape === "string" && Object.hasOwn(presets, shape) ? presets[shape] : "";
|
|
685
|
+
const customGeometry = typeof shape === "string" && Object.hasOwn(customGeometries, shape)
|
|
686
|
+
? customGeometries[shape]
|
|
687
|
+
: "";
|
|
688
|
+
const adjustedGeometry = typeof shape === "string" && Object.hasOwn(adjustedGeometries, shape)
|
|
689
|
+
? adjustedGeometries[shape]
|
|
690
|
+
: "";
|
|
691
|
+
const geometry = adjustedGeometry || customGeometry || (preset
|
|
692
|
+
? `<a:prstGeom prst="${preset}"><a:avLst/></a:prstGeom>`
|
|
693
|
+
: "");
|
|
694
|
+
if (!geometry) {
|
|
617
695
|
fail(
|
|
618
|
-
`${path}.shape must be rect, roundedRect, ellipse, diamond, triangle, hexagon, or
|
|
696
|
+
`${path}.shape must be rect, roundedRect, topRoundedRect, stadium, ellipse, diamond, triangle, hexagon, quarterHeightHexagon, parallelogram, reverseParallelogram, trapezoid, invertedTrapezoid, or sequenceTab`,
|
|
619
697
|
);
|
|
620
698
|
}
|
|
621
|
-
const
|
|
622
|
-
const properties = `${xfrmXml(bounds
|
|
699
|
+
const opacities = paintOpacities(element, path);
|
|
700
|
+
const properties = `${xfrmXml(bounds, "a:xfrm", rotationUnits)}${geometry}${colorXml(
|
|
623
701
|
element.fill,
|
|
624
702
|
`${path}.fill`,
|
|
625
|
-
opacity,
|
|
626
|
-
)}${lineXml(element, path)}`;
|
|
703
|
+
opacities.opacity * opacities.fillOpacity,
|
|
704
|
+
)}${lineXml(element, path, opacities)}`;
|
|
627
705
|
const shapeText = shapeTextOf(element, path);
|
|
628
706
|
if (!shapeText) textBodyPropertiesXml(element, path);
|
|
629
707
|
const text = shapeText
|
|
@@ -741,9 +819,12 @@ function connectorXml(element, path, nextId, relationships) {
|
|
|
741
819
|
: positiveNumber(element.strokeWidth, `${path}.strokeWidth`);
|
|
742
820
|
const color = colorOf(element.stroke ?? "#000000", `${path}.stroke`);
|
|
743
821
|
if (!color) fail(`${path}.stroke cannot be null`);
|
|
744
|
-
const
|
|
745
|
-
const alpha = Math.round(
|
|
822
|
+
const opacities = paintOpacities(element, path);
|
|
823
|
+
const alpha = Math.round(
|
|
824
|
+
color.alpha * opacities.opacity * opacities.strokeOpacity * 100000,
|
|
825
|
+
);
|
|
746
826
|
const dash = dashXml(element.dash, `${path}.dash`);
|
|
827
|
+
const cap = lineCapXml(element.lineCap, `${path}.lineCap`);
|
|
747
828
|
const shapes = [];
|
|
748
829
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
749
830
|
const start = points[index];
|
|
@@ -762,7 +843,7 @@ function connectorXml(element, path, nextId, relationships) {
|
|
|
762
843
|
? arrowXml(element.arrowEnd, `${path}.arrowEnd`)
|
|
763
844
|
: "";
|
|
764
845
|
shapes.push(
|
|
765
|
-
`<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="Connector ${id}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${emu(x)}" y="${emu(y)}"/><a:ext cx="${emu(Math.abs(end.x - start.x))}" cy="${emu(Math.abs(end.y - start.y))}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:ln w="${emu(width)}"><a:solidFill><a:srgbClr val="${color.hex}">${
|
|
846
|
+
`<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="Connector ${id}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${emu(x)}" y="${emu(y)}"/><a:ext cx="${emu(Math.abs(end.x - start.x))}" cy="${emu(Math.abs(end.y - start.y))}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:ln w="${emu(width)}"${cap}><a:solidFill><a:srgbClr val="${color.hex}">${
|
|
766
847
|
alpha < 100000 ? `<a:alpha val="${alpha}"/>` : ""
|
|
767
848
|
}</a:srgbClr></a:solidFill>${dash}${head}${tail}</a:ln></p:spPr></p:sp>`,
|
|
768
849
|
);
|