@stll/folio-core 0.29.0 → 0.30.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/dist/controller/folioEditor.d.ts +28 -1
- package/dist/controller/folioEditor.js +37 -26
- package/dist/controller/fontReadiness.js +28 -24
- package/dist/controller/hiddenEditorManager.d.ts +2 -7
- package/dist/controller/layoutPipeline.js +33 -3
- package/dist/controller/noteEditorManager.d.ts +3 -6
- package/dist/docx/metafileSvg.d.ts +12 -0
- package/dist/docx/metafileSvg.js +462 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
- package/dist/docx/parser.d.ts +2 -2
- package/dist/docx/parser.js +26 -0
- package/dist/docx/runParser.js +2 -1
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/styleParser.js +2 -1
- package/dist/docx/textBoxParser.js +14 -0
- package/dist/docx/vmlImageParser.js +72 -152
- package/dist/docx/vmlPreview.d.ts +31 -0
- package/dist/docx/vmlPreview.js +535 -0
- package/dist/docx/wrapTypes.d.ts +2 -36
- package/dist/docx/wrapTypes.js +1 -39
- package/dist/fonts/fontAlternates.d.ts +8 -0
- package/dist/fonts/fontAlternates.js +16 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +78 -28
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
- package/dist/layout-bridge/engine/selectionRects.js +3 -1
- package/dist/layout-engine/index.js +65 -32
- package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
- package/dist/layout-engine/justifiedLineFit.js +6 -0
- package/dist/layout-engine/keep-together.js +15 -10
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
- package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
- package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
- package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
- package/dist/layout-engine/measure/measureBlocks.js +197 -30
- package/dist/layout-engine/measure/measureContainer.js +20 -24
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +14 -8
- package/dist/layout-engine/measure/measureParagraph.js +111 -37
- package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
- package/dist/layout-engine/measure/tabCalculator.js +144 -0
- package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
- package/dist/layout-engine/measure/tableCellFlow.js +45 -8
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
- package/dist/layout-engine/paginator.d.ts +26 -2
- package/dist/layout-engine/paginator.js +76 -14
- package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
- package/dist/layout-engine/paragraphSpacing.js +4 -3
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
- package/dist/layout-engine/section-breaks.d.ts +3 -1
- package/dist/layout-engine/section-breaks.js +6 -2
- package/dist/layout-engine/types.d.ts +18 -1
- package/dist/layout-engine/types.js +1 -1
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +169 -82
- package/dist/layout-painter/renderTable.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +18 -7
- package/dist/layout-painter/renderTextBox.d.ts +1 -1
- package/dist/layout-painter/renderTextBox.js +6 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
- package/dist/paged-layout/sectionBlockWidths.js +103 -14
- package/dist/prosemirror/attrs/index.js +12 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +6 -2
- package/dist/prosemirror/conversion/toProseDoc.js +10 -3
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
- package/dist/prosemirror/schema/nodes.d.ts +4 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
- package/dist/prosemirror/utils/tabCalculator.js +1 -140
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
- package/dist/types/documentEnumValues.d.ts +4 -1
- package/dist/types/documentEnumValues.js +12 -1
- package/dist/types/editor-story.d.ts +8 -0
- package/dist/types/editor-story.js +0 -0
- package/dist/types/remote-selection.d.ts +11 -0
- package/dist/types/remote-selection.js +0 -0
- package/dist/types/wrap.d.ts +36 -0
- package/dist/types/wrap.js +40 -0
- package/dist/utils/fontResolver.d.ts +1 -1
- package/dist/utils/fontResolver.js +42 -2
- package/dist/utils/formatToStyle.js +4 -2
- package/dist/utils/horizontalScale.d.ts +28 -0
- package/dist/utils/horizontalScale.js +42 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
|
|
2
2
|
import { isValidHexColor } from "../../utils/colorResolver.js";
|
|
3
|
+
import { roundHorizontalScalePercentForSerialization } from "../../utils/horizontalScale.js";
|
|
3
4
|
import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
|
|
4
5
|
import { requiresXmlSpacePreserve } from "../textWhitespace.js";
|
|
5
6
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
@@ -127,7 +128,8 @@ function serializeTextFormatting(formatting) {
|
|
|
127
128
|
const colorXml = serializeColorElement(formatting.color);
|
|
128
129
|
if (colorXml) parts.push(colorXml);
|
|
129
130
|
if (formatting.spacing !== void 0) parts.push(`<w:spacing w:val="${intAttr(formatting.spacing)}"/>`);
|
|
130
|
-
|
|
131
|
+
const horizontalScale = roundHorizontalScalePercentForSerialization(formatting.scale);
|
|
132
|
+
if (horizontalScale !== void 0) parts.push(`<w:w w:val="${intAttr(horizontalScale)}"/>`);
|
|
131
133
|
if (formatting.kerning !== void 0) parts.push(`<w:kern w:val="${intAttr(formatting.kerning)}"/>`);
|
|
132
134
|
if (formatting.position !== void 0) parts.push(`<w:position w:val="${intAttr(formatting.position)}"/>`);
|
|
133
135
|
if (formatting.fontSize !== void 0) parts.push(`<w:sz w:val="${intAttr(formatting.fontSize)}"/>`);
|
|
@@ -448,6 +450,16 @@ function serializeDrawingContent(content) {
|
|
|
448
450
|
function serializeShapeTextBody(blocks) {
|
|
449
451
|
return blocks.map((block) => block.type === "paragraph" ? serializeParagraph(block) : serializeTable(block, serializeParagraph)).join("");
|
|
450
452
|
}
|
|
453
|
+
const serializeTextBodyAnchor = (anchor) => {
|
|
454
|
+
switch (anchor) {
|
|
455
|
+
case "top": return "t";
|
|
456
|
+
case "middle": return "ctr";
|
|
457
|
+
case "bottom": return "b";
|
|
458
|
+
case "distributed": return "dist";
|
|
459
|
+
case "justified": return "just";
|
|
460
|
+
default: return anchor;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
451
463
|
function serializeGeometryAdjustments(shape) {
|
|
452
464
|
if (!shape.geometryAdjustments || shape.geometryAdjustments.length === 0) return "<a:avLst/>";
|
|
453
465
|
return `<a:avLst>${shape.geometryAdjustments.map(({ name, formula }) => `<a:gd name="${escapeXml(name)}" fmla="${escapeXml(formula)}"/>`).join("")}</a:avLst>`;
|
|
@@ -484,7 +496,7 @@ function serializeShapeContent(content) {
|
|
|
484
496
|
const tb = shape.textBody;
|
|
485
497
|
const bpAttrs = ["rot=\"0\"", "vert=\"horz\""];
|
|
486
498
|
if (tb.textWrap) bpAttrs.push(`wrap="${tb.textWrap}"`);
|
|
487
|
-
if (tb.anchor) bpAttrs.push(`anchor="${tb.anchor
|
|
499
|
+
if (tb.anchor) bpAttrs.push(`anchor="${serializeTextBodyAnchor(tb.anchor)}"`);
|
|
488
500
|
if (tb.anchorCenter) bpAttrs.push("anchorCtr=\"1\"");
|
|
489
501
|
if (tb.margins) {
|
|
490
502
|
if (tb.margins.left != null) bpAttrs.push(`lIns="${intAttr(tb.margins.left)}"`);
|
package/dist/docx/styleParser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isValidHexColor } from "../utils/colorResolver.js";
|
|
2
|
+
import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
|
|
2
3
|
import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
|
|
3
4
|
import { mergeStyleTextFormatting } from "../utils/textFormattingMerge.js";
|
|
4
5
|
import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
@@ -137,7 +138,7 @@ function parseRunProperties(rPr, theme) {
|
|
|
137
138
|
}
|
|
138
139
|
const w = findChild(rPr, "w", "w");
|
|
139
140
|
if (w) {
|
|
140
|
-
const val =
|
|
141
|
+
const val = parseHorizontalScalePercent(getAttribute(w, "w", "val"));
|
|
141
142
|
if (val !== void 0) formatting.scale = val;
|
|
142
143
|
}
|
|
143
144
|
const kern = findChild(rPr, "w", "kern");
|
|
@@ -4,6 +4,16 @@ import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttri
|
|
|
4
4
|
//#region src/docx/textBoxParser.ts
|
|
5
5
|
/** Default text box margins in EMUs (0.1 inch) */
|
|
6
6
|
const DEFAULT_MARGIN_EMU = 91440;
|
|
7
|
+
const parseTextBoxVerticalAlign = (value) => {
|
|
8
|
+
switch (value) {
|
|
9
|
+
case "t": return "top";
|
|
10
|
+
case "ctr": return "middle";
|
|
11
|
+
case "b": return "bottom";
|
|
12
|
+
case "dist": return "distributed";
|
|
13
|
+
case "just": return "justified";
|
|
14
|
+
default: return;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
7
17
|
/**
|
|
8
18
|
* Parse text body properties from wps:bodyPr
|
|
9
19
|
* Returns margins/insets for the text box
|
|
@@ -16,6 +26,8 @@ function parseBodyProperties(bodyPr) {
|
|
|
16
26
|
if (findChildByLocalName(bodyPr, "spAutoFit")) result.autoFit = "shape";
|
|
17
27
|
else if (findChildByLocalName(bodyPr, "normAutofit")) result.autoFit = "normal";
|
|
18
28
|
else if (findChildByLocalName(bodyPr, "noAutofit")) result.autoFit = "none";
|
|
29
|
+
const verticalAlign = parseTextBoxVerticalAlign(getAttribute(bodyPr, null, "anchor"));
|
|
30
|
+
if (verticalAlign) result.verticalAlign = verticalAlign;
|
|
19
31
|
const lIns = parseNumericAttribute(bodyPr, null, "lIns");
|
|
20
32
|
const rIns = parseNumericAttribute(bodyPr, null, "rIns");
|
|
21
33
|
const tIns = parseNumericAttribute(bodyPr, null, "tIns");
|
|
@@ -131,6 +143,7 @@ function parseTextBox(drawingEl) {
|
|
|
131
143
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
132
144
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
133
145
|
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
146
|
+
if (bodyProps.verticalAlign) textBox.verticalAlign = bodyProps.verticalAlign;
|
|
134
147
|
if (isAnchor) {
|
|
135
148
|
const position = parseAnchorPosition(container);
|
|
136
149
|
if (position) textBox.position = position;
|
|
@@ -174,6 +187,7 @@ function parseTextBoxFromShape(wsp, size, position, wrap) {
|
|
|
174
187
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
175
188
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
176
189
|
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
190
|
+
if (bodyProps.verticalAlign) textBox.verticalAlign = bodyProps.verticalAlign;
|
|
177
191
|
if (position) textBox.position = position;
|
|
178
192
|
if (wrap) textBox.wrap = wrap;
|
|
179
193
|
return textBox;
|
|
@@ -1,113 +1,71 @@
|
|
|
1
1
|
import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
2
2
|
import { pixelsToEmu } from "../utils/units.js";
|
|
3
3
|
import { resolveImageData } from "./imageParser.js";
|
|
4
|
+
import { isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl } from "./vmlPreview.js";
|
|
4
5
|
import { isWatermarkShape } from "./watermarkParser.js";
|
|
5
|
-
import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild,
|
|
6
|
+
import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
|
|
6
7
|
//#region src/docx/vmlImageParser.ts
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"teal",
|
|
27
|
-
"aqua"
|
|
28
|
-
]);
|
|
29
|
-
/**
|
|
30
|
-
* Convert a CSS length (pt/in/px/cm/mm/pc, default px) to pixels. Units are
|
|
31
|
-
* matched case-insensitively (`2IN`, `2Pt` are valid VML). The numeric part
|
|
32
|
-
* must be a single well-formed decimal, so malformed input like `1.2.3` is
|
|
33
|
-
* rejected rather than truncated to `1.2`.
|
|
34
|
-
*/
|
|
35
|
-
function cssLengthToPx(raw) {
|
|
36
|
-
if (!raw) return;
|
|
37
|
-
const match = /^(?<amount>-?(?:\d+(?:\.\d+)?|\.\d+))\s*(?<unit>pt|in|px|cm|mm|pc)?$/iu.exec(raw.trim());
|
|
38
|
-
const amountText = match?.groups?.["amount"];
|
|
39
|
-
if (amountText === void 0) return;
|
|
40
|
-
const value = Number.parseFloat(amountText);
|
|
41
|
-
if (Number.isNaN(value)) return;
|
|
42
|
-
switch (match?.groups?.["unit"]?.toLowerCase()) {
|
|
43
|
-
case "pt": return value / 72 * 96;
|
|
44
|
-
case "in": return value * 96;
|
|
45
|
-
case "cm": return value / 2.54 * 96;
|
|
46
|
-
case "mm": return value / 25.4 * 96;
|
|
47
|
-
case "pc": return value / 6 * 96;
|
|
48
|
-
case "px":
|
|
49
|
-
case void 0: return value;
|
|
50
|
-
default: return;
|
|
8
|
+
const VML_POSITION_ABSOLUTE = "absolute";
|
|
9
|
+
const IMAGE_WRAP_INLINE = "inline";
|
|
10
|
+
const IMAGE_WRAP_BEHIND = "behind";
|
|
11
|
+
const IMAGE_WRAP_IN_FRONT = "inFront";
|
|
12
|
+
const pixelsToSafeEmu = (pixels) => {
|
|
13
|
+
const emu = pixelsToEmu(pixels);
|
|
14
|
+
return Number.isSafeInteger(emu) ? emu : void 0;
|
|
15
|
+
};
|
|
16
|
+
const horizontalRelativeTo = (value) => {
|
|
17
|
+
switch (value?.toLowerCase()) {
|
|
18
|
+
case "char": return "character";
|
|
19
|
+
case "text": return "column";
|
|
20
|
+
case "margin": return "margin";
|
|
21
|
+
case "page": return "page";
|
|
22
|
+
case "left-margin-area": return "leftMargin";
|
|
23
|
+
case "right-margin-area": return "rightMargin";
|
|
24
|
+
case "inner-margin-area": return "insideMargin";
|
|
25
|
+
case "outer-margin-area": return "outsideMargin";
|
|
26
|
+
default: return "character";
|
|
51
27
|
}
|
|
52
|
-
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (colon < 0) continue;
|
|
65
|
-
const key = decl.slice(0, colon).trim().toLowerCase();
|
|
66
|
-
const value = decl.slice(colon + 1).trim();
|
|
67
|
-
if (!key || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
68
|
-
out[key] = value;
|
|
28
|
+
};
|
|
29
|
+
const verticalRelativeTo = (value) => {
|
|
30
|
+
switch (value?.toLowerCase()) {
|
|
31
|
+
case "line": return "line";
|
|
32
|
+
case "text": return "paragraph";
|
|
33
|
+
case "margin": return "margin";
|
|
34
|
+
case "page": return "page";
|
|
35
|
+
case "top-margin-area": return "topMargin";
|
|
36
|
+
case "bottom-margin-area": return "bottomMargin";
|
|
37
|
+
case "inner-margin-area": return "insideMargin";
|
|
38
|
+
case "outer-margin-area": return "outsideMargin";
|
|
39
|
+
default: return "paragraph";
|
|
69
40
|
}
|
|
70
|
-
return out;
|
|
71
|
-
}
|
|
72
|
-
const finiteNumber = (raw) => {
|
|
73
|
-
if (!raw || !/^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(raw.trim())) return;
|
|
74
|
-
const value = Number.parseFloat(raw);
|
|
75
|
-
return Number.isFinite(value) ? value : void 0;
|
|
76
41
|
};
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
42
|
+
const vmlImageLayout = (style) => {
|
|
43
|
+
if (style["position"]?.toLowerCase() !== VML_POSITION_ABSOLUTE) return { wrap: { type: IMAGE_WRAP_INLINE } };
|
|
44
|
+
const leftPx = vmlCssLengthToPx(style["margin-left"] ?? style["left"]) ?? 0;
|
|
45
|
+
const topPx = vmlCssLengthToPx(style["margin-top"] ?? style["top"]) ?? 0;
|
|
46
|
+
const zIndex = parseVmlNumber(style["z-index"]);
|
|
47
|
+
return {
|
|
48
|
+
wrap: { type: zIndex !== void 0 && zIndex < 0 ? IMAGE_WRAP_BEHIND : IMAGE_WRAP_IN_FRONT },
|
|
49
|
+
position: {
|
|
50
|
+
horizontal: {
|
|
51
|
+
relativeTo: horizontalRelativeTo(style["mso-position-horizontal-relative"]),
|
|
52
|
+
posOffset: pixelsToSafeEmu(leftPx) ?? 0
|
|
53
|
+
},
|
|
54
|
+
vertical: {
|
|
55
|
+
relativeTo: verticalRelativeTo(style["mso-position-vertical-relative"]),
|
|
56
|
+
posOffset: pixelsToSafeEmu(topPx) ?? 0
|
|
57
|
+
}
|
|
58
|
+
}
|
|
85
59
|
};
|
|
86
60
|
};
|
|
87
|
-
const safeColor = (raw, fallback) => {
|
|
88
|
-
const normalized = raw?.trim().toLowerCase();
|
|
89
|
-
if (!normalized) return fallback;
|
|
90
|
-
if (SAFE_VML_COLORS.has(normalized)) return normalized;
|
|
91
|
-
if (/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/u.test(normalized)) return normalized;
|
|
92
|
-
return /^[0-9a-f]{6}$/u.test(normalized) ? `#${normalized}` : fallback;
|
|
93
|
-
};
|
|
94
|
-
const validPreviewDimension = (value) => value !== void 0 && value > 0 && value <= MAX_VML_PREVIEW_DIMENSION_PX;
|
|
95
|
-
const svgDataUrl = (svg) => svg.length <= MAX_VML_SVG_CHARACTERS ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` : void 0;
|
|
96
|
-
const vmlShapeSvg = (shape, width, height, x = 0, y = 0) => {
|
|
97
|
-
const fill = safeColor(getAttribute(shape, null, "fillcolor"), "white");
|
|
98
|
-
const stroke = getAttribute(shape, null, "stroked")?.toLowerCase() !== "f" ? safeColor(getAttribute(shape, null, "strokecolor"), "black") : "none";
|
|
99
|
-
const localName = getLocalName(shape.name ?? "");
|
|
100
|
-
if (localName === "oval") return `<ellipse cx="${x + width / 2}" cy="${y + height / 2}" rx="${width / 2}" ry="${height / 2}" fill="${fill}" stroke="${stroke}"/>`;
|
|
101
|
-
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${localName === "roundrect" ? Math.min(width, height) * .1 : 0}" fill="${fill}" stroke="${stroke}"/>`;
|
|
102
|
-
};
|
|
103
61
|
const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) => {
|
|
104
|
-
const src =
|
|
62
|
+
const src = vmlSvgDataUrl(svg);
|
|
105
63
|
if (!src) return null;
|
|
106
|
-
const leftPx =
|
|
107
|
-
const topPx =
|
|
64
|
+
const leftPx = vmlCssLengthToPx(style["margin-left"] ?? style["left"]) ?? 0;
|
|
65
|
+
const topPx = vmlCssLengthToPx(style["margin-top"] ?? style["top"]) ?? 0;
|
|
108
66
|
const horizontalRelative = style["mso-position-horizontal-relative"] === "page";
|
|
109
67
|
const verticalRelative = style["mso-position-vertical-relative"] === "page";
|
|
110
|
-
const zIndex =
|
|
68
|
+
const zIndex = parseVmlNumber(style["z-index"]);
|
|
111
69
|
return {
|
|
112
70
|
type: "drawing",
|
|
113
71
|
image: {
|
|
@@ -124,62 +82,24 @@ const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) =>
|
|
|
124
82
|
position: {
|
|
125
83
|
horizontal: {
|
|
126
84
|
relativeTo: horizontalRelative ? "page" : "character",
|
|
127
|
-
posOffset:
|
|
85
|
+
posOffset: pixelsToSafeEmu(leftPx) ?? 0
|
|
128
86
|
},
|
|
129
87
|
vertical: {
|
|
130
88
|
relativeTo: verticalRelative ? "page" : "paragraph",
|
|
131
|
-
posOffset:
|
|
89
|
+
posOffset: pixelsToSafeEmu(topPx) ?? 0
|
|
132
90
|
}
|
|
133
91
|
}
|
|
134
92
|
},
|
|
135
93
|
rawXml: elementToXml(cloneWithXmlnsDeclarations(pictElement, rootXmlns))
|
|
136
94
|
};
|
|
137
95
|
};
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return previewImage(pictElement, svg, widthPx, heightPx, style, rootXmlns);
|
|
146
|
-
};
|
|
147
|
-
const parseGroupPreview = (pictElement, group, rootXmlns) => {
|
|
148
|
-
const style = parseStyleAttr(getAttribute(group, null, "style"));
|
|
149
|
-
const widthPx = cssLengthToPx(style["width"]);
|
|
150
|
-
const heightPx = cssLengthToPx(style["height"]);
|
|
151
|
-
if (!validPreviewDimension(widthPx) || !validPreviewDimension(heightPx)) return null;
|
|
152
|
-
const origin = coordinatePair(getAttribute(group, null, "coordorigin")) ?? {
|
|
153
|
-
first: 0,
|
|
154
|
-
second: 0
|
|
155
|
-
};
|
|
156
|
-
const size = coordinatePair(getAttribute(group, null, "coordsize")) ?? {
|
|
157
|
-
first: widthPx,
|
|
158
|
-
second: heightPx
|
|
159
|
-
};
|
|
160
|
-
if (size.first <= 0 || size.second <= 0) return null;
|
|
161
|
-
const content = getChildElements(group).slice(0, MAX_VML_PREVIEW_SHAPES).map((child) => {
|
|
162
|
-
const localName = getLocalName(child.name ?? "");
|
|
163
|
-
if (findDeep(child, "v", "textbox")) return "";
|
|
164
|
-
if (localName === "line") {
|
|
165
|
-
const from = coordinatePair(getAttribute(child, null, "from"));
|
|
166
|
-
const to = coordinatePair(getAttribute(child, null, "to"));
|
|
167
|
-
if (!from || !to) return "";
|
|
168
|
-
const stroke = safeColor(getAttribute(child, null, "strokecolor"), "black");
|
|
169
|
-
return `<line x1="${from.first}" y1="${from.second}" x2="${to.first}" y2="${to.second}" stroke="${stroke}"/>`;
|
|
170
|
-
}
|
|
171
|
-
if (localName !== "rect" && localName !== "roundrect" && localName !== "oval") return "";
|
|
172
|
-
const childStyle = parseStyleAttr(getAttribute(child, null, "style"));
|
|
173
|
-
const x = finiteNumber(childStyle["left"]);
|
|
174
|
-
const y = finiteNumber(childStyle["top"]);
|
|
175
|
-
const width = finiteNumber(childStyle["width"]);
|
|
176
|
-
const height = finiteNumber(childStyle["height"]);
|
|
177
|
-
if (x === void 0 || y === void 0 || width === void 0 || height === void 0) return "";
|
|
178
|
-
return vmlShapeSvg(child, width, height, x, y);
|
|
179
|
-
}).join("");
|
|
180
|
-
if (!content) return null;
|
|
181
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${origin.first} ${origin.second} ${size.first} ${size.second}" width="${widthPx}" height="${heightPx}">${content}</svg>`;
|
|
182
|
-
return previewImage(pictElement, svg, widthPx, heightPx, style, rootXmlns);
|
|
96
|
+
const previewDrawing = (pictElement, preview, rootXmlns) => {
|
|
97
|
+
switch (preview.type) {
|
|
98
|
+
case "rendered": return previewImage(pictElement, preview.svg, preview.widthPx, preview.heightPx, preview.style, rootXmlns);
|
|
99
|
+
case "empty":
|
|
100
|
+
case "invalid": return null;
|
|
101
|
+
default: return preview;
|
|
102
|
+
}
|
|
183
103
|
};
|
|
184
104
|
/**
|
|
185
105
|
* Read the relationship id off a `v:imagedata` element. Word writes `r:id`;
|
|
@@ -212,18 +132,18 @@ function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
|
|
|
212
132
|
if (!rId) continue;
|
|
213
133
|
if (isWatermarkShape(shape)) continue;
|
|
214
134
|
const { src, mimeType, filename } = resolveImageData(rId, rels ?? void 0, media ?? void 0);
|
|
215
|
-
const shapeStyle =
|
|
216
|
-
const widthPx =
|
|
217
|
-
const heightPx =
|
|
218
|
-
if (getLocalName(pictElement.name ?? "") === "object" && (widthPx !== void 0 && !
|
|
135
|
+
const shapeStyle = parseVmlStyle(getAttribute(shape, null, "style"));
|
|
136
|
+
const widthPx = vmlCssLengthToPx(shapeStyle["width"]);
|
|
137
|
+
const heightPx = vmlCssLengthToPx(shapeStyle["height"]);
|
|
138
|
+
if (getLocalName(pictElement.name ?? "") === "object" && (widthPx !== void 0 && !isValidVmlPreviewDimension(widthPx) || heightPx !== void 0 && !isValidVmlPreviewDimension(heightPx))) continue;
|
|
219
139
|
const image = {
|
|
220
140
|
type: "image",
|
|
221
141
|
rId,
|
|
222
142
|
size: {
|
|
223
|
-
width: widthPx != null ?
|
|
224
|
-
height: heightPx != null ?
|
|
143
|
+
width: widthPx != null ? pixelsToSafeEmu(widthPx) ?? 0 : 0,
|
|
144
|
+
height: heightPx != null ? pixelsToSafeEmu(heightPx) ?? 0 : 0
|
|
225
145
|
},
|
|
226
|
-
|
|
146
|
+
...vmlImageLayout(shapeStyle)
|
|
227
147
|
};
|
|
228
148
|
const safeSrc = sanitizeImageSrc(src);
|
|
229
149
|
if (safeSrc) image.src = safeSrc;
|
|
@@ -240,12 +160,12 @@ function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
|
|
|
240
160
|
for (const child of getChildElements(pictElement)) {
|
|
241
161
|
const localName = getLocalName(child.name ?? "");
|
|
242
162
|
if (localName === "group") {
|
|
243
|
-
const preview =
|
|
163
|
+
const preview = previewDrawing(pictElement, renderVmlGroupPreview(child), rootXmlns);
|
|
244
164
|
if (preview) return preview;
|
|
245
165
|
continue;
|
|
246
166
|
}
|
|
247
167
|
if (localName === "rect" || localName === "roundrect" || localName === "oval") {
|
|
248
|
-
const preview =
|
|
168
|
+
const preview = previewDrawing(pictElement, renderStandaloneVmlPreview(child), rootXmlns);
|
|
249
169
|
if (preview) return preview;
|
|
250
170
|
}
|
|
251
171
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { XmlElement } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/vmlPreview.d.ts
|
|
3
|
+
type VmlPreviewResult = {
|
|
4
|
+
type: "rendered";
|
|
5
|
+
svg: string;
|
|
6
|
+
widthPx: number;
|
|
7
|
+
heightPx: number;
|
|
8
|
+
style: Record<string, string>;
|
|
9
|
+
} | {
|
|
10
|
+
type: "empty";
|
|
11
|
+
} | {
|
|
12
|
+
type: "invalid";
|
|
13
|
+
};
|
|
14
|
+
/** Parse a VML/CSS length into CSS pixels without accepting partial numbers. */
|
|
15
|
+
declare const vmlCssLengthToPx: (raw: string | undefined) => number | undefined;
|
|
16
|
+
/** Read a VML `style` attribute into a prototype-free lowercased record. */
|
|
17
|
+
declare const parseVmlStyle: (style: string | null) => Record<string, string>;
|
|
18
|
+
/** Parse a finite VML number without exponent or partial-number coercion. */
|
|
19
|
+
declare const parseVmlNumber: (raw: string | null | undefined) => number | undefined;
|
|
20
|
+
/** Check a physical preview dimension against the retained SVG layout bound. */
|
|
21
|
+
declare const isValidVmlPreviewDimension: (value: number | undefined) => value is number;
|
|
22
|
+
/** Encode a generated, already-sanitized SVG without exceeding its output cap. */
|
|
23
|
+
declare const vmlSvgDataUrl: (svg: string) => string | undefined;
|
|
24
|
+
/** Render a bounded standalone VML rectangle or oval preview. */
|
|
25
|
+
declare const renderStandaloneVmlPreview: (shape: XmlElement) => VmlPreviewResult;
|
|
26
|
+
/** Render a VML group through bounded, non-clipping local-coordinate transforms. */
|
|
27
|
+
declare const renderVmlGroupPreview: (group: XmlElement) => VmlPreviewResult;
|
|
28
|
+
/** Bound retained synthetic VML previews while preserving their raw replay nodes. */
|
|
29
|
+
declare const enforcePackageVmlPreviewBudget: (root: unknown, maxCharacters?: number) => void;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { VmlPreviewResult, enforcePackageVmlPreviewBudget, isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl };
|