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