reamkit 1.16.0 → 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.
Files changed (65) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/project.js +5 -1
  3. package/dist/esm/core/document-model/index.d.ts +1 -1
  4. package/dist/esm/core/document-model/types.d.ts +473 -7
  5. package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
  6. package/dist/esm/core/drawingml/chart-geometry.js +58 -9
  7. package/dist/esm/core/drawingml/chart-parser.js +44 -5
  8. package/dist/esm/core/drawingml/colors.d.ts +1 -1
  9. package/dist/esm/core/drawingml/colors.js +27 -1
  10. package/dist/esm/core/drawingml/preset-geometry.js +233 -0
  11. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  12. package/dist/esm/core/drawingml/shape-render.js +116 -3
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +19 -1
  15. package/dist/esm/core/font/measure.d.ts +5 -0
  16. package/dist/esm/core/font/measure.js +21 -1
  17. package/dist/esm/core/images.d.ts +2 -2
  18. package/dist/esm/core/images.js +181 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/numbering/apply.d.ts +5 -3
  21. package/dist/esm/core/numbering/apply.js +42 -11
  22. package/dist/esm/core/numbering/index.js +2 -0
  23. package/dist/esm/core/numbering/state.d.ts +13 -1
  24. package/dist/esm/core/numbering/state.js +256 -15
  25. package/dist/esm/core/po-helpers.js +16 -1
  26. package/dist/esm/core/style-cascade/resolver.js +66 -11
  27. package/dist/esm/core/style-cascade/table.js +35 -11
  28. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  29. package/dist/esm/core/style-cascade/types.js +4 -0
  30. package/dist/esm/core/vector.d.ts +17 -0
  31. package/dist/esm/excel/print-model.js +1 -1
  32. package/dist/esm/excel/sheet-to-flow.js +10 -8
  33. package/dist/esm/excel/worksheet-parser.js +1 -1
  34. package/dist/esm/html/html-writer.js +18 -4
  35. package/dist/esm/layout/page-doc.d.ts +62 -2
  36. package/dist/esm/layout/styled-layout.d.ts +44 -2
  37. package/dist/esm/layout/styled-layout.js +1870 -228
  38. package/dist/esm/pdf/shading.js +76 -6
  39. package/dist/esm/pdf/styled-page-emitter.js +217 -43
  40. package/dist/esm/pdf/vector-graphics.d.ts +28 -1
  41. package/dist/esm/pdf/vector-graphics.js +94 -11
  42. package/dist/esm/pptx/pptx-reader.js +1 -0
  43. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  44. package/dist/esm/pptx/slide-parser.js +86 -23
  45. package/dist/esm/svg/svg-writer.js +1 -1
  46. package/dist/esm/word/document-parser.d.ts +96 -7
  47. package/dist/esm/word/document-parser.js +537 -116
  48. package/dist/esm/word/docx-reader.js +135 -43
  49. package/dist/esm/word/docx-writer.js +6 -1
  50. package/dist/esm/word/drawing-parser.d.ts +53 -5
  51. package/dist/esm/word/drawing-parser.js +1540 -54
  52. package/dist/esm/word/index.d.ts +4 -4
  53. package/dist/esm/word/numbering-parser.d.ts +2 -1
  54. package/dist/esm/word/numbering-parser.js +107 -21
  55. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  56. package/dist/esm/word/paragraph-properties.js +219 -10
  57. package/dist/esm/word/run-properties.js +45 -1
  58. package/dist/esm/word/settings-parser.d.ts +24 -0
  59. package/dist/esm/word/settings-parser.js +31 -5
  60. package/dist/esm/word/shading.d.ts +11 -0
  61. package/dist/esm/word/shading.js +64 -0
  62. package/dist/esm/word/styles-parser.js +8 -2
  63. package/dist/esm/word/table-parser.d.ts +9 -1
  64. package/dist/esm/word/table-parser.js +182 -30
  65. package/package.json +1 -1
@@ -1,11 +1,25 @@
1
1
  import { emuToPt, pt } from "../core/ir/units.js";
