reamkit 1.15.4 → 1.17.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/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +659 -7
- package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
- package/dist/esm/core/drawingml/chart-geometry.js +424 -92
- package/dist/esm/core/drawingml/chart-parser.js +301 -11
- package/dist/esm/core/drawingml/colors.d.ts +7 -7
- package/dist/esm/core/drawingml/colors.js +84 -18
- package/dist/esm/core/drawingml/preset-geometry.js +240 -6
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
- package/dist/esm/core/drawingml/theme-parser.js +92 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +353 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/ir/sheet.d.ts +61 -0
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/vector.d.ts +30 -0
- package/dist/esm/excel/activex-parser.js +1 -0
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +899 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +553 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +150 -61
- package/dist/esm/excel/xlsx-reader.js +242 -11
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +29 -6
- package/dist/esm/layout/page-doc.d.ts +87 -1
- package/dist/esm/layout/styled-layout.d.ts +46 -2
- package/dist/esm/layout/styled-layout.js +2345 -274
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +355 -49
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +31 -2
- package/dist/esm/pdf/vector-graphics.js +103 -3
- package/dist/esm/pptx/pptx-reader.js +2 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +98 -24
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +538 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +86 -5
- package/dist/esm/word/drawing-parser.js +1590 -54
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +108 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +32 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +9 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { emuToPt, pt } from "../core/ir/units.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { buildStroke } from "../core/drawingml/shape-render.js";
|
|
3
|
+
import { poAttr, poAttrLocal, poChildren, poFindDescendant, poIntAttr, poIs, poIsLocal, poTag, poText } from "../core/po-helpers.js";
|
|
4
|
+
import { applyColorMods, readColorMods, resolveColorNode } from "../core/drawingml/colors.js";
|
|
4
5
|
//#region src/word/drawing-parser.ts
|
|
5
6
|
var WPS_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
var DRAWINGML_URI_BASES = ["http://schemas.openxmlformats.org/drawingml/2006/", "http://purl.oclc.org/ooxml/drawingml/"];
|
|
8
|
+
function isDrawingMlUri(uri, name) {
|
|
9
|
+
return uri !== void 0 && DRAWINGML_URI_BASES.some((base) => uri === base + name);
|
|
10
|
+
}
|
|
11
|
+
var WPG_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
|
|
12
|
+
var WPC_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas";
|
|
13
|
+
var EMPTY_GEOMETRY = {
|
|
14
|
+
pathWidth: 0,
|
|
15
|
+
pathHeight: 0,
|
|
16
|
+
commands: []
|
|
17
|
+
};
|
|
18
|
+
var UNDERSTOOD_NS = new Set([
|
|
19
|
+
"wps",
|
|
20
|
+
"wpg",
|
|
21
|
+
"wpc"
|
|
22
|
+
]);
|
|
9
23
|
function parseFloatAnchor(anchor) {
|
|
10
24
|
if (!poIs(anchor, "wp:anchor")) return void 0;
|
|
11
25
|
const behindRaw = poAttr(anchor, "behindDoc");
|
|
@@ -15,20 +29,35 @@ function parseFloatAnchor(anchor) {
|
|
|
15
29
|
else if (poIs(child, "wp:wrapTight")) wrap = "tight";
|
|
16
30
|
else if (poIs(child, "wp:wrapThrough")) wrap = "through";
|
|
17
31
|
else if (poIs(child, "wp:wrapTopAndBottom")) wrap = "topAndBottom";
|
|
32
|
+
const zOrder = poIntAttr(anchor, "relativeHeight");
|
|
18
33
|
const posH = parseAnchorPos(anchor, "wp:positionH", [
|
|
19
34
|
"margin",
|
|
20
35
|
"page",
|
|
21
|
-
"column"
|
|
36
|
+
"column",
|
|
37
|
+
"leftMargin",
|
|
38
|
+
"rightMargin"
|
|
22
39
|
]);
|
|
23
40
|
const posV = parseAnchorPos(anchor, "wp:positionV", [
|
|
24
41
|
"margin",
|
|
25
42
|
"page",
|
|
26
43
|
"paragraph",
|
|
27
|
-
"line"
|
|
44
|
+
"line",
|
|
45
|
+
"topMargin",
|
|
46
|
+
"bottomMargin"
|
|
28
47
|
]);
|
|
48
|
+
const dist = (name) => emuToPt(poIntAttr(anchor, name) ?? 0);
|
|
49
|
+
const wrapDist = {
|
|
50
|
+
topPt: dist("distT"),
|
|
51
|
+
bottomPt: dist("distB"),
|
|
52
|
+
leftPt: dist("distL"),
|
|
53
|
+
rightPt: dist("distR")
|
|
54
|
+
};
|
|
55
|
+
const anyDist = Object.values(wrapDist).some((v) => v > 0);
|
|
29
56
|
return {
|
|
30
57
|
wrap,
|
|
31
58
|
...behind ? { behind: true } : {},
|
|
59
|
+
...zOrder !== void 0 ? { zOrder } : {},
|
|
60
|
+
...anyDist ? { wrapDist } : {},
|
|
32
61
|
...posH ? { posH } : {},
|
|
33
62
|
...posV ? { posV } : {}
|
|
34
63
|
};
|
|
@@ -38,8 +67,74 @@ var ANCHOR_ALIGNS = new Set([
|
|
|
38
67
|
"center",
|
|
39
68
|
"right"
|
|
40
69
|
]);
|
|
70
|
+
var ANCHOR_V_ALIGNS = new Set([
|
|
71
|
+
"top",
|
|
72
|
+
"center",
|
|
73
|
+
"bottom"
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* `wp14:sizeRelH` / `wp14:sizeRelV` — the drawing's width and height as
|
|
77
|
+
* hundredths of a percent of the page or the margins. Word writes the resolved
|
|
78
|
+
* extent beside them for readers that do not know the namespace, which is what
|
|
79
|
+
* we drew: dml-shape-relsize.docx asks for 40% of the margin and got the
|
|
80
|
+
* fallback's 90pt.
|
|
81
|
+
*
|
|
82
|
+
* @param anchor The `wp:anchor` node.
|
|
83
|
+
* @returns The relative size, or `undefined` when the anchor states none.
|
|
84
|
+
*/
|
|
85
|
+
function parseRelativeSize(anchor) {
|
|
86
|
+
const read = (tag, pctTag) => {
|
|
87
|
+
const node = expandMcChildren(poChildren(anchor)).find((c) => poIs(c, tag));
|
|
88
|
+
if (!node) return void 0;
|
|
89
|
+
const pctNode = poChildren(node).find((c) => poIs(c, pctTag));
|
|
90
|
+
const raw = pctNode ? Number(poText(pctNode).trim()) : NaN;
|
|
91
|
+
if (!Number.isFinite(raw) || raw <= 0) return void 0;
|
|
92
|
+
const from = poAttr(node, "relativeFrom") ?? "margin";
|
|
93
|
+
return {
|
|
94
|
+
pct: raw / 1e5,
|
|
95
|
+
from
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const h = read("wp14:sizeRelH", "wp14:pctWidth");
|
|
99
|
+
const v = read("wp14:sizeRelV", "wp14:pctHeight");
|
|
100
|
+
if (!h && !v) return void 0;
|
|
101
|
+
return {
|
|
102
|
+
...h ? {
|
|
103
|
+
widthPct: h.pct,
|
|
104
|
+
widthFrom: relFromH(h.from)
|
|
105
|
+
} : {},
|
|
106
|
+
...v ? {
|
|
107
|
+
heightPct: v.pct,
|
|
108
|
+
heightFrom: relFromV(v.from)
|
|
109
|
+
} : {}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function relFromH(raw) {
|
|
113
|
+
if (raw === "page") return "page";
|
|
114
|
+
if (raw === "leftMargin" || raw === "insideMargin") return "leftMargin";
|
|
115
|
+
if (raw === "rightMargin" || raw === "outsideMargin") return "rightMargin";
|
|
116
|
+
return "margin";
|
|
117
|
+
}
|
|
118
|
+
function relFromV(raw) {
|
|
119
|
+
if (raw === "page") return "page";
|
|
120
|
+
if (raw === "topMargin" || raw === "insideMargin") return "topMargin";
|
|
121
|
+
if (raw === "bottomMargin" || raw === "outsideMargin") return "bottomMargin";
|
|
122
|
+
return "margin";
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The first ELEMENT among a node's children. `poTag` gives a text node no tag
|
|
126
|
+
* at all, and a "not #text" test let the whitespace between pretty-printed
|
|
127
|
+
* elements win: dashed_line_custdash_percentage.docx indents its `a:lnRef`, so
|
|
128
|
+
* the colour inside it went unread and its blue rule came out black.
|
|
129
|
+
*
|
|
130
|
+
* @param node The parent node.
|
|
131
|
+
* @returns The first child that is an element, or `undefined`.
|
|
132
|
+
*/
|
|
133
|
+
function firstElementChild(node) {
|
|
134
|
+
return poChildren(node).find((c) => poTag(c) !== void 0);
|
|
135
|
+
}
|
|
41
136
|
function parseAnchorPos(anchor, tag, allowed) {
|
|
42
|
-
const pos = poChildren(anchor).find((c) => poIs(c, tag));
|
|
137
|
+
const pos = expandMcChildren(poChildren(anchor)).find((c) => poIs(c, tag));
|
|
43
138
|
if (!pos) return void 0;
|
|
44
139
|
const relRaw = poAttr(pos, "relativeFrom") ?? "margin";
|
|
45
140
|
const relativeFrom = allowed.includes(relRaw) ? relRaw : allowed[0];
|
|
@@ -50,7 +145,8 @@ function parseAnchorPos(anchor, tag, allowed) {
|
|
|
50
145
|
return {
|
|
51
146
|
relativeFrom,
|
|
52
147
|
...Number.isFinite(offsetRaw) ? { offsetPt: emuToPt(offsetRaw) } : {},
|
|
53
|
-
...tag === "wp:positionH" && ANCHOR_ALIGNS.has(alignRaw) ? { align: alignRaw } : {}
|
|
148
|
+
...tag === "wp:positionH" && ANCHOR_ALIGNS.has(alignRaw) ? { align: alignRaw } : {},
|
|
149
|
+
...tag === "wp:positionV" && ANCHOR_V_ALIGNS.has(alignRaw) ? { align: alignRaw } : {}
|
|
54
150
|
};
|
|
55
151
|
}
|
|
56
152
|
/**
|
|
@@ -97,7 +193,7 @@ function expandMcChildren(children) {
|
|
|
97
193
|
* @param parseBody Optional body parser for a shape's text box (omitted ⇒ no text).
|
|
98
194
|
* @returns The parsed content, or `null` when no anchor / recognizable graphic is found.
|
|
99
195
|
*/
|
|
100
|
-
function parseDrawing(drawing, resolveColor, parseBody) {
|
|
196
|
+
function parseDrawing(drawing, resolveColor, parseBody, resolveImage, resolveChartPart, themeLineWidths, themeStyles, parseDrawingText) {
|
|
101
197
|
const anchor = poChildren(drawing).find((c) => poIs(c, "wp:inline")) ?? poChildren(drawing).find((c) => poIs(c, "wp:anchor"));
|
|
102
198
|
if (!anchor) return null;
|
|
103
199
|
const extent = poFindDescendant(anchor, "wp:extent");
|
|
@@ -112,29 +208,78 @@ function parseDrawing(drawing, resolveColor, parseBody) {
|
|
|
112
208
|
...altText ? { altText } : {},
|
|
113
209
|
...float ? { float } : {}
|
|
114
210
|
};
|
|
211
|
+
const relativeSize = parseRelativeSize(anchor);
|
|
115
212
|
const graphicData = poFindDescendant(anchor, "a:graphicData");
|
|
116
213
|
const uri = graphicData ? poAttr(graphicData, "uri") : void 0;
|
|
117
214
|
if (graphicData && uri === WPS_URI) {
|
|
118
|
-
const data = parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody);
|
|
119
|
-
|
|
215
|
+
const data = parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
216
|
+
if (!data) return null;
|
|
217
|
+
return {
|
|
120
218
|
kind: "shape",
|
|
121
|
-
data
|
|
219
|
+
data: {
|
|
220
|
+
...data,
|
|
221
|
+
...relativeSize ? { relativeSize } : {}
|
|
222
|
+
},
|
|
122
223
|
...alt
|
|
123
|
-
}
|
|
224
|
+
};
|
|
124
225
|
}
|
|
125
|
-
if (graphicData && uri ===
|
|
226
|
+
if (graphicData && uri === WPG_URI) {
|
|
227
|
+
const data = parseWgp(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
228
|
+
if (!data) return null;
|
|
229
|
+
return {
|
|
230
|
+
kind: "shape",
|
|
231
|
+
data: {
|
|
232
|
+
...data,
|
|
233
|
+
...relativeSize ? { relativeSize } : {}
|
|
234
|
+
},
|
|
235
|
+
...alt
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (graphicData && isDrawingMlUri(uri, "lockedCanvas")) {
|
|
239
|
+
const data = parseLockedCanvas(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
240
|
+
if (!data) return null;
|
|
241
|
+
return {
|
|
242
|
+
kind: "shape",
|
|
243
|
+
data: {
|
|
244
|
+
...data,
|
|
245
|
+
...relativeSize ? { relativeSize } : {}
|
|
246
|
+
},
|
|
247
|
+
...alt
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (graphicData && uri === WPC_URI) {
|
|
251
|
+
const canvas = expandMcChildren(poChildren(graphicData)).find((c) => poIsLocal(c, "wpc"));
|
|
252
|
+
if (!canvas || extentCx === void 0 || extentCy === void 0) return null;
|
|
253
|
+
const children = groupChildren(canvas, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
254
|
+
return {
|
|
255
|
+
kind: "shape",
|
|
256
|
+
data: {
|
|
257
|
+
width: emuToPt(extentCx),
|
|
258
|
+
height: emuToPt(extentCy),
|
|
259
|
+
...children.length > 0 ? { children } : {},
|
|
260
|
+
geometry: {
|
|
261
|
+
kind: "custom",
|
|
262
|
+
custom: EMPTY_GEOMETRY
|
|
263
|
+
},
|
|
264
|
+
fill: { kind: "none" },
|
|
265
|
+
...relativeSize ? { relativeSize } : {}
|
|
266
|
+
},
|
|
267
|
+
...alt
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (graphicData && isDrawingMlUri(uri, "chart")) {
|
|
126
271
|
const cChart = poFindDescendant(graphicData, "c:chart");
|
|
127
272
|
const chartRelId = cChart ? poAttr(cChart, "id") : void 0;
|
|
128
273
|
if (chartRelId && extentCx !== void 0 && extentCy !== void 0) return {
|
|
129
274
|
kind: "chart",
|
|
130
|
-
chartRelId,
|
|
275
|
+
chartRelId: resolveChartPart?.(chartRelId) ?? chartRelId,
|
|
131
276
|
width: emuToPt(extentCx),
|
|
132
277
|
height: emuToPt(extentCy),
|
|
133
278
|
...alt
|
|
134
279
|
};
|
|
135
280
|
return null;
|
|
136
281
|
}
|
|
137
|
-
if (graphicData && uri
|
|
282
|
+
if (graphicData && isDrawingMlUri(uri, "diagram")) {
|
|
138
283
|
const relIds = poFindDescendant(graphicData, "dgm:relIds");
|
|
139
284
|
const dmRelId = relIds ? poAttr(relIds, "dm") : void 0;
|
|
140
285
|
if (dmRelId && extentCx !== void 0 && extentCy !== void 0) return {
|
|
@@ -148,13 +293,39 @@ function parseDrawing(drawing, resolveColor, parseBody) {
|
|
|
148
293
|
}
|
|
149
294
|
const blip = poFindDescendant(anchor, "a:blip");
|
|
150
295
|
const rId = blip ? poAttr(blip, "embed") : void 0;
|
|
151
|
-
if (extentCx !== void 0 && extentCy !== void 0 && rId)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
296
|
+
if (extentCx !== void 0 && extentCy !== void 0 && rId) {
|
|
297
|
+
const crop = parseSrcRect(poFindDescendant(anchor, "a:srcRect"));
|
|
298
|
+
const xfrm = poFindDescendant(anchor, "a:xfrm");
|
|
299
|
+
const rot = xfrm ? poIntAttr(xfrm, "rot") : void 0;
|
|
300
|
+
const on = (name) => xfrm !== void 0 && (poAttr(xfrm, name) === "1" || poAttr(xfrm, name) === "true");
|
|
301
|
+
const effect = poIs(anchor, "wp:inline") ? poChildren(anchor).find((c) => poIs(c, "wp:effectExtent")) : void 0;
|
|
302
|
+
const side = (name) => emuToPt(effect ? Math.max(0, poIntAttr(effect, name) ?? 0) : 0);
|
|
303
|
+
const box = {
|
|
304
|
+
leftPt: side("l"),
|
|
305
|
+
topPt: side("t"),
|
|
306
|
+
rightPt: side("r"),
|
|
307
|
+
bottomPt: side("b")
|
|
308
|
+
};
|
|
309
|
+
const effectExtent = Object.values(box).some((v) => v > 0) ? box : void 0;
|
|
310
|
+
const spPr = poFindDescendant(anchor, "pic:spPr");
|
|
311
|
+
const outline = spPr ? pictureOutline(parseLine(spPr, resolveColor)) : void 0;
|
|
312
|
+
const shadow = spPr ? parseShadow(spPr, resolveColor) : void 0;
|
|
313
|
+
return {
|
|
314
|
+
kind: "image",
|
|
315
|
+
imageId: rId,
|
|
316
|
+
width: emuToPt(extentCx),
|
|
317
|
+
height: emuToPt(extentCy),
|
|
318
|
+
...outline ? { outline } : {},
|
|
319
|
+
...shadow ? { shadow } : {},
|
|
320
|
+
...crop ? { crop } : {},
|
|
321
|
+
...rot ? { rotation60k: rot } : {},
|
|
322
|
+
...on("flipH") ? { flipH: true } : {},
|
|
323
|
+
...on("flipV") ? { flipV: true } : {},
|
|
324
|
+
...relativeSize ? { relativeSize } : {},
|
|
325
|
+
...effectExtent ? { effectExtent } : {},
|
|
326
|
+
...alt
|
|
327
|
+
};
|
|
328
|
+
}
|
|
158
329
|
return null;
|
|
159
330
|
}
|
|
160
331
|
/**
|
|
@@ -169,9 +340,9 @@ function parseDrawing(drawing, resolveColor, parseBody) {
|
|
|
169
340
|
* @param node The `w:pict` / `w:object` node.
|
|
170
341
|
* @returns The picture, or `null` when there is no embedded `v:imagedata` or no usable size.
|
|
171
342
|
*/
|
|
172
|
-
function parseVmlPicture(node) {
|
|
173
|
-
const imagedata = poFindDescendant(node, "v:imagedata");
|
|
174
|
-
if (!imagedata) return
|
|
343
|
+
function parseVmlPicture(node, parseBody) {
|
|
344
|
+
const imagedata = poChildren(node).some((c) => poIs(c, "v:group")) ? void 0 : poFindDescendant(node, "v:imagedata");
|
|
345
|
+
if (!imagedata) return parseVmlWordArt(node) ?? parseVmlShape(node, parseBody);
|
|
175
346
|
const imageId = poAttr(imagedata, "id");
|
|
176
347
|
if (imageId === void 0) return null;
|
|
177
348
|
const shape = poFindDescendant(node, "v:shape") ?? poFindDescendant(node, "v:rect") ?? poFindDescendant(node, "v:oval");
|
|
@@ -179,14 +350,754 @@ function parseVmlPicture(node) {
|
|
|
179
350
|
const height = vmlStyleLength(shape, "height");
|
|
180
351
|
if (width === void 0 || height === void 0) return null;
|
|
181
352
|
const altText = (shape ? poAttr(shape, "alt") : void 0)?.trim() || poAttr(imagedata, "title")?.trim() || void 0;
|
|
353
|
+
const float = shape ? vmlFloat(poAttr(shape, "style") ?? "", poIntAttr(shape, "z-index"), shape) : void 0;
|
|
354
|
+
const typeRef = shape ? poAttr(shape, "type")?.replace(/^#/u, "") : void 0;
|
|
355
|
+
const shapeType = typeRef !== void 0 ? vmlShapeTypes(node).get(typeRef) : void 0;
|
|
356
|
+
const stroked = shape ? poAttr(shape, "stroked") ?? (shapeType ? poAttr(shapeType, "stroked") : void 0) : void 0;
|
|
357
|
+
const outline = shape && (stroked === "t" || stroked === "true") ? pictureOutline(vmlLine(shape, shapeType)) : void 0;
|
|
182
358
|
return {
|
|
183
359
|
kind: "image",
|
|
184
360
|
imageId,
|
|
185
361
|
width,
|
|
186
362
|
height,
|
|
363
|
+
...outline ? { outline } : {},
|
|
364
|
+
...float ? { float } : {},
|
|
187
365
|
...altText ? { altText } : {}
|
|
188
366
|
};
|
|
189
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* §14.1.2 — a legacy VML shape drawn as the DrawingML one it corresponds to:
|
|
370
|
+
* `v:rect`, `v:roundrect`, `v:oval`, `v:line` and a `v:shape` we cannot resolve
|
|
371
|
+
* a path for (drawn as its box). 113 corpus documents still hold one — older
|
|
372
|
+
* Word wrote every drawing this way — and read only for its `v:imagedata`,
|
|
373
|
+
* every one of them lost the shape, its fill, its outline and the text in it:
|
|
374
|
+
* drawinglayer-pic-pos.docx frames its title in a `v:rect` and we printed the
|
|
375
|
+
* page without it.
|
|
376
|
+
*
|
|
377
|
+
* @param node The `w:pict` / `w:object` node.
|
|
378
|
+
* @param parseBody Body parser for a `v:textbox`'s content.
|
|
379
|
+
* @returns The shape, or `null` when nothing drawable is found.
|
|
380
|
+
*/
|
|
381
|
+
function parseVmlShape(node, parseBody) {
|
|
382
|
+
const shape = poChildren(node).find((c) => VML_SHAPE_TAGS.has(poTag(c) ?? ""));
|
|
383
|
+
if (!shape) return null;
|
|
384
|
+
if (poIs(shape, "v:group")) {
|
|
385
|
+
const data = parseVmlGroup(shape, parseBody, vmlShapeTypes(node));
|
|
386
|
+
if (!data) return null;
|
|
387
|
+
const groupFloat = vmlFloat(poAttr(shape, "style") ?? "", poIntAttr(shape, "z-index"), shape);
|
|
388
|
+
return {
|
|
389
|
+
kind: "shape",
|
|
390
|
+
data,
|
|
391
|
+
...groupFloat ? { float: groupFloat } : {},
|
|
392
|
+
...poAttr(shape, "alt")?.trim() ? { altText: poAttr(shape, "alt").trim() } : {}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const tag = poTag(shape) ?? "";
|
|
396
|
+
const style = poAttr(shape, "style") ?? "";
|
|
397
|
+
const width = vmlStyleLength(shape, "width");
|
|
398
|
+
const height = vmlStyleLength(shape, "height");
|
|
399
|
+
const from = tag === "v:line" ? vmlPoint(poAttr(shape, "from")) : void 0;
|
|
400
|
+
const to = tag === "v:line" ? vmlPoint(poAttr(shape, "to")) : void 0;
|
|
401
|
+
const boxW = width ?? (from && to ? Math.abs(to.x - from.x) : void 0);
|
|
402
|
+
const boxH = height ?? (from && to ? Math.abs(to.y - from.y) : void 0);
|
|
403
|
+
if (boxW === void 0 || boxH === void 0) return null;
|
|
404
|
+
const data = vmlShapeData(shape, tag, pt(Math.max(1, boxW)), pt(Math.max(1, boxH)), parseBody, vmlShapeTypes(node));
|
|
405
|
+
if (!data) return null;
|
|
406
|
+
const float = vmlFloat(style, poIntAttr(shape, "z-index"), shape);
|
|
407
|
+
return {
|
|
408
|
+
kind: "shape",
|
|
409
|
+
data,
|
|
410
|
+
...float ? { float } : {},
|
|
411
|
+
...poAttr(shape, "alt")?.trim() ? { altText: poAttr(shape, "alt").trim() } : {}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* One VML primitive as {@link ShapeData}: its preset geometry, fill, outline
|
|
416
|
+
* and the words in its text box.
|
|
417
|
+
*
|
|
418
|
+
* @param shape The `v:rect` / `v:oval` / … node.
|
|
419
|
+
* @param tag Its tag, which picks the preset.
|
|
420
|
+
* @param width The box's width, already in points.
|
|
421
|
+
* @param height Its height.
|
|
422
|
+
* @param parseBody Body parser for a `v:textbox`.
|
|
423
|
+
* @returns The shape, or `null` when it draws nothing and says nothing.
|
|
424
|
+
*/
|
|
425
|
+
/**
|
|
426
|
+
* §14.1.2.19 `v:shapetype` — the reusable shape definitions declared alongside
|
|
427
|
+
* the shapes that reference them by `@type="#id"`, keyed by id.
|
|
428
|
+
*
|
|
429
|
+
* @param node The `w:pict` / `w:object` node.
|
|
430
|
+
* @returns The shapetypes found anywhere within it.
|
|
431
|
+
*/
|
|
432
|
+
function vmlShapeTypes(node) {
|
|
433
|
+
const out = /* @__PURE__ */ new Map();
|
|
434
|
+
const walk = (n) => {
|
|
435
|
+
for (const c of poChildren(n)) {
|
|
436
|
+
if (poIs(c, "v:shapetype")) {
|
|
437
|
+
const id = poAttr(c, "id");
|
|
438
|
+
if (id !== void 0) out.set(id, c);
|
|
439
|
+
}
|
|
440
|
+
walk(c);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
walk(node);
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
function vmlShapeData(shape, tag, width, height, parseBody, shapeTypes) {
|
|
447
|
+
const typeRef = poAttr(shape, "type")?.replace(/^#/u, "");
|
|
448
|
+
const shapeType = typeRef !== void 0 ? shapeTypes?.get(typeRef) : void 0;
|
|
449
|
+
const fill = vmlFill(shape, shapeType);
|
|
450
|
+
const line = vmlLine(shape, shapeType);
|
|
451
|
+
const text = parseBody ? vmlTextBox(shape, parseBody) : void 0;
|
|
452
|
+
if (fill.kind === "none" && !line && !text) return null;
|
|
453
|
+
const rotationDeg = vmlStyleNumber(shape, "rotation");
|
|
454
|
+
const flip = vmlStyleValue(shape, "flip")?.toLowerCase() ?? "";
|
|
455
|
+
const flipH = flip.includes("x");
|
|
456
|
+
const flipV = flip.includes("y");
|
|
457
|
+
const spun = rotationDeg !== void 0 && rotationDeg % 360 !== 0 ? rotationDeg : void 0;
|
|
458
|
+
const transform = spun !== void 0 || flipH || flipV ? {
|
|
459
|
+
...spun !== void 0 ? { rotation60k: Math.round(spun * 6e4) } : {},
|
|
460
|
+
...flipH ? { flipH: true } : {},
|
|
461
|
+
...flipV ? { flipV: true } : {}
|
|
462
|
+
} : void 0;
|
|
463
|
+
const wordArt = vmlWordArtBody(shape, width, height);
|
|
464
|
+
if (wordArt) return {
|
|
465
|
+
width,
|
|
466
|
+
height,
|
|
467
|
+
geometry: {
|
|
468
|
+
kind: "preset",
|
|
469
|
+
preset: "rect",
|
|
470
|
+
adjust: /* @__PURE__ */ new Map()
|
|
471
|
+
},
|
|
472
|
+
fill: { kind: "none" },
|
|
473
|
+
text: wordArt,
|
|
474
|
+
...transform ? { transform } : {}
|
|
475
|
+
};
|
|
476
|
+
return {
|
|
477
|
+
width,
|
|
478
|
+
height,
|
|
479
|
+
geometry: vmlCustomGeometry(shape, shapeType) ?? {
|
|
480
|
+
kind: "preset",
|
|
481
|
+
preset: (shapeType && VML_SPT_PRESETS[poAttrLocal(shapeType, "spt") ?? ""]) ?? VML_PRESETS[tag] ?? "rect",
|
|
482
|
+
adjust: /* @__PURE__ */ new Map()
|
|
483
|
+
},
|
|
484
|
+
fill,
|
|
485
|
+
...line ? { line } : {},
|
|
486
|
+
...transform ? { transform } : {},
|
|
487
|
+
...text ? { text } : {}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* §14.1.4 `@path` — a VML shape's own outline, in the coordinate space its
|
|
492
|
+
* `@coordsize` declares (1000×1000 by default) with `@coordorigin` at the
|
|
493
|
+
* corner. Word writes freeform shapes this way, as plain numbers; a path that
|
|
494
|
+
* names a FORMULA (`@n`) or an arc command is left to the preset fallback.
|
|
495
|
+
*
|
|
496
|
+
* @param shape The `v:shape`/`v:rect`/… node.
|
|
497
|
+
* @param shapeType Its `v:shapetype`, which may carry the path instead.
|
|
498
|
+
* @returns The geometry, or `undefined` when there is no path we can read.
|
|
499
|
+
*/
|
|
500
|
+
function vmlCustomGeometry(shape, shapeType) {
|
|
501
|
+
const raw = poAttr(shape, "path") ?? (shapeType ? poAttr(shapeType, "path") : void 0);
|
|
502
|
+
if (raw === void 0 || raw === "" || raw.includes("@")) return void 0;
|
|
503
|
+
const size = poAttr(shape, "coordsize") ?? (shapeType ? poAttr(shapeType, "coordsize") : void 0);
|
|
504
|
+
const origin = poAttr(shape, "coordorigin") ?? (shapeType ? poAttr(shapeType, "coordorigin") : void 0);
|
|
505
|
+
const [sx, sy] = pairOf(size) ?? [1e3, 1e3];
|
|
506
|
+
const [ox, oy] = pairOf(origin) ?? [0, 0];
|
|
507
|
+
if (sx <= 0 || sy <= 0) return void 0;
|
|
508
|
+
const commands = vmlPathCommands(raw, ox, oy);
|
|
509
|
+
if (commands === void 0 || commands.length === 0) return void 0;
|
|
510
|
+
return {
|
|
511
|
+
kind: "custom",
|
|
512
|
+
custom: {
|
|
513
|
+
pathWidth: sx,
|
|
514
|
+
pathHeight: sy,
|
|
515
|
+
commands
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function pairOf(v) {
|
|
520
|
+
const m = v !== void 0 ? /^\s*(-?\d+)[ ,]+(-?\d+)\s*$/u.exec(v) : null;
|
|
521
|
+
return m ? [Number(m[1]), Number(m[2])] : void 0;
|
|
522
|
+
}
|
|
523
|
+
var VML_PATH_PAIRS = new Set([
|
|
524
|
+
"nf",
|
|
525
|
+
"ns",
|
|
526
|
+
"qx",
|
|
527
|
+
"qy",
|
|
528
|
+
"qb",
|
|
529
|
+
"at",
|
|
530
|
+
"wa",
|
|
531
|
+
"ar",
|
|
532
|
+
"ae"
|
|
533
|
+
]);
|
|
534
|
+
function vmlPathCommands(raw, originX, originY) {
|
|
535
|
+
const out = [];
|
|
536
|
+
let x = 0;
|
|
537
|
+
let y = 0;
|
|
538
|
+
let i = 0;
|
|
539
|
+
const isLetter = (c) => /[a-z]/iu.test(c);
|
|
540
|
+
while (i < raw.length) {
|
|
541
|
+
const c = raw[i];
|
|
542
|
+
if (!isLetter(c)) {
|
|
543
|
+
if (/[\s,]/u.test(c)) {
|
|
544
|
+
i++;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
let cmd = c.toLowerCase();
|
|
550
|
+
i++;
|
|
551
|
+
if (i < raw.length && isLetter(raw[i])) {
|
|
552
|
+
const pair = cmd + raw[i].toLowerCase();
|
|
553
|
+
if (VML_PATH_PAIRS.has(pair)) {
|
|
554
|
+
cmd = pair;
|
|
555
|
+
i++;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const start = i;
|
|
559
|
+
while (i < raw.length && !isLetter(raw[i])) i++;
|
|
560
|
+
const args = raw.slice(start, i);
|
|
561
|
+
if (cmd === "e" || cmd === "n" || cmd === "nf" || cmd === "ns") continue;
|
|
562
|
+
if (cmd === "x") {
|
|
563
|
+
out.push({ cmd: "close" });
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const nums = vmlPathNumbers(args);
|
|
567
|
+
if (nums === void 0) return void 0;
|
|
568
|
+
const rel = cmd === "t" || cmd === "r" || cmd === "v";
|
|
569
|
+
if (cmd === "m" || cmd === "l" || cmd === "t" || cmd === "r") {
|
|
570
|
+
if (nums.length < 2 || nums.length % 2 !== 0) return void 0;
|
|
571
|
+
for (let k = 0; k < nums.length; k += 2) {
|
|
572
|
+
x = rel ? x + nums[k] : nums[k];
|
|
573
|
+
y = rel ? y + nums[k + 1] : nums[k + 1];
|
|
574
|
+
const move = (cmd === "m" || cmd === "t") && k === 0;
|
|
575
|
+
out.push({
|
|
576
|
+
cmd: move ? "move" : "line",
|
|
577
|
+
x: x - originX,
|
|
578
|
+
y: y - originY
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (cmd === "c" || cmd === "v") {
|
|
584
|
+
if (nums.length < 6 || nums.length % 6 !== 0) return void 0;
|
|
585
|
+
for (let k = 0; k < nums.length; k += 6) {
|
|
586
|
+
const x1 = rel ? x + nums[k] : nums[k];
|
|
587
|
+
const y1 = rel ? y + nums[k + 1] : nums[k + 1];
|
|
588
|
+
const x2 = rel ? x + nums[k + 2] : nums[k + 2];
|
|
589
|
+
const y2 = rel ? y + nums[k + 3] : nums[k + 3];
|
|
590
|
+
x = rel ? x + nums[k + 4] : nums[k + 4];
|
|
591
|
+
y = rel ? y + nums[k + 5] : nums[k + 5];
|
|
592
|
+
out.push({
|
|
593
|
+
cmd: "cubic",
|
|
594
|
+
x1: x1 - originX,
|
|
595
|
+
y1: y1 - originY,
|
|
596
|
+
x2: x2 - originX,
|
|
597
|
+
y2: y2 - originY,
|
|
598
|
+
x: x - originX,
|
|
599
|
+
y: y - originY
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
}
|
|
608
|
+
function vmlPathNumbers(args) {
|
|
609
|
+
const fields = args.trim().split(/\s*,\s*|\s+/u);
|
|
610
|
+
const out = [];
|
|
611
|
+
for (const f of fields) {
|
|
612
|
+
if (f === "") {
|
|
613
|
+
out.push(0);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (!/^-?\d+$/u.test(f)) return void 0;
|
|
617
|
+
out.push(Number(f));
|
|
618
|
+
}
|
|
619
|
+
return out;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* §14.1.2.7 `v:group` — a VML group: its `@style` gives the box on the page,
|
|
623
|
+
* its `@coordsize`/`@coordorigin` the space its children are positioned in.
|
|
624
|
+
* dml-textshape.docx draws its whole diagram inside one, and read as a single
|
|
625
|
+
* shape it drew nothing at all.
|
|
626
|
+
*
|
|
627
|
+
* @param group The `v:group` node.
|
|
628
|
+
* @param parseBody Body parser for the members' text boxes.
|
|
629
|
+
* @returns The group as a shape with members, or `null` when it has no box.
|
|
630
|
+
*/
|
|
631
|
+
function parseVmlGroup(group, parseBody, shapeTypes, sizePt) {
|
|
632
|
+
const width = sizePt?.w ?? vmlStyleLength(group, "width");
|
|
633
|
+
const height = sizePt?.h ?? vmlStyleLength(group, "height");
|
|
634
|
+
if (width === void 0 || height === void 0) return null;
|
|
635
|
+
const size = vmlPair(poAttr(group, "coordsize")) ?? {
|
|
636
|
+
x: width,
|
|
637
|
+
y: height
|
|
638
|
+
};
|
|
639
|
+
const origin = vmlPair(poAttr(group, "coordorigin")) ?? {
|
|
640
|
+
x: 0,
|
|
641
|
+
y: 0
|
|
642
|
+
};
|
|
643
|
+
const sx = size.x > 0 ? width / size.x : 1;
|
|
644
|
+
const sy = size.y > 0 ? height / size.y : 1;
|
|
645
|
+
const children = [];
|
|
646
|
+
for (const child of poChildren(group)) {
|
|
647
|
+
const tag = poTag(child) ?? "";
|
|
648
|
+
if (!VML_SHAPE_TAGS.has(tag)) continue;
|
|
649
|
+
const ends = tag === "v:line" ? {
|
|
650
|
+
from: vmlPair(poAttr(child, "from")),
|
|
651
|
+
to: vmlPair(poAttr(child, "to"))
|
|
652
|
+
} : void 0;
|
|
653
|
+
const span = ends?.from && ends.to ? ends : void 0;
|
|
654
|
+
const left = span ? Math.min(span.from.x, span.to.x) : vmlStyleNumber(child, "left") ?? 0;
|
|
655
|
+
const top = span ? Math.min(span.from.y, span.to.y) : vmlStyleNumber(child, "top") ?? 0;
|
|
656
|
+
const w = span ? Math.abs(span.to.x - span.from.x) : vmlStyleNumber(child, "width");
|
|
657
|
+
const h = span ? Math.abs(span.to.y - span.from.y) : vmlStyleNumber(child, "height");
|
|
658
|
+
if (w === void 0 || h === void 0) continue;
|
|
659
|
+
const data = tag === "v:group" ? parseVmlGroup(child, parseBody, shapeTypes, {
|
|
660
|
+
w: Math.max(1, w * sx),
|
|
661
|
+
h: Math.max(1, h * sy)
|
|
662
|
+
}) : vmlShapeData(child, tag, pt(Math.max(1, w * sx)), pt(Math.max(1, h * sy)), parseBody, shapeTypes);
|
|
663
|
+
if (!data) continue;
|
|
664
|
+
children.push({
|
|
665
|
+
shape: {
|
|
666
|
+
...data,
|
|
667
|
+
paragraphProperties: {}
|
|
668
|
+
},
|
|
669
|
+
xPt: pt((left - origin.x) * sx),
|
|
670
|
+
yPt: pt((top - origin.y) * sy)
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
if (children.length === 0) return null;
|
|
674
|
+
return {
|
|
675
|
+
width: pt(width),
|
|
676
|
+
height: pt(height),
|
|
677
|
+
children,
|
|
678
|
+
geometry: {
|
|
679
|
+
kind: "custom",
|
|
680
|
+
custom: EMPTY_GEOMETRY
|
|
681
|
+
},
|
|
682
|
+
fill: { kind: "none" }
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
var VML_SHAPE_TAGS = new Set([
|
|
686
|
+
"v:rect",
|
|
687
|
+
"v:roundrect",
|
|
688
|
+
"v:oval",
|
|
689
|
+
"v:line",
|
|
690
|
+
"v:shape",
|
|
691
|
+
"v:group"
|
|
692
|
+
]);
|
|
693
|
+
/** A bare `x,y` attribute pair (`coordsize`, `coordorigin`). */
|
|
694
|
+
function vmlPair(raw) {
|
|
695
|
+
if (!raw) return void 0;
|
|
696
|
+
const [a, b] = raw.split(",");
|
|
697
|
+
const x = Number(a);
|
|
698
|
+
const y = Number(b);
|
|
699
|
+
return Number.isFinite(x) && Number.isFinite(y) ? {
|
|
700
|
+
x,
|
|
701
|
+
y
|
|
702
|
+
} : void 0;
|
|
703
|
+
}
|
|
704
|
+
/** A unitless `@style` number (a child inside a group is in coord units). */
|
|
705
|
+
/** §14.1.2.19 — a CSS-like `@style` property's raw value ("flip:xy" → "xy"). */
|
|
706
|
+
function vmlStyleValue(shape, prop) {
|
|
707
|
+
const style = poAttr(shape, "style");
|
|
708
|
+
if (style === void 0) return void 0;
|
|
709
|
+
return new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "iu").exec(style)?.[1]?.trim();
|
|
710
|
+
}
|
|
711
|
+
function vmlStyleNumber(shape, prop) {
|
|
712
|
+
const style = poAttr(shape, "style");
|
|
713
|
+
if (style === void 0) return void 0;
|
|
714
|
+
const m = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*(-?[0-9.]+)`, "iu").exec(style);
|
|
715
|
+
if (!m) return void 0;
|
|
716
|
+
const v = Number.parseFloat(m[1]);
|
|
717
|
+
return Number.isFinite(v) ? v : void 0;
|
|
718
|
+
}
|
|
719
|
+
var VML_SPT_PRESETS = {
|
|
720
|
+
"1": "rect",
|
|
721
|
+
"2": "roundRect",
|
|
722
|
+
"3": "ellipse",
|
|
723
|
+
"4": "diamond",
|
|
724
|
+
"5": "triangle",
|
|
725
|
+
"6": "rtTriangle",
|
|
726
|
+
"7": "parallelogram",
|
|
727
|
+
"8": "trapezoid",
|
|
728
|
+
"9": "hexagon",
|
|
729
|
+
"12": "star5",
|
|
730
|
+
"13": "rightArrow",
|
|
731
|
+
"20": "line",
|
|
732
|
+
"32": "line",
|
|
733
|
+
"33": "bentConnector2",
|
|
734
|
+
"34": "bentConnector3",
|
|
735
|
+
"56": "pentagon",
|
|
736
|
+
"58": "star8",
|
|
737
|
+
"59": "star16",
|
|
738
|
+
"60": "star32",
|
|
739
|
+
"66": "leftArrow",
|
|
740
|
+
"67": "downArrow",
|
|
741
|
+
"68": "upArrow"
|
|
742
|
+
};
|
|
743
|
+
var VML_PRESETS = {
|
|
744
|
+
"v:rect": "rect",
|
|
745
|
+
"v:roundrect": "roundRect",
|
|
746
|
+
"v:oval": "ellipse",
|
|
747
|
+
"v:line": "line",
|
|
748
|
+
"v:shape": "rect"
|
|
749
|
+
};
|
|
750
|
+
/** A VML `from`/`to` point ("0,0" or "10pt,20pt"), in points. */
|
|
751
|
+
function vmlPoint(raw) {
|
|
752
|
+
if (!raw) return void 0;
|
|
753
|
+
const parts = raw.split(",");
|
|
754
|
+
if (parts.length !== 2) return void 0;
|
|
755
|
+
const num = (t) => {
|
|
756
|
+
const m = /(-?[0-9.]+)\s*(pt|in|px|cm|mm|pc)?/iu.exec(t.trim());
|
|
757
|
+
if (!m) return NaN;
|
|
758
|
+
return Number.parseFloat(m[1]) * (VML_UNIT_TO_PT[(m[2] ?? "px").toLowerCase()] ?? 1);
|
|
759
|
+
};
|
|
760
|
+
const x = num(parts[0]);
|
|
761
|
+
const y = num(parts[1]);
|
|
762
|
+
return Number.isFinite(x) && Number.isFinite(y) ? {
|
|
763
|
+
x,
|
|
764
|
+
y
|
|
765
|
+
} : void 0;
|
|
766
|
+
}
|
|
767
|
+
/** §14.1.2.11 — a VML colour: `#rrggbb`, a named colour, or a system one. */
|
|
768
|
+
function vmlColor(raw) {
|
|
769
|
+
if (!raw) return void 0;
|
|
770
|
+
const v = raw.trim().replace(/^#/u, "").split(" ")[0] ?? "";
|
|
771
|
+
if (/^[0-9A-Fa-f]{6}$/u.test(v)) return v.toUpperCase();
|
|
772
|
+
if (/^[0-9A-Fa-f]{3}$/u.test(v)) return [...v].map((c) => c + c).join("").toUpperCase();
|
|
773
|
+
return VML_NAMED_COLORS[v.toLowerCase()];
|
|
774
|
+
}
|
|
775
|
+
var VML_NAMED_COLORS = {
|
|
776
|
+
black: "000000",
|
|
777
|
+
silver: "C0C0C0",
|
|
778
|
+
gray: "808080",
|
|
779
|
+
grey: "808080",
|
|
780
|
+
white: "FFFFFF",
|
|
781
|
+
maroon: "800000",
|
|
782
|
+
red: "FF0000",
|
|
783
|
+
purple: "800080",
|
|
784
|
+
fuchsia: "FF00FF",
|
|
785
|
+
green: "008000",
|
|
786
|
+
lime: "00FF00",
|
|
787
|
+
olive: "808000",
|
|
788
|
+
yellow: "FFFF00",
|
|
789
|
+
navy: "000080",
|
|
790
|
+
blue: "0000FF",
|
|
791
|
+
teal: "008080",
|
|
792
|
+
aqua: "00FFFF",
|
|
793
|
+
window: "FFFFFF",
|
|
794
|
+
windowtext: "000000"
|
|
795
|
+
};
|
|
796
|
+
/**
|
|
797
|
+
* §14.1.2.5 — `@filled="f"` or a `<v:fill type="none">` says the shape is not
|
|
798
|
+
* filled; otherwise `@fillcolor`, defaulting to VML's own white. Exported
|
|
799
|
+
* because §17.2.1's page background is a `v:background` with exactly this fill
|
|
800
|
+
* on it — the same gradients and the same pictures.
|
|
801
|
+
*
|
|
802
|
+
* @param shape The VML element carrying the fill.
|
|
803
|
+
* @param shapeType Its `v:shapetype`, for the attributes it does not state.
|
|
804
|
+
* @param resolveImage Resolver for a `v:fill` that paints a PICTURE (`@r:id`).
|
|
805
|
+
* @returns The fill.
|
|
806
|
+
*/
|
|
807
|
+
function vmlFill(shape, shapeType, resolveImage) {
|
|
808
|
+
const filled = poAttr(shape, "filled") ?? (shapeType ? poAttr(shapeType, "filled") : void 0);
|
|
809
|
+
if (filled === "f" || filled === "false") return { kind: "none" };
|
|
810
|
+
const fillEl = poChildren(shape).find((c) => poIs(c, "v:fill"));
|
|
811
|
+
if (fillEl && (poAttr(fillEl, "type") === "none" || poAttr(fillEl, "on") === "f")) return { kind: "none" };
|
|
812
|
+
const base = vmlColor(poAttr(shape, "fillcolor")) ?? (fillEl ? vmlColor(poAttr(fillEl, "color")) : void 0) ?? vmlColor(shapeType ? poAttr(shapeType, "fillcolor") : void 0) ?? "FFFFFF";
|
|
813
|
+
const fillRelId = fillEl && poAttr(fillEl, "type") === "frame" ? poAttr(fillEl, "r:id") : void 0;
|
|
814
|
+
const picture = fillRelId !== void 0 ? resolveImage?.(fillRelId) : void 0;
|
|
815
|
+
if (picture !== void 0) return {
|
|
816
|
+
kind: "picture",
|
|
817
|
+
imageResource: picture
|
|
818
|
+
};
|
|
819
|
+
const gradient = fillEl ? vmlGradient(fillEl, base) : void 0;
|
|
820
|
+
if (gradient) return {
|
|
821
|
+
kind: "gradient",
|
|
822
|
+
gradient
|
|
823
|
+
};
|
|
824
|
+
return {
|
|
825
|
+
kind: "solid",
|
|
826
|
+
colorHex: base
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function vmlGradient(fillEl, base) {
|
|
830
|
+
const type = poAttr(fillEl, "type");
|
|
831
|
+
if (type !== "gradient" && type !== "gradientRadial") return void 0;
|
|
832
|
+
const color2 = vmlColor(poAttr(fillEl, "color2"));
|
|
833
|
+
if (color2 === void 0) return void 0;
|
|
834
|
+
const raw = Number.parseFloat(poAttr(fillEl, "angle") ?? "");
|
|
835
|
+
const angle = (90 + (Number.isFinite(raw) ? raw : 0)) % 360;
|
|
836
|
+
const center = type === "gradientRadial" ? vmlRadialCenter(raw, poAttr(fillEl, "focus")) : void 0;
|
|
837
|
+
return {
|
|
838
|
+
kind: type === "gradientRadial" ? "radial" : "linear",
|
|
839
|
+
...type === "gradientRadial" ? { sweep: "rect" } : { angle },
|
|
840
|
+
...center ? { center } : {},
|
|
841
|
+
stops: vmlStops(poAttr(fillEl, "colors")) ?? vmlFocusStops(poAttr(fillEl, "focus"), base, color2)
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function vmlRadialCenter(angleRaw, focus) {
|
|
845
|
+
const pct = Number.parseFloat((focus ?? "").replace("%", ""));
|
|
846
|
+
if (!Number.isFinite(pct) || Math.abs(pct) < 99) return void 0;
|
|
847
|
+
const rad = (Number.isFinite(angleRaw) ? angleRaw : 0) * Math.PI / 180;
|
|
848
|
+
const dx = Math.sin(rad);
|
|
849
|
+
const dy = Math.cos(rad);
|
|
850
|
+
const snap = (v) => v > .5 ? 1 : v < -.5 ? 0 : .5;
|
|
851
|
+
return {
|
|
852
|
+
x: snap(dx),
|
|
853
|
+
y: snap(dy)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function vmlFocusStops(raw, base, color2) {
|
|
857
|
+
const pct = Number.parseFloat((raw ?? "").replace("%", ""));
|
|
858
|
+
const f = Number.isFinite(pct) ? Math.abs(pct) / 100 : 0;
|
|
859
|
+
if (f <= .01 || f >= .99) return [{
|
|
860
|
+
offset: 0,
|
|
861
|
+
colorHex: base
|
|
862
|
+
}, {
|
|
863
|
+
offset: 1,
|
|
864
|
+
colorHex: color2
|
|
865
|
+
}];
|
|
866
|
+
return [
|
|
867
|
+
{
|
|
868
|
+
offset: 0,
|
|
869
|
+
colorHex: base
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
offset: f,
|
|
873
|
+
colorHex: color2
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
offset: 1,
|
|
877
|
+
colorHex: base
|
|
878
|
+
}
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
function vmlStops(raw) {
|
|
882
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
883
|
+
const stops = [];
|
|
884
|
+
for (const part of raw.split(";")) {
|
|
885
|
+
const [posRaw, colorRaw] = part.trim().split(/\s+/u);
|
|
886
|
+
const colorHex = vmlColor(colorRaw);
|
|
887
|
+
if (posRaw === void 0 || colorHex === void 0) continue;
|
|
888
|
+
const pct = posRaw.endsWith("%");
|
|
889
|
+
const n = Number.parseFloat(pct ? posRaw.slice(0, -1) : posRaw.replace(/f$/iu, ""));
|
|
890
|
+
if (!Number.isFinite(n)) continue;
|
|
891
|
+
const offset = pct ? n / 100 : n > 1 ? n / 65536 : n;
|
|
892
|
+
stops.push({
|
|
893
|
+
offset: Math.min(1, Math.max(0, offset)),
|
|
894
|
+
colorHex
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (stops.length < 2) return void 0;
|
|
898
|
+
return stops.sort((a, b) => a.offset - b.offset);
|
|
899
|
+
}
|
|
900
|
+
function vmlLine(shape, shapeType) {
|
|
901
|
+
const stroked = poAttr(shape, "stroked") ?? (shapeType ? poAttr(shapeType, "stroked") : void 0);
|
|
902
|
+
if (stroked === "f" || stroked === "false") return void 0;
|
|
903
|
+
const strokeEl = poChildren(shape).find((c) => poIs(c, "v:stroke"));
|
|
904
|
+
if (strokeEl && poAttr(strokeEl, "on") === "f") return void 0;
|
|
905
|
+
const colorHex = vmlColor(poAttr(shape, "strokecolor")) ?? (strokeEl ? vmlColor(poAttr(strokeEl, "color")) : void 0) ?? "000000";
|
|
906
|
+
const arrow = (kind) => {
|
|
907
|
+
const type = VML_ARROWS.get(poAttr(strokeEl, `${kind}arrow`) ?? "");
|
|
908
|
+
if (type === void 0) return void 0;
|
|
909
|
+
const w = VML_ARROW_SIZES.get(poAttr(strokeEl, `${kind}arrowwidth`) ?? "");
|
|
910
|
+
const len = VML_ARROW_SIZES.get(poAttr(strokeEl, `${kind}arrowlength`) ?? "");
|
|
911
|
+
return {
|
|
912
|
+
type,
|
|
913
|
+
...w ? { width: w } : {},
|
|
914
|
+
...len ? { length: len } : {}
|
|
915
|
+
};
|
|
916
|
+
};
|
|
917
|
+
const headEnd = arrow("start");
|
|
918
|
+
const tailEnd = arrow("end");
|
|
919
|
+
const weight = poAttr(shape, "strokeweight");
|
|
920
|
+
const m = weight ? /(-?[0-9.]+)\s*(pt|in|px|cm|mm|pc)?/iu.exec(weight) : null;
|
|
921
|
+
const widthPt = m ? Number.parseFloat(m[1]) * (VML_UNIT_TO_PT[(m[2] ?? "px").toLowerCase()] ?? 1) : .75;
|
|
922
|
+
return {
|
|
923
|
+
fill: "solid",
|
|
924
|
+
colorHex,
|
|
925
|
+
width: pt(Number.isFinite(widthPt) ? widthPt : .75),
|
|
926
|
+
...headEnd ? { headEnd } : {},
|
|
927
|
+
...tailEnd ? { tailEnd } : {}
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
var VML_ARROWS = new Map([
|
|
931
|
+
["block", "triangle"],
|
|
932
|
+
["classic", "stealth"],
|
|
933
|
+
["diamond", "diamond"],
|
|
934
|
+
["oval", "oval"],
|
|
935
|
+
["open", "arrow"]
|
|
936
|
+
]);
|
|
937
|
+
var VML_ARROW_SIZES = new Map([
|
|
938
|
+
["narrow", "sm"],
|
|
939
|
+
["short", "sm"],
|
|
940
|
+
["medium", "med"],
|
|
941
|
+
["wide", "lg"],
|
|
942
|
+
["long", "lg"]
|
|
943
|
+
]);
|
|
944
|
+
function vmlWordArtBody(shape, width, height) {
|
|
945
|
+
const textpath = poChildren(shape).find((c) => poIs(c, "v:textpath"));
|
|
946
|
+
const string = textpath ? poAttr(textpath, "string") : void 0;
|
|
947
|
+
if (string === void 0 || string === "") return void 0;
|
|
948
|
+
const fill = poAttr(shape, "fillcolor");
|
|
949
|
+
const colorHex = fill && /^#?[0-9A-Fa-f]{6}$/u.test(fill) ? fill.replace("#", "").toUpperCase() : void 0;
|
|
950
|
+
const lines = string.split(/\r\n|[\r\n]/u);
|
|
951
|
+
const fontSizePt = pt(Math.max(1, height / lines.length));
|
|
952
|
+
return {
|
|
953
|
+
content: lines.map((line) => ({
|
|
954
|
+
kind: "paragraph",
|
|
955
|
+
paragraph: {
|
|
956
|
+
properties: {
|
|
957
|
+
alignment: "center",
|
|
958
|
+
spacingBefore: pt(0),
|
|
959
|
+
spacingAfter: pt(0),
|
|
960
|
+
spacingLine: pt(12),
|
|
961
|
+
spacingLineRule: "auto"
|
|
962
|
+
},
|
|
963
|
+
runs: [{
|
|
964
|
+
text: line,
|
|
965
|
+
properties: {
|
|
966
|
+
fontSizePt,
|
|
967
|
+
bold: true,
|
|
968
|
+
...colorHex ? { colorHex } : {}
|
|
969
|
+
}
|
|
970
|
+
}]
|
|
971
|
+
}
|
|
972
|
+
})),
|
|
973
|
+
anchor: "ctr",
|
|
974
|
+
fitToBox: true,
|
|
975
|
+
insetLeft: pt(0),
|
|
976
|
+
insetRight: pt(0),
|
|
977
|
+
insetTop: pt(0),
|
|
978
|
+
insetBottom: pt(0)
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function vmlTextBox(shape, parseBody) {
|
|
982
|
+
const box = poChildren(shape).find((c) => poIs(c, "v:textbox"));
|
|
983
|
+
const content = box ? poFindDescendant(box, "w:txbxContent") : void 0;
|
|
984
|
+
if (!content) return void 0;
|
|
985
|
+
const blocks = parseBody(poChildren(content));
|
|
986
|
+
return blocks.length > 0 ? { content: blocks } : void 0;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* The `@style` of a positioned VML shape as a float anchor: `position:absolute`
|
|
990
|
+
* with `margin-left`/`margin-top` (or `left`/`top`) against whatever
|
|
991
|
+
* `mso-position-*-relative` names.
|
|
992
|
+
*
|
|
993
|
+
* @param style The shape's `@style`.
|
|
994
|
+
* @param zIndex The shape's z-index, when it has one.
|
|
995
|
+
* @returns The anchor, or `undefined` for an inline shape.
|
|
996
|
+
*/
|
|
997
|
+
function vmlWrap(shape) {
|
|
998
|
+
const w = shape ? poChildren(shape).find((c) => poIsLocal(c, "wrap")) : void 0;
|
|
999
|
+
if (!w) return "none";
|
|
1000
|
+
const type = poAttr(w, "type");
|
|
1001
|
+
if (type === "topAndBottom") return "topAndBottom";
|
|
1002
|
+
if (type === "none") return "none";
|
|
1003
|
+
return "square";
|
|
1004
|
+
}
|
|
1005
|
+
function vmlFloat(style, zIndexAttr, shape) {
|
|
1006
|
+
if (!/position\s*:\s*absolute/iu.test(style)) return void 0;
|
|
1007
|
+
const prop = (name) => {
|
|
1008
|
+
const m = new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*(-?[0-9.]+)(pt|in|px|cm|mm|pc)?`, "iu").exec(style);
|
|
1009
|
+
if (!m) return void 0;
|
|
1010
|
+
const v = Number.parseFloat(m[1]) * (VML_UNIT_TO_PT[(m[2] ?? "px").toLowerCase()] ?? 1);
|
|
1011
|
+
return Number.isFinite(v) ? v : void 0;
|
|
1012
|
+
};
|
|
1013
|
+
const rel = (name) => new RegExp(`mso-position-${name}-relative\\s*:\\s*([a-z-]+)`, "iu").exec(style)?.[1];
|
|
1014
|
+
const hRaw = rel("horizontal");
|
|
1015
|
+
const vRaw = rel("vertical");
|
|
1016
|
+
const align = /mso-position-horizontal\s*:\s*(center|right|left)/iu.exec(style)?.[1];
|
|
1017
|
+
const vAlign = /mso-position-vertical\s*:\s*(center|top|bottom)/iu.exec(style)?.[1];
|
|
1018
|
+
const x = prop("margin-left") ?? prop("left") ?? 0;
|
|
1019
|
+
const y = prop("margin-top") ?? prop("top") ?? 0;
|
|
1020
|
+
const zIndex = prop("z-index") ?? zIndexAttr;
|
|
1021
|
+
return {
|
|
1022
|
+
wrap: vmlWrap(shape),
|
|
1023
|
+
...zIndex !== void 0 && zIndex < 0 ? { behind: true } : {},
|
|
1024
|
+
...zIndex !== void 0 ? { zOrder: Math.abs(zIndex) } : {},
|
|
1025
|
+
posH: {
|
|
1026
|
+
relativeFrom: hRaw === "page" ? "page" : hRaw === "margin" ? "margin" : "column",
|
|
1027
|
+
...align ? { align } : { offsetPt: pt(x) }
|
|
1028
|
+
},
|
|
1029
|
+
posV: {
|
|
1030
|
+
relativeFrom: vRaw === "page" ? "page" : vRaw === "margin" ? "margin" : vRaw === "line" ? "line" : "paragraph",
|
|
1031
|
+
...vAlign ? { align: vAlign } : {},
|
|
1032
|
+
offsetPt: pt(y)
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* §14.1.2.22 `v:textpath` — legacy WordArt: text bent along a preset path, whose
|
|
1038
|
+
* STRING lives in an attribute rather than in the document body. The path is a
|
|
1039
|
+
* `v:shapetype` of formulas we do not evaluate, so the words are set flat in
|
|
1040
|
+
* the shape's box — which is the whole of what the page says. Read nowhere,
|
|
1041
|
+
* WordArt.docx printed an empty page.
|
|
1042
|
+
*
|
|
1043
|
+
* @param node The `w:pict` / `w:object` node.
|
|
1044
|
+
* @returns The shape, or `null` when there is no textpath or no usable size.
|
|
1045
|
+
*/
|
|
1046
|
+
function parseVmlWordArt(node) {
|
|
1047
|
+
const shape = poFindDescendant(node, "v:shape");
|
|
1048
|
+
const textpath = shape ? poFindDescendant(shape, "v:textpath") : void 0;
|
|
1049
|
+
const string = textpath ? poAttr(textpath, "string") : void 0;
|
|
1050
|
+
if (string === void 0 || string === "") return null;
|
|
1051
|
+
const width = vmlStyleLength(shape, "width");
|
|
1052
|
+
const height = vmlStyleLength(shape, "height");
|
|
1053
|
+
if (width === void 0 || height === void 0) return null;
|
|
1054
|
+
const fill = poAttr(shape, "fillcolor");
|
|
1055
|
+
const colorHex = fill && /^#?[0-9A-Fa-f]{6}$/u.test(fill) ? fill.replace("#", "").toUpperCase() : void 0;
|
|
1056
|
+
const lines = string.split(/\r\n|[\r\n]/u);
|
|
1057
|
+
const fontSizePt = pt(Math.max(1, height / lines.length));
|
|
1058
|
+
return {
|
|
1059
|
+
kind: "shape",
|
|
1060
|
+
data: {
|
|
1061
|
+
width,
|
|
1062
|
+
height,
|
|
1063
|
+
geometry: {
|
|
1064
|
+
kind: "preset",
|
|
1065
|
+
preset: "rect",
|
|
1066
|
+
adjust: /* @__PURE__ */ new Map()
|
|
1067
|
+
},
|
|
1068
|
+
fill: { kind: "none" },
|
|
1069
|
+
text: {
|
|
1070
|
+
content: lines.map((line) => ({
|
|
1071
|
+
kind: "paragraph",
|
|
1072
|
+
paragraph: {
|
|
1073
|
+
properties: {
|
|
1074
|
+
alignment: "center",
|
|
1075
|
+
spacingBefore: pt(0),
|
|
1076
|
+
spacingAfter: pt(0),
|
|
1077
|
+
spacingLine: pt(12),
|
|
1078
|
+
spacingLineRule: "auto"
|
|
1079
|
+
},
|
|
1080
|
+
runs: [{
|
|
1081
|
+
text: line,
|
|
1082
|
+
properties: {
|
|
1083
|
+
fontSizePt,
|
|
1084
|
+
bold: true,
|
|
1085
|
+
...colorHex ? { colorHex } : {}
|
|
1086
|
+
}
|
|
1087
|
+
}]
|
|
1088
|
+
}
|
|
1089
|
+
})),
|
|
1090
|
+
anchor: "ctr",
|
|
1091
|
+
fitToBox: true,
|
|
1092
|
+
insetLeft: pt(0),
|
|
1093
|
+
insetRight: pt(0),
|
|
1094
|
+
insetTop: pt(0),
|
|
1095
|
+
insetBottom: pt(0)
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
...poAttr(shape, "alt")?.trim() ? { altText: poAttr(shape, "alt").trim() } : {}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
190
1101
|
var VML_UNIT_TO_PT = {
|
|
191
1102
|
pt: 1,
|
|
192
1103
|
in: 72,
|
|
@@ -204,10 +1115,276 @@ function vmlStyleLength(shape, prop) {
|
|
|
204
1115
|
if (!Number.isFinite(value) || value <= 0) return void 0;
|
|
205
1116
|
return pt(value * (VML_UNIT_TO_PT[(m[2] ?? "px").toLowerCase()] ?? 1));
|
|
206
1117
|
}
|
|
207
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* §20.1.8.55 `a:srcRect` — the picture's own edges, cut away before it is
|
|
1120
|
+
* fitted to its frame. Each is a percentage in thousandths (ST_Percentage), so
|
|
1121
|
+
* `l="14711"` drops the left 14.711%.
|
|
1122
|
+
*
|
|
1123
|
+
* @param node The `a:srcRect` element, or `undefined` when the fill declares none.
|
|
1124
|
+
* @returns The crop, or `undefined` when nothing is cut away.
|
|
1125
|
+
*/
|
|
1126
|
+
function parseSrcRect(node) {
|
|
1127
|
+
if (!node) return void 0;
|
|
1128
|
+
const edge = (name) => {
|
|
1129
|
+
const v = Number(poAttr(node, name));
|
|
1130
|
+
return Number.isFinite(v) && v > 0 ? v / 1e5 : 0;
|
|
1131
|
+
};
|
|
1132
|
+
const crop = {
|
|
1133
|
+
left: edge("l"),
|
|
1134
|
+
top: edge("t"),
|
|
1135
|
+
right: edge("r"),
|
|
1136
|
+
bottom: edge("b")
|
|
1137
|
+
};
|
|
1138
|
+
if (crop.left + crop.top + crop.right + crop.bottom === 0) return void 0;
|
|
1139
|
+
if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) return void 0;
|
|
1140
|
+
return crop;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* §20.5.2.17 `wpg:wgp` — a drawing group: a box holding shapes of its own, in
|
|
1144
|
+
* its own coordinate space. `a:xfrm` gives the box (`a:off`/`a:ext`) and the
|
|
1145
|
+
* space its members are written in (`a:chOff`/`a:chExt`); a member at (x, y) in
|
|
1146
|
+
* that space lands at `off + (x − chOff) · ext / chExt` in the group's.
|
|
1147
|
+
*
|
|
1148
|
+
* The group draws nothing itself. Left unread, Tdf147485.docx — whose whole
|
|
1149
|
+
* picture is one — printed an empty page: the `mc:Fallback` beside the group is
|
|
1150
|
+
* VML, a geometry language of its own that we do not read either.
|
|
1151
|
+
*
|
|
1152
|
+
* @returns The group as a shape with children, or `null` when it states no size.
|
|
1153
|
+
*/
|
|
1154
|
+
/**
|
|
1155
|
+
* §20.3 — a `lc:lockedCanvas`: the same content model as a group, so it lays
|
|
1156
|
+
* out as one. Its own `a:grpSpPr/a:xfrm` states the child coordinate space the
|
|
1157
|
+
* members are placed in; the drawing's `wp:extent` states the box that space
|
|
1158
|
+
* is drawn into.
|
|
1159
|
+
*/
|
|
1160
|
+
function parseLockedCanvas(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText) {
|
|
1161
|
+
const canvas = expandMcChildren(poChildren(graphicData)).find((c) => poIsLocal(c, "lockedCanvas"));
|
|
1162
|
+
if (!canvas) return null;
|
|
1163
|
+
const raw = groupChildren(canvas, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
1164
|
+
if (extentCx === void 0 || extentCy === void 0) return null;
|
|
1165
|
+
const space = canvasChildSpace(canvas);
|
|
1166
|
+
let children;
|
|
1167
|
+
if (space) {
|
|
1168
|
+
const sx = emuToPt(extentCx) / space.w;
|
|
1169
|
+
const sy = emuToPt(extentCy) / space.h;
|
|
1170
|
+
children = raw.map((c) => ({
|
|
1171
|
+
shape: {
|
|
1172
|
+
...c.shape,
|
|
1173
|
+
width: pt(c.shape.width * sx),
|
|
1174
|
+
height: pt(c.shape.height * sy)
|
|
1175
|
+
},
|
|
1176
|
+
xPt: pt((c.xPt - space.x) * sx),
|
|
1177
|
+
yPt: pt((c.yPt - space.y) * sy)
|
|
1178
|
+
}));
|
|
1179
|
+
} else {
|
|
1180
|
+
const origin = contentOrigin(raw);
|
|
1181
|
+
children = raw.map((c) => ({
|
|
1182
|
+
...c,
|
|
1183
|
+
xPt: pt(c.xPt - origin.x),
|
|
1184
|
+
yPt: pt(c.yPt - origin.y)
|
|
1185
|
+
}));
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
width: emuToPt(extentCx),
|
|
1189
|
+
height: emuToPt(extentCy),
|
|
1190
|
+
...children.length > 0 ? { children } : {},
|
|
1191
|
+
geometry: {
|
|
1192
|
+
kind: "custom",
|
|
1193
|
+
custom: EMPTY_GEOMETRY
|
|
1194
|
+
},
|
|
1195
|
+
fill: { kind: "none" }
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function canvasChildSpace(canvas) {
|
|
1199
|
+
const grpSpPr = poChildren(canvas).find((c) => poIsLocal(c, "grpSpPr"));
|
|
1200
|
+
const xfrm = grpSpPr ? poChildren(grpSpPr).find((c) => poIs(c, "a:xfrm")) : void 0;
|
|
1201
|
+
const chOff = xfrm ? poChildren(xfrm).find((c) => poIs(c, "a:chOff")) : void 0;
|
|
1202
|
+
const chExt = xfrm ? poChildren(xfrm).find((c) => poIs(c, "a:chExt")) : void 0;
|
|
1203
|
+
const cx = chExt ? poIntAttr(chExt, "cx") : void 0;
|
|
1204
|
+
const cy = chExt ? poIntAttr(chExt, "cy") : void 0;
|
|
1205
|
+
if (cx === void 0 || cy === void 0 || cx <= 0 || cy <= 0) return void 0;
|
|
1206
|
+
return {
|
|
1207
|
+
x: emuToPt((chOff ? poIntAttr(chOff, "x") : void 0) ?? 0),
|
|
1208
|
+
y: emuToPt((chOff ? poIntAttr(chOff, "y") : void 0) ?? 0),
|
|
1209
|
+
w: emuToPt(cx),
|
|
1210
|
+
h: emuToPt(cy)
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function parseWgp(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText) {
|
|
1214
|
+
const wgp = expandMcChildren(poChildren(graphicData)).find((c) => poIs(c, "wpg:wgp"));
|
|
1215
|
+
if (!wgp) return null;
|
|
1216
|
+
const children = groupChildren(wgp, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
1217
|
+
const grpSpPr = poChildren(wgp).find((c) => poIs(c, "wpg:grpSpPr"));
|
|
1218
|
+
const ext = grpSpPr ? poChildren(poChildren(grpSpPr).find((c) => poIs(c, "a:xfrm")) ?? grpSpPr).find((c) => poIs(c, "a:ext")) : void 0;
|
|
1219
|
+
const widthEmu = extentCx ?? (ext ? poIntAttr(ext, "cx") : void 0);
|
|
1220
|
+
const heightEmu = extentCy ?? (ext ? poIntAttr(ext, "cy") : void 0);
|
|
1221
|
+
if (widthEmu === void 0 || heightEmu === void 0) return null;
|
|
1222
|
+
return {
|
|
1223
|
+
width: emuToPt(widthEmu),
|
|
1224
|
+
height: emuToPt(heightEmu),
|
|
1225
|
+
...children.length > 0 ? { children } : {},
|
|
1226
|
+
geometry: {
|
|
1227
|
+
kind: "custom",
|
|
1228
|
+
custom: EMPTY_GEOMETRY
|
|
1229
|
+
},
|
|
1230
|
+
fill: { kind: "none" }
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function groupChildren(wgp, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText, unitScale = {
|
|
1234
|
+
x: 1,
|
|
1235
|
+
y: 1
|
|
1236
|
+
}) {
|
|
1237
|
+
const grpSpPr = poChildren(wgp).find((c) => poIsLocal(c, "grpSpPr"));
|
|
1238
|
+
const xfrm = grpSpPr ? poChildren(grpSpPr).find((c) => poIs(c, "a:xfrm")) : void 0;
|
|
1239
|
+
const at = (tag, ax, ay) => {
|
|
1240
|
+
const n = xfrm ? poChildren(xfrm).find((c) => poIs(c, tag)) : void 0;
|
|
1241
|
+
const x = n ? poIntAttr(n, ax) : void 0;
|
|
1242
|
+
const y = n ? poIntAttr(n, ay) : void 0;
|
|
1243
|
+
return x !== void 0 && y !== void 0 ? {
|
|
1244
|
+
x,
|
|
1245
|
+
y
|
|
1246
|
+
} : void 0;
|
|
1247
|
+
};
|
|
1248
|
+
const ext = at("a:ext", "cx", "cy");
|
|
1249
|
+
const chExt = at("a:chExt", "cx", "cy");
|
|
1250
|
+
const sx = (ext && chExt && chExt.x > 0 ? ext.x / chExt.x : 1) * unitScale.x;
|
|
1251
|
+
const sy = (ext && chExt && chExt.y > 0 ? ext.y / chExt.y : 1) * unitScale.y;
|
|
1252
|
+
const declared = at("a:chOff", "x", "y");
|
|
1253
|
+
const chOff = xfrm !== void 0 && (!chExt || chExt.x <= 0 || chExt.y <= 0) ? memberOrigin(wgp, declared ?? {
|
|
1254
|
+
x: 0,
|
|
1255
|
+
y: 0
|
|
1256
|
+
}) : declared ?? {
|
|
1257
|
+
x: 0,
|
|
1258
|
+
y: 0
|
|
1259
|
+
};
|
|
1260
|
+
const out = [];
|
|
1261
|
+
for (const child of expandMcChildren(poChildren(wgp))) {
|
|
1262
|
+
const nested = poIsLocal(child, "grpSp");
|
|
1263
|
+
const picture = poIsLocal(child, "pic");
|
|
1264
|
+
if (!isGroupMember(child)) continue;
|
|
1265
|
+
const spPr = poChildren(child).find((c) => poIsLocal(c, nested ? "grpSpPr" : "spPr"));
|
|
1266
|
+
const childXfrm = spPr ? poChildren(spPr).find((c) => poIs(c, "a:xfrm")) : void 0;
|
|
1267
|
+
const cOff = childXfrm ? poChildren(childXfrm).find((c) => poIs(c, "a:off")) : void 0;
|
|
1268
|
+
const cExt = childXfrm ? poChildren(childXfrm).find((c) => poIs(c, "a:ext")) : void 0;
|
|
1269
|
+
const cx = cExt ? poIntAttr(cExt, "cx") : void 0;
|
|
1270
|
+
const cy = cExt ? poIntAttr(cExt, "cy") : void 0;
|
|
1271
|
+
if (cx === void 0 || cy === void 0) continue;
|
|
1272
|
+
const data = nested ? parseNestedGroup(child, cx * sx, cy * sy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText, {
|
|
1273
|
+
x: sx,
|
|
1274
|
+
y: sy
|
|
1275
|
+
}) : picture ? parseGroupPicture(child, cx, cy, resolveImage) : parseWspNode(child, cx, cy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText);
|
|
1276
|
+
if (!data) continue;
|
|
1277
|
+
const x = (poIntAttr(cOff, "x") ?? 0) - chOff.x;
|
|
1278
|
+
const y = (poIntAttr(cOff, "y") ?? 0) - chOff.y;
|
|
1279
|
+
out.push({
|
|
1280
|
+
shape: {
|
|
1281
|
+
...data,
|
|
1282
|
+
width: emuToPt(cx * sx),
|
|
1283
|
+
height: emuToPt(cy * sy),
|
|
1284
|
+
paragraphProperties: {}
|
|
1285
|
+
},
|
|
1286
|
+
xPt: emuToPt(x * sx),
|
|
1287
|
+
yPt: emuToPt(y * sy)
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
return out;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* The top-left corner the drawn content actually occupies, in the group's own
|
|
1294
|
+
* coordinates — nested members counted at their absolute position.
|
|
1295
|
+
*
|
|
1296
|
+
* @param children The group's members.
|
|
1297
|
+
* @returns The minimum x/y over the whole tree (0,0 when it is empty).
|
|
1298
|
+
*/
|
|
1299
|
+
function contentOrigin(children) {
|
|
1300
|
+
let minX = Infinity;
|
|
1301
|
+
let minY = Infinity;
|
|
1302
|
+
const walk = (list, ox, oy) => {
|
|
1303
|
+
for (const c of list) {
|
|
1304
|
+
const x = ox + c.xPt;
|
|
1305
|
+
const y = oy + c.yPt;
|
|
1306
|
+
if (c.shape.children && c.shape.children.length > 0) {
|
|
1307
|
+
walk(c.shape.children, x, y);
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (x < minX) minX = x;
|
|
1311
|
+
if (y < minY) minY = y;
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
walk(children, 0, 0);
|
|
1315
|
+
return {
|
|
1316
|
+
x: Number.isFinite(minX) ? minX : 0,
|
|
1317
|
+
y: Number.isFinite(minY) ? minY : 0
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
/** A node that is a group MEMBER: a shape, a connector, a picture, or a nested group. */
|
|
1321
|
+
function isGroupMember(node) {
|
|
1322
|
+
return poIsLocal(node, "wsp") || poIsLocal(node, "sp") || poIsLocal(node, "cxnSp") || poIsLocal(node, "grpSp") || poIsLocal(node, "pic");
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* The top-left corner of a group's members, in the child coordinate space —
|
|
1326
|
+
* the origin to use when the group declares no child space of its own.
|
|
1327
|
+
*
|
|
1328
|
+
* @param wgp The group / canvas node.
|
|
1329
|
+
* @param fallback What to return when no member states an offset.
|
|
1330
|
+
* @returns The minimum `a:off` over the members.
|
|
1331
|
+
*/
|
|
1332
|
+
function memberOrigin(wgp, fallback) {
|
|
1333
|
+
let minX = Infinity;
|
|
1334
|
+
let minY = Infinity;
|
|
1335
|
+
for (const child of expandMcChildren(poChildren(wgp))) {
|
|
1336
|
+
if (!isGroupMember(child)) continue;
|
|
1337
|
+
const xfrm = poChildren(poChildren(child).find((c) => poIsLocal(c, "spPr") || poIsLocal(c, "grpSpPr"))).find((c) => poIs(c, "a:xfrm"));
|
|
1338
|
+
const off = xfrm ? poChildren(xfrm).find((c) => poIs(c, "a:off")) : void 0;
|
|
1339
|
+
const x = poIntAttr(off, "x");
|
|
1340
|
+
const y = poIntAttr(off, "y");
|
|
1341
|
+
if (x !== void 0 && x < minX) minX = x;
|
|
1342
|
+
if (y !== void 0 && y < minY) minY = y;
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
x: Number.isFinite(minX) ? minX : fallback.x,
|
|
1346
|
+
y: Number.isFinite(minY) ? minY : fallback.y
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function parseNestedGroup(grpSp, widthEmu, heightEmu, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText, unitScale) {
|
|
1350
|
+
const children = groupChildren(grpSp, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText, unitScale);
|
|
1351
|
+
return {
|
|
1352
|
+
width: emuToPt(widthEmu),
|
|
1353
|
+
height: emuToPt(heightEmu),
|
|
1354
|
+
...children.length > 0 ? { children } : {},
|
|
1355
|
+
geometry: {
|
|
1356
|
+
kind: "custom",
|
|
1357
|
+
custom: EMPTY_GEOMETRY
|
|
1358
|
+
},
|
|
1359
|
+
fill: { kind: "none" }
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function parseGroupPicture(pic, widthEmu, heightEmu, resolveImage) {
|
|
1363
|
+
const blip = poFindDescendant(pic, "a:blip");
|
|
1364
|
+
const relId = blip ? poAttr(blip, "embed") : void 0;
|
|
1365
|
+
const resource = relId !== void 0 ? resolveImage?.(relId) : void 0;
|
|
1366
|
+
if (resource === void 0) return null;
|
|
1367
|
+
return {
|
|
1368
|
+
width: emuToPt(widthEmu),
|
|
1369
|
+
height: emuToPt(heightEmu),
|
|
1370
|
+
geometry: {
|
|
1371
|
+
kind: "preset",
|
|
1372
|
+
preset: "rect",
|
|
1373
|
+
adjust: /* @__PURE__ */ new Map()
|
|
1374
|
+
},
|
|
1375
|
+
fill: {
|
|
1376
|
+
kind: "picture",
|
|
1377
|
+
imageResource: resource
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
function parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText) {
|
|
208
1382
|
const wsp = expandMcChildren(poChildren(graphicData)).find((c) => poIs(c, "wps:wsp"));
|
|
209
1383
|
if (!wsp) return null;
|
|
210
|
-
|
|
1384
|
+
return parseWspNode(wsp, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles);
|
|
1385
|
+
}
|
|
1386
|
+
function parseWspNode(wsp, extentCx, extentCy, resolveColor, parseBody, resolveImage, themeLineWidths, themeStyles, parseDrawingText) {
|
|
1387
|
+
const spPr = poChildren(wsp).find((c) => poIsLocal(c, "spPr"));
|
|
211
1388
|
let geometry = {
|
|
212
1389
|
kind: "preset",
|
|
213
1390
|
preset: "rect",
|
|
@@ -234,10 +1411,24 @@ function parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody) {
|
|
|
234
1411
|
const cust = poChildren(spPr).find((c) => poIs(c, "a:custGeom"));
|
|
235
1412
|
if (prst) geometry = parsePrstGeom(prst);
|
|
236
1413
|
else if (cust) geometry = parseCustGeom(cust);
|
|
237
|
-
fill = parseFill(spPr, resolveColor);
|
|
1414
|
+
fill = parseFill(spPr, resolveColor, resolveImage);
|
|
238
1415
|
line = parseLine(spPr, resolveColor);
|
|
239
1416
|
}
|
|
240
|
-
const
|
|
1417
|
+
const txSp = poChildren(wsp).find((c) => poIs(c, "a:txSp"));
|
|
1418
|
+
const txBody = poChildren(wsp).find((c) => poIs(c, "a:txBody")) ?? (txSp ? poChildren(txSp).find((c) => poIs(c, "a:txBody")) : void 0);
|
|
1419
|
+
const text = (parseBody ? parseTextBox(wsp, parseBody) : void 0) ?? (txBody && parseDrawingText ? parseDrawingText(txBody, resolveColor) : void 0);
|
|
1420
|
+
const style = poChildren(wsp).find((c) => poIsLocal(c, "style"));
|
|
1421
|
+
if (style && fill.kind === "none" && !statesFill(spPr)) fill = styleRefFill(style, resolveColor, themeStyles);
|
|
1422
|
+
const styleLine = style ? styleRefLine(style, resolveColor, themeLineWidths) : void 0;
|
|
1423
|
+
if (styleLine) {
|
|
1424
|
+
const own = line;
|
|
1425
|
+
line = own ? {
|
|
1426
|
+
...styleLine,
|
|
1427
|
+
...Object.fromEntries(Object.entries(own).filter(([, v]) => v !== void 0))
|
|
1428
|
+
} : styleLine;
|
|
1429
|
+
}
|
|
1430
|
+
const styled = style && text ? withStyleFontColor(text, style, resolveColor) : text;
|
|
1431
|
+
const shadow = (spPr ? parseShadow(spPr, resolveColor) : void 0) ?? (style ? styleRefShadow(style, resolveColor, themeStyles?.effects) : void 0);
|
|
241
1432
|
if (widthEmu === void 0 || heightEmu === void 0) return null;
|
|
242
1433
|
return {
|
|
243
1434
|
width: emuToPt(widthEmu),
|
|
@@ -245,8 +1436,96 @@ function parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody) {
|
|
|
245
1436
|
geometry,
|
|
246
1437
|
fill,
|
|
247
1438
|
...line ? { line } : {},
|
|
1439
|
+
...shadow ? { shadow } : {},
|
|
248
1440
|
...transform ? { transform } : {},
|
|
249
|
-
...
|
|
1441
|
+
...styled ? { text: styled } : {}
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* §20.1.4.2.8 `<a:effectRef>` — the shadow a gallery style names: a 1-based
|
|
1446
|
+
* index into the theme's `a:effectStyleLst`, plus the colour to put wherever
|
|
1447
|
+
* that style says `phClr`. The same mechanism as the fill and the outline
|
|
1448
|
+
* beside it.
|
|
1449
|
+
*
|
|
1450
|
+
* @param style The shape's `<wps:style>` node.
|
|
1451
|
+
* @param resolveColor The document's colour resolver.
|
|
1452
|
+
* @param themeEffectStyles The theme's effect styles, in list order.
|
|
1453
|
+
* @returns The shadow, or undefined when the style names none.
|
|
1454
|
+
*/
|
|
1455
|
+
function styleRefShadow(style, resolveColor, themeEffectStyles) {
|
|
1456
|
+
const ref = poChildren(style).find((c) => poIs(c, "a:effectRef"));
|
|
1457
|
+
const idx = ref ? Number(poAttr(ref, "idx") ?? "") : NaN;
|
|
1458
|
+
if (!ref || !Number.isFinite(idx) || idx < 1) return void 0;
|
|
1459
|
+
const slot = themeEffectStyles?.[idx - 1];
|
|
1460
|
+
const list = slot ? poChildren(slot).find((c) => poIs(c, "a:effectLst")) : void 0;
|
|
1461
|
+
const shdw = list ? poChildren(list).find((c) => poIs(c, "a:outerShdw")) : void 0;
|
|
1462
|
+
if (!shdw) return void 0;
|
|
1463
|
+
const child = firstElementChild(ref);
|
|
1464
|
+
const phHex = child ? resolveColorNode(child, resolveColor) : void 0;
|
|
1465
|
+
return shadowFromOuterShdw(shdw, phHex ? placeholderColors(resolveColor, phHex) : resolveColor);
|
|
1466
|
+
}
|
|
1467
|
+
function placeholderColors(base, phHex) {
|
|
1468
|
+
return (raw) => "scheme" in raw && raw.scheme === "phClr" ? applyColorMods(phHex, raw.mods ?? []) : base(raw);
|
|
1469
|
+
}
|
|
1470
|
+
var FILL_TAGS = new Set([
|
|
1471
|
+
"a:noFill",
|
|
1472
|
+
"a:solidFill",
|
|
1473
|
+
"a:gradFill",
|
|
1474
|
+
"a:blipFill",
|
|
1475
|
+
"a:pattFill",
|
|
1476
|
+
"a:grpFill"
|
|
1477
|
+
]);
|
|
1478
|
+
function statesFill(spPr) {
|
|
1479
|
+
return spPr !== void 0 && poChildren(spPr).some((c) => FILL_TAGS.has(poTag(c) ?? ""));
|
|
1480
|
+
}
|
|
1481
|
+
function styleRefFill(style, resolveColor, themeStyles) {
|
|
1482
|
+
const ref = poChildren(style).find((c) => poIs(c, "a:fillRef"));
|
|
1483
|
+
if (!ref || poAttr(ref, "idx") === "0") return { kind: "none" };
|
|
1484
|
+
const child = firstElementChild(ref);
|
|
1485
|
+
const colorHex = child ? resolveColorNode(child, resolveColor) : void 0;
|
|
1486
|
+
const idx = Number(poAttr(ref, "idx") ?? "");
|
|
1487
|
+
const slot = Number.isFinite(idx) && idx >= 1001 ? themeStyles?.bgFills?.[idx - 1001] : Number.isFinite(idx) && idx >= 1 ? themeStyles?.fills?.[idx - 1] : void 0;
|
|
1488
|
+
if (slot) {
|
|
1489
|
+
const fill = fillFromNode(slot, colorHex ? placeholderColors(resolveColor, colorHex) : resolveColor);
|
|
1490
|
+
if (fill) return fill;
|
|
1491
|
+
}
|
|
1492
|
+
return colorHex === void 0 ? { kind: "none" } : {
|
|
1493
|
+
kind: "solid",
|
|
1494
|
+
colorHex
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
function styleRefLine(style, resolveColor, themeLineWidths) {
|
|
1498
|
+
const ref = poChildren(style).find((c) => poIs(c, "a:lnRef"));
|
|
1499
|
+
if (!ref || poAttr(ref, "idx") === "0") return void 0;
|
|
1500
|
+
const child = firstElementChild(ref);
|
|
1501
|
+
const colorHex = child ? resolveColorNode(child, resolveColor) : void 0;
|
|
1502
|
+
if (colorHex === void 0) return void 0;
|
|
1503
|
+
const idx = poIntAttr(ref, "idx");
|
|
1504
|
+
return {
|
|
1505
|
+
fill: "solid",
|
|
1506
|
+
colorHex,
|
|
1507
|
+
width: pt((idx !== void 0 && idx > 0 ? themeLineWidths?.[idx - 1] : void 0) ?? .75)
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function withStyleFontColor(text, style, resolveColor) {
|
|
1511
|
+
const child = firstElementChild(poChildren(style).find((c) => poIs(c, "a:fontRef")));
|
|
1512
|
+
const colorHex = child ? resolveColorNode(child, resolveColor) : void 0;
|
|
1513
|
+
if (colorHex === void 0) return text;
|
|
1514
|
+
return {
|
|
1515
|
+
...text,
|
|
1516
|
+
content: text.content.map((block) => block.kind === "paragraph" ? {
|
|
1517
|
+
...block,
|
|
1518
|
+
paragraph: {
|
|
1519
|
+
...block.paragraph,
|
|
1520
|
+
properties: {
|
|
1521
|
+
...block.paragraph.properties,
|
|
1522
|
+
inheritedRun: {
|
|
1523
|
+
...block.paragraph.properties.inheritedRun,
|
|
1524
|
+
colorHex
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
} : block)
|
|
250
1529
|
};
|
|
251
1530
|
}
|
|
252
1531
|
function parseTextBox(wsp, parseBody) {
|
|
@@ -263,8 +1542,13 @@ function parseTextBox(wsp, parseBody) {
|
|
|
263
1542
|
const bIns = bodyPr ? poIntAttr(bodyPr, "bIns") : void 0;
|
|
264
1543
|
const a = bodyPr ? poAttr(bodyPr, "anchor") : void 0;
|
|
265
1544
|
const anchor = a === "ctr" ? "ctr" : a === "b" ? "b" : a === "t" ? "t" : void 0;
|
|
1545
|
+
const v = bodyPr ? poAttr(bodyPr, "vert") : void 0;
|
|
1546
|
+
const vertical = v === "vert270" ? "vert270" : v !== void 0 && v !== "horz" ? "vert" : void 0;
|
|
1547
|
+
const autoFit = bodyPr !== void 0 && poChildren(bodyPr).some((c) => poIs(c, "a:spAutoFit"));
|
|
266
1548
|
return {
|
|
267
1549
|
content,
|
|
1550
|
+
...vertical ? { vertical } : {},
|
|
1551
|
+
...autoFit ? { autoFit: true } : {},
|
|
268
1552
|
...lIns !== void 0 ? { insetLeft: emuToPt(lIns) } : {},
|
|
269
1553
|
...tIns !== void 0 ? { insetTop: emuToPt(tIns) } : {},
|
|
270
1554
|
...rIns !== void 0 ? { insetRight: emuToPt(rIns) } : {},
|
|
@@ -273,6 +1557,16 @@ function parseTextBox(wsp, parseBody) {
|
|
|
273
1557
|
};
|
|
274
1558
|
}
|
|
275
1559
|
var isTrue = (v) => v === "1" || v === "true" || v === "on";
|
|
1560
|
+
/**
|
|
1561
|
+
* §20.1.7.6 `a:xfrm` — how a shape sits in its box: rotated about its centre by
|
|
1562
|
+
* `@rot` (sixtieth-thousandths of a degree) and mirrored by `@flipH`/`@flipV`.
|
|
1563
|
+
* The box itself comes from elsewhere — the anchor on a sheet, the extent in a
|
|
1564
|
+
* document — and holds the shape UNROTATED, which is what makes this a separate
|
|
1565
|
+
* transform rather than a different rectangle.
|
|
1566
|
+
*
|
|
1567
|
+
* @param xfrm The `a:xfrm` element.
|
|
1568
|
+
* @returns The transform; empty when it states neither rotation nor a flip.
|
|
1569
|
+
*/
|
|
276
1570
|
function parseXfrm(xfrm) {
|
|
277
1571
|
const rot = poIntAttr(xfrm, "rot");
|
|
278
1572
|
const flipH = isTrue(poAttr(xfrm, "flipH"));
|
|
@@ -415,27 +1709,174 @@ var firstPt = (node) => pts(node)[0];
|
|
|
415
1709
|
* @param spPr The `wps:spPr` node.
|
|
416
1710
|
* @param resolveColor Resolver for theme/scheme colours.
|
|
417
1711
|
*/
|
|
418
|
-
function parseFill(spPr, resolveColor) {
|
|
1712
|
+
function parseFill(spPr, resolveColor, resolveImage) {
|
|
419
1713
|
for (const child of poChildren(spPr)) {
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
const hex = colorFromContainer(child, resolveColor);
|
|
423
|
-
return hex ? {
|
|
424
|
-
kind: "solid",
|
|
425
|
-
colorHex: hex
|
|
426
|
-
} : { kind: "none" };
|
|
427
|
-
}
|
|
428
|
-
if (poIs(child, "a:gradFill")) {
|
|
429
|
-
const gradient = parseGradient(child, resolveColor);
|
|
430
|
-
return gradient ? {
|
|
431
|
-
kind: "gradient",
|
|
432
|
-
gradient
|
|
433
|
-
} : { kind: "none" };
|
|
434
|
-
}
|
|
1714
|
+
const own = fillFromNode(child, resolveColor, resolveImage);
|
|
1715
|
+
if (own) return own;
|
|
435
1716
|
}
|
|
436
1717
|
return { kind: "none" };
|
|
437
1718
|
}
|
|
438
1719
|
/**
|
|
1720
|
+
* One fill ELEMENT — `a:noFill` / `a:solidFill` / `a:gradFill` / `a:blipFill` —
|
|
1721
|
+
* as a {@link ShapeFill}. The same reader serves a shape's own `a:spPr` and the
|
|
1722
|
+
* theme slot a `<a:fillRef>` names, which is a fill in its own right.
|
|
1723
|
+
*
|
|
1724
|
+
* @param child The candidate node (anything else returns undefined).
|
|
1725
|
+
* @param resolveColor The colour resolver (with `phClr` bound, for a theme slot).
|
|
1726
|
+
* @param resolveImage Resolver for a picture fill's relationship.
|
|
1727
|
+
* @returns The fill, or undefined when the node is not one.
|
|
1728
|
+
*/
|
|
1729
|
+
function fillFromNode(child, resolveColor, resolveImage) {
|
|
1730
|
+
if (poIs(child, "a:noFill")) return { kind: "none" };
|
|
1731
|
+
if (poIs(child, "a:blipFill")) {
|
|
1732
|
+
const blip = poFindDescendant(child, "a:blip");
|
|
1733
|
+
const relId = blip ? poAttr(blip, "embed") : void 0;
|
|
1734
|
+
const resource = relId !== void 0 ? resolveImage?.(relId) : void 0;
|
|
1735
|
+
if (resource === void 0) return { kind: "none" };
|
|
1736
|
+
const crop = parseSrcRect(poFindDescendant(child, "a:srcRect")) ?? fillRectCrop(child);
|
|
1737
|
+
return {
|
|
1738
|
+
kind: "picture",
|
|
1739
|
+
imageResource: resource,
|
|
1740
|
+
...crop ? { imageCrop: crop } : {}
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
if (poIs(child, "a:solidFill")) {
|
|
1744
|
+
const hex = colorFromContainer(child, resolveColor);
|
|
1745
|
+
return hex ? {
|
|
1746
|
+
kind: "solid",
|
|
1747
|
+
colorHex: hex
|
|
1748
|
+
} : { kind: "none" };
|
|
1749
|
+
}
|
|
1750
|
+
if (poIs(child, "a:pattFill")) {
|
|
1751
|
+
const fg = poChildren(child).find((c) => poIs(c, "a:fgClr"));
|
|
1752
|
+
const bg = poChildren(child).find((c) => poIs(c, "a:bgClr"));
|
|
1753
|
+
const fgHex = fg ? colorFromContainer(fg, resolveColor) : void 0;
|
|
1754
|
+
const bgHex = bg ? colorFromContainer(bg, resolveColor) : "FFFFFF";
|
|
1755
|
+
if (!fgHex) return { kind: "none" };
|
|
1756
|
+
return {
|
|
1757
|
+
kind: "solid",
|
|
1758
|
+
colorHex: blendHex(fgHex, bgHex ?? "FFFFFF", patternCoverage(poAttr(child, "prst")))
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
if (poIs(child, "a:gradFill")) {
|
|
1762
|
+
const gradient = parseGradient(child, resolveColor);
|
|
1763
|
+
return gradient ? {
|
|
1764
|
+
kind: "gradient",
|
|
1765
|
+
gradient
|
|
1766
|
+
} : { kind: "none" };
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* §20.1.8.37 — roughly how much of a tile the preset pattern inks, which is
|
|
1771
|
+
* what decides how strongly the foreground colour shows through. The families
|
|
1772
|
+
* carry the answer: `pctNN` states it outright, `lt*` is sparse, `dk*` dense,
|
|
1773
|
+
* and the woven ones cover about half.
|
|
1774
|
+
*
|
|
1775
|
+
* @param prst The `a:pattFill` `@prst` value.
|
|
1776
|
+
* @returns The ink coverage, 0..1.
|
|
1777
|
+
*/
|
|
1778
|
+
function patternCoverage(prst) {
|
|
1779
|
+
if (!prst) return .25;
|
|
1780
|
+
const pct = /^pct(\d+)$/u.exec(prst);
|
|
1781
|
+
if (pct) return Math.min(1, Number(pct[1]) / 100);
|
|
1782
|
+
if (prst.startsWith("lt")) return .15;
|
|
1783
|
+
if (prst.startsWith("dk")) return .5;
|
|
1784
|
+
if (prst.startsWith("wd")) return .2;
|
|
1785
|
+
if (prst.startsWith("nar")) return .35;
|
|
1786
|
+
if (prst.endsWith("Grid") || prst === "divot" || prst === "wave") return .15;
|
|
1787
|
+
if (DENSE_PATTERNS.has(prst)) return .5;
|
|
1788
|
+
return .25;
|
|
1789
|
+
}
|
|
1790
|
+
var DENSE_PATTERNS = new Set([
|
|
1791
|
+
"trellis",
|
|
1792
|
+
"weave",
|
|
1793
|
+
"plaid",
|
|
1794
|
+
"smCheck",
|
|
1795
|
+
"lgCheck",
|
|
1796
|
+
"solidDmnd",
|
|
1797
|
+
"sphere",
|
|
1798
|
+
"dkCross",
|
|
1799
|
+
"dkDiagCross"
|
|
1800
|
+
]);
|
|
1801
|
+
/** `a` mixed over `b` at `t` (0 = all `b`, 1 = all `a`), as 6-hex. */
|
|
1802
|
+
function blendHex(a, b, t) {
|
|
1803
|
+
const ch = (hex, i) => parseInt(hex.slice(i, i + 2), 16);
|
|
1804
|
+
const mix = (i) => Math.round(ch(a, i) * t + ch(b, i) * (1 - t)).toString(16).padStart(2, "0");
|
|
1805
|
+
return (mix(0) + mix(2) + mix(4)).toUpperCase();
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* §20.1.8.30 `a:stretch/a:fillRect` — where the picture's edges land relative
|
|
1809
|
+
* to the shape's box, in 1000ths of a percent of that box. A NEGATIVE inset
|
|
1810
|
+
* pushes the edge outside the box, which is how a fill zooms in: what remains
|
|
1811
|
+
* inside is the part the box shows, so the insets convert straight to the crop
|
|
1812
|
+
* fractions of the SOURCE. crop-roundtrip.docx frames the middle of its photo
|
|
1813
|
+
* that way and we drew the whole of it.
|
|
1814
|
+
*
|
|
1815
|
+
* @param blipFill The `a:blipFill` node.
|
|
1816
|
+
* @returns The crop, or `undefined` when the fill states no `a:fillRect`.
|
|
1817
|
+
*/
|
|
1818
|
+
function fillRectCrop(blipFill) {
|
|
1819
|
+
const stretch = poChildren(blipFill).find((c) => poIs(c, "a:stretch"));
|
|
1820
|
+
const rect = stretch ? poChildren(stretch).find((c) => poIs(c, "a:fillRect")) : void 0;
|
|
1821
|
+
if (!rect) return void 0;
|
|
1822
|
+
const side = (name) => (poIntAttr(rect, name) ?? 0) / 1e5;
|
|
1823
|
+
const l = side("l");
|
|
1824
|
+
const t = side("t");
|
|
1825
|
+
const r = side("r");
|
|
1826
|
+
const b = side("b");
|
|
1827
|
+
if (l === 0 && t === 0 && r === 0 && b === 0) return void 0;
|
|
1828
|
+
const spanX = 1 - l - r;
|
|
1829
|
+
const spanY = 1 - t - b;
|
|
1830
|
+
if (spanX <= 0 || spanY <= 0) return void 0;
|
|
1831
|
+
const clamp = (v) => Math.min(.99, Math.max(0, v));
|
|
1832
|
+
return {
|
|
1833
|
+
left: clamp(-l / spanX),
|
|
1834
|
+
top: clamp(-t / spanY),
|
|
1835
|
+
right: clamp(-r / spanX),
|
|
1836
|
+
bottom: clamp(-b / spanY)
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
|
|
1841
|
+
* `a:outerShdw`).
|
|
1842
|
+
*
|
|
1843
|
+
* The spec states the displacement in polar form: `dist` in EMU and `dir` in
|
|
1844
|
+
* 60 000ths of a degree, measured clockwise from due east in a frame whose y
|
|
1845
|
+
* grows DOWNWARD — so the standard 2 700 000 (45°) puts the shadow down and to
|
|
1846
|
+
* the right, which is where every reader draws it.
|
|
1847
|
+
*
|
|
1848
|
+
* @param spPr The shape's `spPr` node.
|
|
1849
|
+
* @param resolveColor Resolver for theme/scheme colours.
|
|
1850
|
+
* @returns The shadow, or undefined when the shape declares none.
|
|
1851
|
+
*/
|
|
1852
|
+
function parseShadow(spPr, resolveColor) {
|
|
1853
|
+
const list = poChildren(spPr).find((c) => poIs(c, "a:effectLst"));
|
|
1854
|
+
const shdw = list ? poChildren(list).find((c) => poIs(c, "a:outerShdw")) : void 0;
|
|
1855
|
+
return shdw ? shadowFromOuterShdw(shdw, resolveColor) : void 0;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Build a {@link ShapeShadow} from an `a:outerShdw` node.
|
|
1859
|
+
*
|
|
1860
|
+
* @param shdw The `a:outerShdw` node.
|
|
1861
|
+
* @param resolveColor Resolver for theme/scheme colours.
|
|
1862
|
+
* @returns The shadow, or undefined when its colour will not resolve.
|
|
1863
|
+
*/
|
|
1864
|
+
function shadowFromOuterShdw(shdw, resolveColor) {
|
|
1865
|
+
const colorNode = poChildren(shdw).find((c) => resolveColorNode(c, resolveColor) !== void 0);
|
|
1866
|
+
const colorHex = colorNode ? resolveColorNode(colorNode, resolveColor) : void 0;
|
|
1867
|
+
if (!colorHex) return void 0;
|
|
1868
|
+
const alphaMod = colorNode ? readColorMods(colorNode).find((m) => m.kind === "alpha") : void 0;
|
|
1869
|
+
const dist = emuToPt(poIntAttr(shdw, "dist") ?? 0);
|
|
1870
|
+
const rad = (poIntAttr(shdw, "dir") ?? 0) / 6e4 * Math.PI / 180;
|
|
1871
|
+
return {
|
|
1872
|
+
dxPt: dist * Math.cos(rad),
|
|
1873
|
+
dyPt: dist * Math.sin(rad),
|
|
1874
|
+
blurPt: emuToPt(poIntAttr(shdw, "blurRad") ?? 0),
|
|
1875
|
+
colorHex,
|
|
1876
|
+
alpha: alphaMod ? alphaMod.val : 1
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
439
1880
|
* Parse a shape's outline (`a:ln`) from its `a:spPr` into a {@link ShapeLine}:
|
|
440
1881
|
* width, cap, solid colour, dash pattern, and an explicit `a:noFill` (an unstroked
|
|
441
1882
|
* outline). Returns `undefined` when the shape has no `a:ln`.
|
|
@@ -452,17 +1893,94 @@ function parseLine(spPr, resolveColor) {
|
|
|
452
1893
|
let noFill = false;
|
|
453
1894
|
let colorHex;
|
|
454
1895
|
let dash;
|
|
1896
|
+
let customDash;
|
|
1897
|
+
let headEnd;
|
|
1898
|
+
let tailEnd;
|
|
455
1899
|
for (const c of poChildren(ln)) if (poIs(c, "a:noFill")) noFill = true;
|
|
456
1900
|
else if (poIs(c, "a:solidFill")) colorHex = colorFromContainer(c, resolveColor);
|
|
457
1901
|
else if (poIs(c, "a:prstDash")) dash = normalizeDash(poAttr(c, "val"));
|
|
1902
|
+
else if (poIs(c, "a:custDash")) customDash = parseCustDash(c);
|
|
1903
|
+
else if (poIs(c, "a:headEnd")) headEnd = parseLineEnd(c);
|
|
1904
|
+
else if (poIs(c, "a:tailEnd")) tailEnd = parseLineEnd(c);
|
|
458
1905
|
return {
|
|
459
1906
|
...widthEmu !== void 0 ? { width: emuToPt(widthEmu) } : {},
|
|
460
1907
|
...colorHex ? { colorHex } : {},
|
|
461
1908
|
...dash ? { dash } : {},
|
|
1909
|
+
...customDash && customDash.length > 0 ? { customDash } : {},
|
|
462
1910
|
...cap ? { cap } : {},
|
|
463
|
-
...noFill ? { fill: "none" } : {}
|
|
1911
|
+
...noFill ? { fill: "none" } : {},
|
|
1912
|
+
...headEnd ? { headEnd } : {},
|
|
1913
|
+
...tailEnd ? { tailEnd } : {}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* A picture's frame from the line its `pic:spPr` (or its VML shape) states —
|
|
1918
|
+
* colour and width only, since a picture border is a rule around the box and
|
|
1919
|
+
* carries none of a shape outline's ends or joins.
|
|
1920
|
+
*
|
|
1921
|
+
* @param line The parsed line, or `undefined`.
|
|
1922
|
+
* @returns The outline, or `undefined` when the picture is drawn bare.
|
|
1923
|
+
*/
|
|
1924
|
+
function pictureOutline(line) {
|
|
1925
|
+
const stroke = buildStroke(line);
|
|
1926
|
+
if (!stroke || stroke.widthPt <= 0) return void 0;
|
|
1927
|
+
return {
|
|
1928
|
+
colorHex: stroke.colorHex,
|
|
1929
|
+
widthPt: pt(stroke.widthPt)
|
|
464
1930
|
};
|
|
465
1931
|
}
|
|
1932
|
+
var LINE_END_TYPES = new Set([
|
|
1933
|
+
"triangle",
|
|
1934
|
+
"stealth",
|
|
1935
|
+
"diamond",
|
|
1936
|
+
"oval",
|
|
1937
|
+
"arrow"
|
|
1938
|
+
]);
|
|
1939
|
+
var LINE_END_SIZES = new Set([
|
|
1940
|
+
"sm",
|
|
1941
|
+
"med",
|
|
1942
|
+
"lg"
|
|
1943
|
+
]);
|
|
1944
|
+
/**
|
|
1945
|
+
* §20.1.8.24 / §20.1.8.42 — one end decoration: `@type` plus the `@w`/`@len`
|
|
1946
|
+
* size steps. `none` (and an unknown type) leaves the end bare.
|
|
1947
|
+
*
|
|
1948
|
+
* @param node The `a:headEnd` / `a:tailEnd` node.
|
|
1949
|
+
* @returns The parsed end, or `undefined` when nothing is drawn there.
|
|
1950
|
+
*/
|
|
1951
|
+
function parseLineEnd(node) {
|
|
1952
|
+
const type = poAttr(node, "type");
|
|
1953
|
+
if (type === void 0 || !LINE_END_TYPES.has(type)) return void 0;
|
|
1954
|
+
const w = poAttr(node, "w");
|
|
1955
|
+
const len = poAttr(node, "len");
|
|
1956
|
+
const size = (v) => v !== void 0 && LINE_END_SIZES.has(v) ? v : void 0;
|
|
1957
|
+
const width = size(w);
|
|
1958
|
+
const length = size(len);
|
|
1959
|
+
return {
|
|
1960
|
+
type,
|
|
1961
|
+
...width ? { width } : {},
|
|
1962
|
+
...length ? { length } : {}
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* §20.1.8.21 `a:custDash` — the dash/space lengths of the author's own
|
|
1967
|
+
* pattern. Each `a:ds` states them in 1000ths of a percent of the line's
|
|
1968
|
+
* width, so the numbers here are plain multiples of that width.
|
|
1969
|
+
*
|
|
1970
|
+
* @param custDash The `a:custDash` node.
|
|
1971
|
+
* @returns The pattern as [dash, space, dash, space, …], or `undefined`.
|
|
1972
|
+
*/
|
|
1973
|
+
function parseCustDash(custDash) {
|
|
1974
|
+
const out = [];
|
|
1975
|
+
for (const ds of poChildren(custDash)) {
|
|
1976
|
+
if (!poIs(ds, "a:ds")) continue;
|
|
1977
|
+
const d = (poIntAttr(ds, "d") ?? 0) / 1e5;
|
|
1978
|
+
const sp = (poIntAttr(ds, "sp") ?? 0) / 1e5;
|
|
1979
|
+
if (d <= 0 && sp <= 0) continue;
|
|
1980
|
+
out.push(Math.max(0, d), Math.max(0, sp));
|
|
1981
|
+
}
|
|
1982
|
+
return out.length > 0 ? out : void 0;
|
|
1983
|
+
}
|
|
466
1984
|
var DASH_VALUES = new Set([
|
|
467
1985
|
"solid",
|
|
468
1986
|
"dot",
|
|
@@ -506,10 +2024,28 @@ function parseGradient(grad, resolveColor) {
|
|
|
506
2024
|
}
|
|
507
2025
|
if (stops.length === 0) return void 0;
|
|
508
2026
|
stops.sort((a, b) => a.offset - b.offset);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
2027
|
+
const path = poChildren(grad).find((c) => poIs(c, "a:path"));
|
|
2028
|
+
if (path) {
|
|
2029
|
+
const rect = poChildren(path).find((c) => poIs(c, "a:fillToRect"));
|
|
2030
|
+
const side = (name) => {
|
|
2031
|
+
const v = rect ? poIntAttr(rect, name) : void 0;
|
|
2032
|
+
return v === void 0 ? void 0 : v / 1e5;
|
|
2033
|
+
};
|
|
2034
|
+
const l = side("l");
|
|
2035
|
+
const r = side("r");
|
|
2036
|
+
const t = side("t");
|
|
2037
|
+
const b = side("b");
|
|
2038
|
+
const center = l !== void 0 || r !== void 0 || t !== void 0 || b !== void 0 ? {
|
|
2039
|
+
x: clampUnit(((l ?? 0) + 1 - (r ?? 0)) / 2),
|
|
2040
|
+
y: clampUnit(((t ?? 0) + 1 - (b ?? 0)) / 2)
|
|
2041
|
+
} : void 0;
|
|
2042
|
+
return {
|
|
2043
|
+
kind: "radial",
|
|
2044
|
+
stops,
|
|
2045
|
+
...poAttr(path, "path") === "rect" ? { sweep: "rect" } : {},
|
|
2046
|
+
...center ? { center } : {}
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
513
2049
|
const lin = poChildren(grad).find((c) => poIs(c, "a:lin"));
|
|
514
2050
|
const ang = lin ? poIntAttr(lin, "ang") : void 0;
|
|
515
2051
|
return {
|
|
@@ -522,4 +2058,4 @@ function clampUnit(x) {
|
|
|
522
2058
|
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
523
2059
|
}
|
|
524
2060
|
//#endregion
|
|
525
|
-
export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseVmlPicture };
|
|
2061
|
+
export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseShadow, parseVmlPicture, parseXfrm, shadowFromOuterShdw, vmlFill };
|