@markdstage/markdstage 2.5.0 → 2.5.1
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/package.json +1 -1
- package/shared/renderer/renderer.js +85 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdstage/markdstage",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Present, validate, inspect, capture, and export MarkdStage Markdown decks from the command line — no Copilot canvas required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "runceel",
|
|
@@ -940,6 +940,60 @@ function textContentBounds(element, deck) {
|
|
|
940
940
|
};
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
function textInsetsFor(element) {
|
|
944
|
+
const style = getComputedStyle(element);
|
|
945
|
+
const metric = (padding, border) => {
|
|
946
|
+
const paddingValue = Number.parseFloat(style[padding]);
|
|
947
|
+
const borderValue = Number.parseFloat(style[border]);
|
|
948
|
+
return roundedMetric(
|
|
949
|
+
Math.max(0, Number.isFinite(paddingValue) ? paddingValue : 0) +
|
|
950
|
+
Math.max(0, Number.isFinite(borderValue) ? borderValue : 0),
|
|
951
|
+
);
|
|
952
|
+
};
|
|
953
|
+
const insets = {
|
|
954
|
+
left: metric("paddingLeft", "borderLeftWidth"),
|
|
955
|
+
top: metric("paddingTop", "borderTopWidth"),
|
|
956
|
+
right: metric("paddingRight", "borderRightWidth"),
|
|
957
|
+
bottom: metric("paddingBottom", "borderBottomWidth"),
|
|
958
|
+
};
|
|
959
|
+
return Object.values(insets).some((value) => value > 0) ? insets : null;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function singleLineTextLayout(element, deck, bounds, textInsets, alignment) {
|
|
963
|
+
const content = textContentBounds(element, deck);
|
|
964
|
+
const fontSize = Number.parseFloat(getComputedStyle(element).fontSize);
|
|
965
|
+
const widthBuffer = Math.max(2, (Number.isFinite(fontSize) ? fontSize : 0) * 0.5);
|
|
966
|
+
const fittedInsets = textInsets ? { ...textInsets } : null;
|
|
967
|
+
const requiredWidth = () =>
|
|
968
|
+
content.width +
|
|
969
|
+
(fittedInsets?.left || 0) +
|
|
970
|
+
(fittedInsets?.right || 0) +
|
|
971
|
+
widthBuffer;
|
|
972
|
+
const resolvedAlignment =
|
|
973
|
+
alignment || pptxAlignment(getComputedStyle(element).textAlign);
|
|
974
|
+
// Keep the leading CSS inset, but release a trailing inset when browser and
|
|
975
|
+
// PowerPoint font metrics would otherwise force a visually single line to wrap.
|
|
976
|
+
if (fittedInsets && requiredWidth() > bounds.width) {
|
|
977
|
+
if (resolvedAlignment === "right") fittedInsets.left = 0;
|
|
978
|
+
if (resolvedAlignment === "left") fittedInsets.right = 0;
|
|
979
|
+
}
|
|
980
|
+
const extraWidth = Math.max(0, requiredWidth() - bounds.width);
|
|
981
|
+
const offset =
|
|
982
|
+
resolvedAlignment === "right"
|
|
983
|
+
? extraWidth
|
|
984
|
+
: resolvedAlignment === "center"
|
|
985
|
+
? extraWidth / 2
|
|
986
|
+
: 0;
|
|
987
|
+
return {
|
|
988
|
+
bounds: {
|
|
989
|
+
...bounds,
|
|
990
|
+
x: roundedMetric(bounds.x - offset),
|
|
991
|
+
width: roundedMetric(bounds.width + extraWidth),
|
|
992
|
+
},
|
|
993
|
+
textInsets: fittedInsets,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
|
|
943
997
|
function rasterImageSupported(image) {
|
|
944
998
|
const source = image.currentSrc || image.getAttribute("src") || "";
|
|
945
999
|
if (["cover", "none"].includes(getComputedStyle(image).objectFit)) return false;
|
|
@@ -1028,12 +1082,22 @@ function paragraphFor(element, options = {}) {
|
|
|
1028
1082
|
function renderedTextLineCount(element) {
|
|
1029
1083
|
const range = document.createRange();
|
|
1030
1084
|
range.selectNodeContents(element);
|
|
1031
|
-
const
|
|
1085
|
+
const lines = [];
|
|
1032
1086
|
for (const rect of range.getClientRects()) {
|
|
1033
1087
|
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
1034
|
-
|
|
1088
|
+
const line = lines.find(
|
|
1089
|
+
(candidate) =>
|
|
1090
|
+
rect.top < candidate.bottom - 1 &&
|
|
1091
|
+
rect.bottom > candidate.top + 1,
|
|
1092
|
+
);
|
|
1093
|
+
if (line) {
|
|
1094
|
+
line.top = Math.min(line.top, rect.top);
|
|
1095
|
+
line.bottom = Math.max(line.bottom, rect.bottom);
|
|
1096
|
+
} else {
|
|
1097
|
+
lines.push({ top: rect.top, bottom: rect.bottom });
|
|
1098
|
+
}
|
|
1035
1099
|
}
|
|
1036
|
-
return Math.max(1,
|
|
1100
|
+
return Math.max(1, lines.length);
|
|
1037
1101
|
}
|
|
1038
1102
|
|
|
1039
1103
|
function unsupportedEffects(element) {
|
|
@@ -1587,17 +1651,29 @@ async function collectPptxSlide(slide, index) {
|
|
|
1587
1651
|
: undefined,
|
|
1588
1652
|
});
|
|
1589
1653
|
if (!paragraph.runs.some((run) => run.text.trim())) continue;
|
|
1590
|
-
const disableTextWrap =
|
|
1591
|
-
|
|
1592
|
-
|
|
1654
|
+
const disableTextWrap = renderedTextLineCount(element) === 1;
|
|
1655
|
+
const textInsets = textInsetsFor(element);
|
|
1656
|
+
const baseBounds = element.classList.contains("kicker")
|
|
1657
|
+
? textContentBounds(element, deck)
|
|
1658
|
+
: relativeBounds(element, deck);
|
|
1659
|
+
const singleLineLayout = disableTextWrap
|
|
1660
|
+
? singleLineTextLayout(
|
|
1661
|
+
element,
|
|
1662
|
+
deck,
|
|
1663
|
+
baseBounds,
|
|
1664
|
+
textInsets,
|
|
1665
|
+
element.classList.contains("kicker") ? "left" : undefined,
|
|
1666
|
+
)
|
|
1667
|
+
: null;
|
|
1668
|
+
const bounds = singleLineLayout?.bounds || baseBounds;
|
|
1669
|
+
const fittedTextInsets = singleLineLayout?.textInsets || textInsets;
|
|
1593
1670
|
elements.push({
|
|
1594
1671
|
type: "text",
|
|
1595
1672
|
path: elementPath(element, deck),
|
|
1596
|
-
...
|
|
1597
|
-
? textContentBounds(element, deck)
|
|
1598
|
-
: relativeBounds(element, deck)),
|
|
1673
|
+
...bounds,
|
|
1599
1674
|
paragraphs: [paragraph],
|
|
1600
1675
|
opacity: Number(getComputedStyle(element).opacity) || 1,
|
|
1676
|
+
...(fittedTextInsets ? { textInsets: fittedTextInsets } : {}),
|
|
1601
1677
|
...(disableTextWrap ? { textWrap: "none" } : {}),
|
|
1602
1678
|
});
|
|
1603
1679
|
element.setAttribute("data-pptx-native", "text");
|