2
- import { poAttr, poChildren, poFindDescendant, poIntAttr, poIs, poTag, poText } from "../core/po-helpers.js";
3
- import { readColorMods, resolveColorNode } from "../core/drawingml/colors.js";
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 CHART_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
7
- var DIAGRAM_URI = "http://schemas.openxmlformats.org/drawingml/2006/diagram";
8
- var UNDERSTOOD_NS = new Set(["wps"]);
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
- return data ? {
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
- } : null;
224
+ };
225
+ }
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
+ };
124
269
  }
125
- if (graphicData && uri === CHART_URI) {
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 === DIAGRAM_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) return {
152
- kind: "image",
153
- imageId: rId,
154
- width: emuToPt(extentCx),
155
- height: emuToPt(extentCy),
156
- ...alt
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 null;
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
- function parseWsp(graphicData, extentCx, extentCy, resolveColor, parseBody) {
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
- const spPr = poChildren(wsp).find((c) => poIs(c, "wps:spPr"));
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 text = parseBody ? parseTextBox(wsp, parseBody) : void 0;
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
- ...text ? { text } : {}
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) } : {},
@@ -425,27 +1709,134 @@ var firstPt = (node) => pts(node)[0];
425
1709
  * @param spPr The `wps:spPr` node.
426
1710
  * @param resolveColor Resolver for theme/scheme colours.
427
1711
  */
428
- function parseFill(spPr, resolveColor) {
1712
+ function parseFill(spPr, resolveColor, resolveImage) {
429
1713
  for (const child of poChildren(spPr)) {
430
- if (poIs(child, "a:noFill")) return { kind: "none" };
431
- if (poIs(child, "a:solidFill")) {
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
- }
1714
+ const own = fillFromNode(child, resolveColor, resolveImage);
1715
+ if (own) return own;
445
1716
  }
446
1717
  return { kind: "none" };
447
1718
  }
448
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
+ /**
449
1840
  * Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
450
1841
  * `a:outerShdw`).
451
1842
  *
@@ -502,17 +1893,94 @@ function parseLine(spPr, resolveColor) {
502
1893
  let noFill = false;
503
1894
  let colorHex;
504
1895
  let dash;
1896
+ let customDash;
1897
+ let headEnd;
1898
+ let tailEnd;
505
1899
  for (const c of poChildren(ln)) if (poIs(c, "a:noFill")) noFill = true;
506
1900
  else if (poIs(c, "a:solidFill")) colorHex = colorFromContainer(c, resolveColor);
507
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);
508
1905
  return {
509
1906
  ...widthEmu !== void 0 ? { width: emuToPt(widthEmu) } : {},
510
1907
  ...colorHex ? { colorHex } : {},
511
1908
  ...dash ? { dash } : {},
1909
+ ...customDash && customDash.length > 0 ? { customDash } : {},
512
1910
  ...cap ? { cap } : {},
513
- ...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)
1930
+ };
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 } : {}
514
1963
  };
515
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
+ }
516
1984
  var DASH_VALUES = new Set([
517
1985
  "solid",
518
1986
  "dot",
@@ -556,10 +2024,28 @@ function parseGradient(grad, resolveColor) {
556
2024
  }
557
2025
  if (stops.length === 0) return void 0;
558
2026
  stops.sort((a, b) => a.offset - b.offset);
559
- if (poChildren(grad).some((c) => poIs(c, "a:path"))) return {
560
- kind: "radial",
561
- stops
562
- };
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
+ }
563
2049
  const lin = poChildren(grad).find((c) => poIs(c, "a:lin"));
564
2050
  const ang = lin ? poIntAttr(lin, "ang") : void 0;
565
2051
  return {
@@ -572,4 +2058,4 @@ function clampUnit(x) {
572
2058
  return x < 0 ? 0 : x > 1 ? 1 : x;
573
2059
  }
574
2060
  //#endregion
575
- export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseShadow, parseVmlPicture, parseXfrm, shadowFromOuterShdw };
2061
+ export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseShadow, parseVmlPicture, parseXfrm, shadowFromOuterShdw, vmlFill };