reamkit 1.15.4 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/facade.d.ts +5 -0
  3. package/dist/esm/core/converter/project.js +5 -1
  4. package/dist/esm/core/converter/ream.d.ts +6 -0
  5. package/dist/esm/core/converter/ream.js +34 -8
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +659 -7
  8. package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
  9. package/dist/esm/core/drawingml/chart-geometry.js +424 -92
  10. package/dist/esm/core/drawingml/chart-parser.js +301 -11
  11. package/dist/esm/core/drawingml/colors.d.ts +7 -7
  12. package/dist/esm/core/drawingml/colors.js +84 -18
  13. package/dist/esm/core/drawingml/preset-geometry.js +240 -6
  14. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  15. package/dist/esm/core/drawingml/shape-render.js +116 -3
  16. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  17. package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
  18. package/dist/esm/core/drawingml/theme-parser.js +92 -1
  19. package/dist/esm/core/font/measure.d.ts +5 -0
  20. package/dist/esm/core/font/measure.js +21 -1
  21. package/dist/esm/core/font/ttf-subset.js +26 -1
  22. package/dist/esm/core/images.d.ts +2 -2
  23. package/dist/esm/core/images.js +353 -43
  24. package/dist/esm/core/indexed-colors.d.ts +11 -0
  25. package/dist/esm/core/indexed-colors.js +81 -0
  26. package/dist/esm/core/ir/flow.d.ts +23 -1
  27. package/dist/esm/core/ir/sheet.d.ts +61 -0
  28. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  29. package/dist/esm/core/number-format.js +800 -0
  30. package/dist/esm/core/numbering/apply.d.ts +5 -3
  31. package/dist/esm/core/numbering/apply.js +42 -11
  32. package/dist/esm/core/numbering/index.js +2 -0
  33. package/dist/esm/core/numbering/state.d.ts +13 -1
  34. package/dist/esm/core/numbering/state.js +256 -15
  35. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  36. package/dist/esm/core/opc/alternate-content.js +72 -0
  37. package/dist/esm/core/opc/core-properties.js +1 -0
  38. package/dist/esm/core/opc/package.d.ts +14 -1
  39. package/dist/esm/core/opc/package.js +43 -4
  40. package/dist/esm/core/opc/relationships.js +1 -0
  41. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  42. package/dist/esm/core/opc/xml-entities.js +91 -0
  43. package/dist/esm/core/po-helpers.js +16 -1
  44. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  45. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  46. package/dist/esm/core/style-cascade/resolver.js +66 -11
  47. package/dist/esm/core/style-cascade/table.js +35 -11
  48. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  49. package/dist/esm/core/style-cascade/types.js +4 -0
  50. package/dist/esm/core/vector.d.ts +30 -0
  51. package/dist/esm/excel/activex-parser.js +1 -0
  52. package/dist/esm/excel/column-bands.d.ts +26 -1
  53. package/dist/esm/excel/column-bands.js +156 -20
  54. package/dist/esm/excel/comments-parser.js +5 -3
  55. package/dist/esm/excel/conditional-format.d.ts +25 -2
  56. package/dist/esm/excel/conditional-format.js +63 -19
  57. package/dist/esm/excel/escaped-text.d.ts +9 -0
  58. package/dist/esm/excel/escaped-text.js +20 -0
  59. package/dist/esm/excel/form-control-parser.js +1 -0
  60. package/dist/esm/excel/formula/dates.js +1 -1
  61. package/dist/esm/excel/header-footer.d.ts +9 -1
  62. package/dist/esm/excel/header-footer.js +162 -14
  63. package/dist/esm/excel/index.d.ts +1 -1
  64. package/dist/esm/excel/index.js +2 -2
  65. package/dist/esm/excel/pivot-table-parser.js +1 -0
  66. package/dist/esm/excel/print-model.d.ts +75 -0
  67. package/dist/esm/excel/print-model.js +899 -77
  68. package/dist/esm/excel/printer-settings.d.ts +16 -0
  69. package/dist/esm/excel/printer-settings.js +36 -0
  70. package/dist/esm/excel/rich-value.d.ts +13 -0
  71. package/dist/esm/excel/rich-value.js +120 -0
  72. package/dist/esm/excel/shared-strings-parser.js +11 -7
  73. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  74. package/dist/esm/excel/sheet-drawing.js +26 -3
  75. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  76. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  77. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  78. package/dist/esm/excel/sheet-to-flow.js +553 -36
  79. package/dist/esm/excel/slicer-parser.js +1 -0
  80. package/dist/esm/excel/styles-parser.d.ts +30 -2
  81. package/dist/esm/excel/styles-parser.js +177 -40
  82. package/dist/esm/excel/table-parser.js +1 -0
  83. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  84. package/dist/esm/excel/vml-drawing.js +158 -0
  85. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  86. package/dist/esm/excel/workbook-parser.js +5 -1
  87. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  88. package/dist/esm/excel/worksheet-parser.js +150 -61
  89. package/dist/esm/excel/xlsx-reader.js +242 -11
  90. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  91. package/dist/esm/html/html-writer.js +29 -6
  92. package/dist/esm/layout/page-doc.d.ts +87 -1
  93. package/dist/esm/layout/styled-layout.d.ts +46 -2
  94. package/dist/esm/layout/styled-layout.js +2345 -274
  95. package/dist/esm/pdf/cid-font.js +20 -9
  96. package/dist/esm/pdf/objects.d.ts +13 -0
  97. package/dist/esm/pdf/objects.js +18 -1
  98. package/dist/esm/pdf/shading.js +76 -6
  99. package/dist/esm/pdf/styled-page-emitter.js +355 -49
  100. package/dist/esm/pdf/text-page-renderer.js +1 -0
  101. package/dist/esm/pdf/vector-graphics.d.ts +31 -2
  102. package/dist/esm/pdf/vector-graphics.js +103 -3
  103. package/dist/esm/pptx/pptx-reader.js +2 -0
  104. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  105. package/dist/esm/pptx/slide-parser.js +98 -24
  106. package/dist/esm/svg/svg-writer.js +23 -4
  107. package/dist/esm/word/document-parser.d.ts +96 -7
  108. package/dist/esm/word/document-parser.js +538 -116
  109. package/dist/esm/word/docx-reader.js +135 -43
  110. package/dist/esm/word/docx-writer.js +6 -1
  111. package/dist/esm/word/drawing-parser.d.ts +86 -5
  112. package/dist/esm/word/drawing-parser.js +1590 -54
  113. package/dist/esm/word/index.d.ts +4 -4
  114. package/dist/esm/word/numbering-parser.d.ts +2 -1
  115. package/dist/esm/word/numbering-parser.js +108 -21
  116. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  117. package/dist/esm/word/paragraph-properties.js +219 -10
  118. package/dist/esm/word/run-properties.js +45 -1
  119. package/dist/esm/word/settings-parser.d.ts +24 -0
  120. package/dist/esm/word/settings-parser.js +32 -5
  121. package/dist/esm/word/shading.d.ts +11 -0
  122. package/dist/esm/word/shading.js +64 -0
  123. package/dist/esm/word/styles-parser.js +9 -2
  124. package/dist/esm/word/table-parser.d.ts +9 -1
  125. package/dist/esm/word/table-parser.js +182 -30
  126. package/package.json +7 -3
  127. package/dist/esm/excel/number-format.js +0 -476
@@ -4,6 +4,7 @@ import { XMLParser } from "fast-xml-parser";
4
4
  //#region src/core/drawingml/chart-parser.ts
5
5
  var decoder = new TextDecoder("utf-8");
6
6
  var parser = new XMLParser({
7
+ htmlEntities: true,
7
8
  ignoreAttributes: false,
8
9
  attributeNamePrefix: "@_",
9
10
  preserveOrder: true,
@@ -37,35 +38,90 @@ var TYPE_OF_TAG = {
37
38
  * @returns The parsed chart, or `null` when there is no `c:chart` / `c:plotArea`.
38
39
  */
39
40
  function parseChart(chartXml, resolveColor) {
40
- const chart = poFindByPath(parser.parse(decoder.decode(chartXml)), ["c:chartSpace", "c:chart"]);
41
+ const tree = parser.parse(decoder.decode(chartXml));
42
+ const chart = poFindByPath(tree, ["c:chartSpace", "c:chart"]);
41
43
  if (!chart) return null;
42
44
  const plotArea = poChildren(chart).find((c) => poIs(c, "c:plotArea"));
43
45
  if (!plotArea) return null;
44
- const group = poChildren(plotArea).find((c) => (poTag(c) ?? "") in TYPE_OF_TAG);
46
+ const groups = poChildren(plotArea).filter((c) => (poTag(c) ?? "") in TYPE_OF_TAG);
47
+ const group = groups[0];
45
48
  const type = group ? TYPE_OF_TAG[poTag(group)] ?? "unknown" : "unknown";
46
- const serNodes = group ? poChildren(group).filter((c) => poIs(c, "c:ser")) : [];
47
- const series = serNodes.map((s) => parseSeries(s, resolveColor));
49
+ const groupAxIds = (g) => poChildren(g).filter((c) => poIs(c, "c:axId")).map((c) => poAttr(c, "val") ?? "");
50
+ const primaryAxIds = new Set(group ? groupAxIds(group) : []);
51
+ const serNodes = [];
52
+ const series = [];
53
+ let secondaryValAxId;
54
+ for (const g of groups) {
55
+ const groupType = TYPE_OF_TAG[poTag(g)] ?? "unknown";
56
+ const own = groupAxIds(g).filter((id) => !primaryAxIds.has(id));
57
+ const secondary = own.length > 0;
58
+ for (const s of poChildren(g).filter((c) => poIs(c, "c:ser"))) {
59
+ serNodes.push(s);
60
+ series.push({
61
+ ...parseSeries(s, resolveColor),
62
+ ...groupType === type ? {} : { type: groupType },
63
+ ...secondary ? { secondaryAxis: true } : {}
64
+ });
65
+ }
66
+ if (secondary && series.length > 0) secondaryValAxId ??= own.find((id) => poChildren(plotArea).some((c) => poIs(c, "c:valAx") && poChildren(c).some((k) => poIs(k, "c:axId") && poAttr(k, "val") === id)));
67
+ }
68
+ const secondaryValAx = secondaryValAxId ? poChildren(plotArea).find((c) => poIs(c, "c:valAx") && poChildren(c).some((k) => poIs(k, "c:axId") && poAttr(k, "val") === secondaryValAxId)) : void 0;
69
+ const secondaryDeleted = secondaryValAx !== void 0 && poChildren(secondaryValAx).some((c) => poIs(c, "c:delete") && poAttr(c, "val") === "1");
70
+ const secondaryTitleNode = secondaryValAx && !secondaryDeleted ? poChildren(secondaryValAx).find((c) => poIs(c, "c:title")) : void 0;
71
+ const secondaryValAxisTitle = secondaryTitleNode ? collectAT(secondaryTitleNode) || "Axis Title" : void 0;
48
72
  let categories = [];
73
+ let categoriesRef;
49
74
  for (const s of serNodes) {
50
75
  const cat = poChildren(s).find((c) => poIs(c, "c:cat"));
51
76
  if (cat) {
52
77
  categories = denseStrings(cat);
78
+ categoriesRef ??= refFormula(cat);
53
79
  break;
54
80
  }
55
81
  }
82
+ const scatterStyleRaw = group ? poVal(poChildren(group).find((c) => poIs(c, "c:scatterStyle"))) : void 0;
83
+ const scatterStyle = SCATTER_STYLES.has(scatterStyleRaw ?? "") ? scatterStyleRaw : void 0;
84
+ const lineMarkers = group && poIs(group, "c:lineChart") ? poVal(poChildren(group).find((c) => poIs(c, "c:marker"))) === "1" : false;
56
85
  const barDir = group ? poVal(poChildren(group).find((c) => poIs(c, "c:barDir"))) : void 0;
57
86
  const grouping = group ? poVal(poChildren(group).find((c) => poIs(c, "c:grouping"))) : void 0;
58
87
  const doughnut = group ? poIs(group, "c:doughnutChart") : false;
59
88
  const showValues = group ? chartShowsValues(group) : false;
60
89
  const catAxisTitle = axisTitle(plotArea, "c:catAx");
61
90
  const valAxisTitle = axisTitle(plotArea, "c:valAx");
91
+ const catAxNode = poChildren(plotArea).find((c) => poIs(c, "c:catAx"));
92
+ const valAxNode = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
93
+ const axisAt = (...where) => poChildren(plotArea).find((c) => (poIs(c, "c:catAx") || poIs(c, "c:valAx") || poIs(c, "c:dateAx")) && c !== secondaryValAx && where.includes(poVal(poChildren(c).find((k) => poIs(k, "c:axPos"))) ?? ""));
94
+ const bottomAxNode = axisAt("b", "t") ?? catAxNode;
95
+ const leftAxNode = axisAt("l", "r") ?? valAxNode;
96
+ const catAxisLine = lineStyleOf(bottomAxNode, resolveColor);
97
+ const valAxisLine = lineStyleOf(leftAxNode, resolveColor);
98
+ const secondaryValAxisLine = lineStyleOf(secondaryValAx, resolveColor);
99
+ const gridLine = lineStyleOf(leftAxNode ? poChildren(leftAxNode).find((c) => poIs(c, "c:majorGridlines")) : void 0, resolveColor);
100
+ const valAxisMin = axisScaling(plotArea, "c:min");
101
+ const valAxisMax = axisScaling(plotArea, "c:max");
102
+ const catAxisReversed = axisOrientation(catAxNode) === "maxMin";
103
+ const chartSpace = tree.find((c) => poIs(c, "c:chartSpace"));
104
+ const spaceSpPr = poChildren(chartSpace).find((c) => poIs(c, "c:spPr"));
105
+ const frameLine = spaceSpPr ? poChildren(spaceSpPr).find((c) => poIs(c, "a:ln")) : void 0;
106
+ const frameStyle = lineStyleOf(chartSpace, resolveColor);
107
+ const frameFillHex = spaceSpPr ? frameFillOf(spaceSpPr, resolveColor) : "FFFFFF";
108
+ const plotSpPr = poChildren(plotArea).find((c) => poIs(c, "c:spPr"));
109
+ const plotFillHex = plotSpPr ? frameFillOf(plotSpPr, resolveColor) : void 0;
110
+ const plotLine = lineStyleOf(plotArea, resolveColor);
111
+ const frameLineHex = spaceSpPr ? frameLine ? frameFillOf(frameLine, resolveColor) : void 0 : "D9D9D9";
112
+ const numberFormat = valueFormatCode(plotArea);
62
113
  const legend = poChildren(chart).find((c) => poIs(c, "c:legend"));
63
114
  const legendPos = legend ? poVal(poChildren(legend).find((c) => poIs(c, "c:legendPos"))) : void 0;
64
- const title = chartTitle(chart);
115
+ const title = chartTitle(chart, series);
116
+ const gapNode = poChildren(plotArea).flatMap((g) => poChildren(g)).find((c) => poIs(c, "c:gapWidth"));
117
+ const gapRaw = gapNode ? poVal(gapNode) : void 0;
118
+ const gapPercent = gapRaw === void 0 ? NaN : Number(gapRaw);
65
119
  return {
66
120
  type,
67
121
  ...title ? { title } : {},
122
+ ...Number.isFinite(gapPercent) && gapPercent >= 0 ? { gapPercent } : {},
68
123
  categories,
124
+ ...categoriesRef ? { categoriesRef } : {},
69
125
  series,
70
126
  hasLegend: legend !== void 0,
71
127
  ...isLegendPos(legendPos) ? { legendPos } : {},
@@ -74,7 +130,24 @@ function parseChart(chartXml, resolveColor) {
74
130
  ...doughnut ? { doughnut: true } : {},
75
131
  ...showValues ? { showValues: true } : {},
76
132
  ...catAxisTitle ? { catAxisTitle } : {},
77
- ...valAxisTitle ? { valAxisTitle } : {}
133
+ ...catAxisReversed ? { catAxisReversed } : {},
134
+ ...valAxisTitle ? { valAxisTitle } : {},
135
+ ...secondaryValAxisTitle ? { secondaryValAxisTitle } : {},
136
+ ...scatterStyle ? { scatterStyle } : {},
137
+ ...lineMarkers ? { lineMarkers: true } : {},
138
+ ...catAxisLine ? { catAxisLine } : {},
139
+ ...valAxisLine ? { valAxisLine } : {},
140
+ ...secondaryValAxisLine ? { secondaryValAxisLine } : {},
141
+ ...gridLine ? { gridLine } : {},
142
+ ...valAxisMin !== void 0 ? { valAxisMin } : {},
143
+ ...valAxisMax !== void 0 ? { valAxisMax } : {},
144
+ ...frameFillHex ? { frameFillHex } : {},
145
+ ...frameLineHex ? { frameLineHex } : {},
146
+ ...frameStyle?.widthPt !== void 0 ? { frameLineWidthPt: frameStyle.widthPt } : {},
147
+ ...frameStyle?.dash ? { frameLineDash: frameStyle.dash } : {},
148
+ ...plotFillHex ? { plotFillHex } : {},
149
+ ...plotLine && plotLine.none !== true ? { plotLine } : {},
150
+ ...numberFormat ? { numberFormat } : {}
78
151
  };
79
152
  }
80
153
  function parseSeries(ser, resolveColor) {
@@ -85,12 +158,49 @@ function parseSeries(ser, resolveColor) {
85
158
  const name = seriesName(ser);
86
159
  const colorHex = fillColorOf(poChildren(ser).find((c) => poIs(c, "c:spPr")), resolveColor);
87
160
  const pointColors = dataPointColors(ser, resolveColor);
161
+ const pointLabels = customDataLabels(ser);
162
+ const marker = seriesMarker(ser);
163
+ const line = lineStyleOf(ser, resolveColor);
164
+ const valuesRef = valNode ? refFormula(valNode) : void 0;
165
+ const nameRef = refFormula(poChildren(ser).find((c) => poIs(c, "c:tx")));
88
166
  return {
89
167
  values,
168
+ ...valuesRef ? { valuesRef } : {},
169
+ ...nameRef ? { nameRef } : {},
90
170
  ...xValues && xValues.length > 0 ? { xValues } : {},
91
171
  ...name ? { name } : {},
92
172
  ...colorHex ? { colorHex } : {},
93
- ...pointColors.length > 0 ? { pointColors } : {}
173
+ ...pointColors.length > 0 ? { pointColors } : {},
174
+ ...pointLabels.length > 0 ? { pointLabels } : {},
175
+ ...marker ? { marker } : {},
176
+ ...line ? { line } : {}
177
+ };
178
+ }
179
+ var MARKER_SYMBOLS = new Set([
180
+ "circle",
181
+ "dash",
182
+ "diamond",
183
+ "dot",
184
+ "none",
185
+ "plus",
186
+ "square",
187
+ "star",
188
+ "triangle",
189
+ "x"
190
+ ]);
191
+ /**
192
+ * §21.2.2.106 `c:marker` — the series' own point symbol. `auto` (and the
193
+ * picture marker we cannot draw) read as absent, leaving the reader's default.
194
+ */
195
+ function seriesMarker(ser) {
196
+ const node = poChildren(ser).find((c) => poIs(c, "c:marker"));
197
+ if (!node) return void 0;
198
+ const symbol = poVal(poChildren(node).find((c) => poIs(c, "c:symbol")));
199
+ if (!symbol || !MARKER_SYMBOLS.has(symbol)) return void 0;
200
+ const sizePt = poIntAttr(poChildren(node).find((c) => poIs(c, "c:size")), "val");
201
+ return {
202
+ symbol,
203
+ ...sizePt !== void 0 && sizePt > 0 ? { sizePt } : {}
94
204
  };
95
205
  }
96
206
  function seriesName(ser) {
@@ -100,6 +210,29 @@ function seriesName(ser) {
100
210
  if (direct) return poText(direct) || void 0;
101
211
  return readPts(tx)[0]?.v || void 0;
102
212
  }
213
+ /**
214
+ * §21.2.2.49 — the labels the author typed, by point index. A `<c:dLbl>` with
215
+ * its own `<c:tx><c:rich>` replaces whatever the chart would have computed for
216
+ * that point, and it is the only place that text exists.
217
+ */
218
+ function customDataLabels(ser) {
219
+ const dLbls = poChildren(ser).find((c) => poIs(c, "c:dLbls"));
220
+ if (!dLbls) return [];
221
+ const out = [];
222
+ for (const dLbl of poChildren(dLbls)) {
223
+ if (!poIs(dLbl, "c:dLbl")) continue;
224
+ const idxNode = poChildren(dLbl).find((c) => poIs(c, "c:idx"));
225
+ const tx = poChildren(dLbl).find((c) => poIs(c, "c:tx"));
226
+ if (!tx) continue;
227
+ const text = collectAT(tx).trim();
228
+ if (text.length === 0) continue;
229
+ out.push({
230
+ idx: idxNode ? poIntAttr(idxNode, "val") ?? 0 : 0,
231
+ text
232
+ });
233
+ }
234
+ return out;
235
+ }
103
236
  function dataPointColors(ser, resolveColor) {
104
237
  const out = [];
105
238
  for (const dPt of poChildren(ser)) {
@@ -114,11 +247,31 @@ function dataPointColors(ser, resolveColor) {
114
247
  }
115
248
  return out;
116
249
  }
250
+ /**
251
+ * The BACKGROUND fill of a frame (the chart space, the plot rectangle): its own
252
+ * `a:solidFill` and nothing else. Unlike a series, a frame that states
253
+ * `<a:noFill/>` is not filled in the colour of its own rule —
254
+ * Chart_Plot_BorderLine_Style.docx rules its plot in orange and we painted the
255
+ * whole plot orange.
256
+ *
257
+ * @param spPr The frame's `c:spPr`, or `undefined`.
258
+ * @param resolveColor Maps a DrawingML colour reference to 6-hex.
259
+ * @returns The fill colour, or `undefined` when the frame states none.
260
+ */
261
+ function frameFillOf(spPr, resolveColor) {
262
+ if (!spPr) return void 0;
263
+ const solid = poChildren(spPr).find((c) => poIs(c, "a:solidFill"));
264
+ return solid ? colorFromSolidFill(solid, resolveColor) : void 0;
265
+ }
117
266
  function fillColorOf(spPr, resolveColor) {
118
267
  if (!spPr) return void 0;
119
268
  const directFill = poChildren(spPr).find((c) => poIs(c, "a:solidFill"));
120
269
  const fromFill = directFill ? colorFromSolidFill(directFill, resolveColor) : void 0;
121
270
  if (fromFill) return fromFill;
271
+ const grad = poChildren(spPr).find((c) => poIs(c, "a:gradFill"));
272
+ const firstStop = grad ? poChildren(grad).filter((c) => poIs(c, "a:gsLst")).flatMap((lst) => poChildren(lst)).find((gs) => poIs(gs, "a:gs")) : void 0;
273
+ const fromGrad = firstStop ? colorFromSolidFill(firstStop, resolveColor) : void 0;
274
+ if (fromGrad) return fromGrad;
122
275
  const ln = poChildren(spPr).find((c) => poIs(c, "a:ln"));
123
276
  const lnFill = ln ? poChildren(ln).find((c) => poIs(c, "a:solidFill")) : void 0;
124
277
  return lnFill ? colorFromSolidFill(lnFill, resolveColor) : void 0;
@@ -139,14 +292,123 @@ function collectAT(node) {
139
292
  walk(node);
140
293
  return text;
141
294
  }
142
- function chartTitle(chart) {
295
+ /**
296
+ * §21.2.2.213 `c:title` — the chart's title, authored or generated.
297
+ *
298
+ * `c:tx` is OPTIONAL: a `<c:title>` with none asks the application to make the
299
+ * title up, and §21.2.2.10's `c:autoTitleDeleted val="0"` says that generated
300
+ * title has not been removed. Excel and LibreOffice agree on the rule — one
301
+ * series means the series' name, anything else the placeholder "Chart Title" —
302
+ * and reading only the `a:t` runs gave 56557.xlsx a blank page where the
303
+ * reference draws its title. Eleven chart parts across eight corpus files are
304
+ * written this way.
305
+ *
306
+ * The placeholder is an application string and therefore a locale: this file
307
+ * was authored in Swedish, where Excel prints "Diagramrubrik". English is what
308
+ * both references print here, and what we have to pick.
309
+ *
310
+ * @param chart The `c:chart` element.
311
+ * @param series The chart's series, for the single-series case.
312
+ * @returns The title text, or undefined when there is no title to draw.
313
+ */
314
+ function chartTitle(chart, series) {
143
315
  const title = poChildren(chart).find((c) => poIs(c, "c:title"));
144
- return title ? collectAT(title) || void 0 : void 0;
316
+ if (!title) return void 0;
317
+ const authored = collectAT(title) || cachedTitleText(title);
318
+ if (authored) return authored;
319
+ if (poVal(poChildren(chart).find((c) => poIs(c, "c:autoTitleDeleted"))) === "1") return void 0;
320
+ return (series.length === 1 ? series[0]?.name : void 0) ?? "Chart Title";
321
+ }
322
+ /**
323
+ * §21.2.2.215 — a title the author gave as a FORMULA: `c:tx/c:strRef` with the
324
+ * text in its `c:strCache`, and not one `a:t` run anywhere. Read for rich text
325
+ * alone, such a title came out as the generated placeholder —
326
+ * chartTitle_withTitleFormula.xlsx printed "Chart Title" where both references
327
+ * print "Formula Title from Excel 2016".
328
+ *
329
+ * @param title The `c:title` node.
330
+ * @returns The cached text, or undefined when the title carries no cache.
331
+ */
332
+ function cachedTitleText(title) {
333
+ const tx = poChildren(title).find((c) => poIs(c, "c:tx"));
334
+ const ref = tx ? poChildren(tx).find((c) => poIs(c, "c:strRef")) : void 0;
335
+ if (!ref) return void 0;
336
+ const cached = denseStrings(ref).filter((t) => t.length > 0);
337
+ return cached.length > 0 ? cached.join(" ") : void 0;
145
338
  }
146
339
  function axisTitle(plotArea, axTag) {
147
340
  const ax = poChildren(plotArea).find((c) => poIs(c, axTag));
148
341
  const title = ax ? poChildren(ax).find((c) => poIs(c, "c:title")) : void 0;
149
- return title ? collectAT(title) || void 0 : void 0;
342
+ if (!title) return void 0;
343
+ return collectAT(title) || cachedTitleText(title) || "Axis Title";
344
+ }
345
+ /**
346
+ * §21.2.2.121 `c:numFmt` on the value axis — the number format its tick labels
347
+ * and the chart's data labels are drawn in. It is the same code grammar cells
348
+ * use (§18.8.31), so a currency axis reads as currency: without it a monthly
349
+ * budget's axis ran 0/1000/2000 where every other reader shows $0/$1,000/$2,000.
350
+ *
351
+ * `sourceLinked="1"` means "whatever the source cells use", which the chart part
352
+ * does not carry — those keep the plain numeric render.
353
+ */
354
+ /** §21.2.2.134 `c:scaling/c:orientation` — `minMax` (the default) or `maxMin`. */
355
+ function axisOrientation(ax) {
356
+ const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
357
+ return scaling ? poVal(poChildren(scaling).find((c) => poIs(c, "c:orientation"))) : void 0;
358
+ }
359
+ function axisScaling(plotArea, tag) {
360
+ const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
361
+ const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
362
+ const node = scaling ? poChildren(scaling).find((c) => poIs(c, tag)) : void 0;
363
+ const v = node ? Number(poAttr(node, "val")) : NaN;
364
+ return Number.isFinite(v) ? v : void 0;
365
+ }
366
+ /**
367
+ * §21.2.2.196 `c:spPr/a:ln` — an axis's (or a series') own rule.
368
+ * `<a:ln><a:noFill/>` means it draws nothing, which is not the same as having
369
+ * no `c:spPr` at all: the first hides the line, the second leaves it to the
370
+ * renderer's default.
371
+ *
372
+ * @param owner The `c:catAx` / `c:valAx` / `c:majorGridlines` / `c:ser`
373
+ * node.
374
+ * @param resolveColor Maps a DrawingML colour reference to 6 hex digits.
375
+ * @returns The rule, or undefined when the node says nothing about it.
376
+ */
377
+ var DASHES = new Set([
378
+ "solid",
379
+ "dot",
380
+ "dash",
381
+ "dashDot",
382
+ "lgDash",
383
+ "lgDashDot",
384
+ "sysDash",
385
+ "sysDot"
386
+ ]);
387
+ function lineStyleOf(owner, resolveColor) {
388
+ const spPr = owner ? poChildren(owner).find((c) => poIs(c, "c:spPr")) : void 0;
389
+ const ln = spPr ? poChildren(spPr).find((c) => poIs(c, "a:ln")) : void 0;
390
+ if (!ln) return void 0;
391
+ if (poChildren(ln).some((c) => poIs(c, "a:noFill"))) return { none: true };
392
+ const solid = poChildren(ln).find((c) => poIs(c, "a:solidFill"));
393
+ const colorHex = solid ? colorFromSolidFill(solid, resolveColor) : void 0;
394
+ const emu = Number(poAttr(ln, "w"));
395
+ const widthPt = Number.isFinite(emu) && emu > 0 ? emu / 12700 : void 0;
396
+ const prst = poChildren(ln).find((c) => poIs(c, "a:prstDash"));
397
+ const dashVal = prst ? poAttr(prst, "val") : void 0;
398
+ const dash = dashVal !== void 0 && DASHES.has(dashVal) ? dashVal : void 0;
399
+ if (!colorHex && widthPt === void 0 && !dash) return void 0;
400
+ return {
401
+ ...colorHex ? { colorHex } : {},
402
+ ...widthPt !== void 0 ? { widthPt } : {},
403
+ ...dash ? { dash } : {}
404
+ };
405
+ }
406
+ function valueFormatCode(plotArea) {
407
+ const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
408
+ const numFmt = ax ? poChildren(ax).find((c) => poIs(c, "c:numFmt")) : void 0;
409
+ const code = numFmt ? poAttr(numFmt, "formatCode") : void 0;
410
+ if (code === void 0 || code.trim().length === 0) return void 0;
411
+ return code.trim().toLowerCase() === "general" ? void 0 : code;
150
412
  }
151
413
  function dLblsShowVal(dLbls) {
152
414
  if (!dLbls) return false;
@@ -158,6 +420,13 @@ function chartShowsValues(group) {
158
420
  for (const ser of poChildren(group)) if (poIs(ser, "c:ser") && dLblsShowVal(poChildren(ser).find((c) => poIs(c, "c:dLbls")))) return true;
159
421
  return false;
160
422
  }
423
+ /** The `<c:f>` inside a c:val / c:cat / c:tx, or undefined when there is none. */
424
+ function refFormula(container) {
425
+ if (!container) return void 0;
426
+ const f = poFindDescendant(container, "c:f");
427
+ const text = f ? poText(f).trim() : "";
428
+ return text.length > 0 ? text : void 0;
429
+ }
161
430
  function readPts(container) {
162
431
  const cache = poFindDescendant(container, "c:numCache") ?? poFindDescendant(container, "c:strCache");
163
432
  if (!cache) return [];
@@ -249,8 +518,29 @@ function withChartColorStyle(chart, pkg, chartPartPath, resolveColor) {
249
518
  seriesColorCycle: cycle
250
519
  };
251
520
  }
252
- return chart;
521
+ const themed = ACCENT_SLOTS.map((scheme) => resolveColor({ scheme }));
522
+ return themed.every((c) => c !== void 0) ? {
523
+ ...chart,
524
+ seriesColorCycle: themed
525
+ } : chart;
253
526
  }
527
+ /** §20.1.4.1.5 — the accent slots a chart cycles its series through. */
528
+ var ACCENT_SLOTS = [
529
+ "accent1",
530
+ "accent2",
531
+ "accent3",
532
+ "accent4",
533
+ "accent5",
534
+ "accent6"
535
+ ];
536
+ var SCATTER_STYLES = new Set([
537
+ "none",
538
+ "line",
539
+ "lineMarker",
540
+ "marker",
541
+ "smooth",
542
+ "smoothMarker"
543
+ ]);
254
544
  var REL_CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
255
545
  //#endregion
256
546
  export { parseChart, withChartColorStyle };
@@ -2,11 +2,11 @@ import { PoNode } from '../po-helpers.js';
2
2
  /**
3
3
  * A colour transform child (§20.1.2.3): `lumMod`/`lumOff` modulate luminance,
4
4
  * `shade` darkens toward black, `tint` lightens toward white. `val` is normalised
5
- * to 0..1 (the XML stores thousandths of a percent). `alpha` is parsed but ignored
5
+ * to 0..1 (the XML stores thousandths of a percent). `alpha` composites over white
6
6
  * (solid fills emit no transparency).
7
7
  */
8
8
  export interface ColorMod {
9
- readonly kind: 'lumMod' | 'lumOff' | 'shade' | 'tint' | 'alpha';
9
+ readonly kind: 'lumMod' | 'lumOff' | 'shade' | 'tint' | 'alpha' | 'satMod' | 'hueOff' | 'satOff';
10
10
  readonly val: number;
11
11
  }
12
12
  /**
@@ -60,11 +60,11 @@ export declare const defaultColorResolver: ColorResolver;
60
60
  */
61
61
  export declare function readColorMods(colorNode: PoNode): Array<ColorMod>;
62
62
  /**
63
- * Resolve an `a:srgbClr` / `a:schemeClr` node to a hex string (with colour
64
- * transforms applied). Returns `undefined` when the node is some other element,
65
- * valueless, or the resolver does not know the colour. Container traversal policy
66
- * (stop at the first colour node vs continue past unresolved ones) stays with the
67
- * callers.
63
+ * Resolve an `a:srgbClr` / `a:schemeClr` / `a:sysClr` node to a hex string (with
64
+ * colour transforms applied). Returns `undefined` when the node is some other
65
+ * element, valueless, or the resolver does not know the colour. Container
66
+ * traversal policy (stop at the first colour node vs continue past unresolved
67
+ * ones) stays with the callers.
68
68
  *
69
69
  * @param c The candidate colour node.
70
70
  * @param resolveColor The resolver mapping a {@link RawColor} to hex.
@@ -96,6 +96,19 @@ function applyColorMods(hex, mods) {
96
96
  const [h, s, l] = rgbToHsl(r, g, b);
97
97
  const l2 = m.kind === "lumMod" ? l * m.val : clamp01(l + m.val);
98
98
  [r, g, b] = hslToRgb(h, s, l2);
99
+ } else if (m.kind === "hueOff") {
100
+ const [h, sat, l] = rgbToHsl(r, g, b);
101
+ [r, g, b] = hslToRgb(((h + m.val) % 360 + 360) % 360, sat, l);
102
+ } else if (m.kind === "satOff") {
103
+ const [h, sat, l] = rgbToHsl(r, g, b);
104
+ [r, g, b] = hslToRgb(h, clamp01(sat + m.val), l);
105
+ } else if (m.kind === "alpha") {
106
+ r = r * m.val + (1 - m.val);
107
+ g = g * m.val + (1 - m.val);
108
+ b = b * m.val + (1 - m.val);
109
+ } else {
110
+ const [h, s, l] = rgbToHsl(r, g, b);
111
+ [r, g, b] = hslToRgb(h, clamp01(s * m.val), l);
99
112
  }
100
113
  const toHex = (x) => Math.round(clamp01(x) * 255).toString(16).padStart(2, "0");
101
114
  return (toHex(r) + toHex(g) + toHex(b)).toUpperCase();
@@ -148,39 +161,92 @@ function makeColorResolver(palette) {
148
161
  }
149
162
  /** A {@link ColorResolver} backed by the {@link DEFAULT_THEME_PALETTE}. */
150
163
  var defaultColorResolver = makeColorResolver(DEFAULT_THEME_PALETTE);
164
+ var PRESET_COLORS = new Map([
165
+ ["black", "000000"],
166
+ ["white", "FFFFFF"],
167
+ ["gray", "808080"],
168
+ ["grey", "808080"],
169
+ ["darkGray".toLowerCase(), "A9A9A9"],
170
+ ["lightGray".toLowerCase(), "D3D3D3"],
171
+ ["red", "FF0000"],
172
+ ["green", "008000"],
173
+ ["blue", "0000FF"],
174
+ ["yellow", "FFFF00"],
175
+ ["cyan", "00FFFF"],
176
+ ["magenta", "FF00FF"],
177
+ ["dkGray".toLowerCase(), "A9A9A9"],
178
+ ["ltGray".toLowerCase(), "D3D3D3"]
179
+ ]);
151
180
  /**
152
181
  * Read the colour transform children ({@link ColorMod}s) under an `a:srgbClr` /
153
182
  * `a:schemeClr` node, normalising each `val` from thousandths-of-a-percent to 0..1.
154
183
  */
155
184
  function readColorMods(colorNode) {
156
185
  const mods = [];
157
- for (const c of poChildren(colorNode)) for (const kind of [
158
- "lumMod",
159
- "lumOff",
160
- "shade",
161
- "tint",
162
- "alpha"
163
- ]) if (poIs(c, `a:${kind}`)) {
164
- const v = poIntAttr(c, "val");
165
- if (v !== void 0) mods.push({
166
- kind,
167
- val: v / 1e5
168
- });
186
+ for (const c of poChildren(colorNode)) {
187
+ for (const kind of [
188
+ "lumMod",
189
+ "lumOff",
190
+ "shade",
191
+ "tint",
192
+ "alpha",
193
+ "satMod",
194
+ "satOff"
195
+ ]) if (poIs(c, `a:${kind}`)) {
196
+ const v = poIntAttr(c, "val");
197
+ if (v !== void 0) mods.push({
198
+ kind,
199
+ val: v / 1e5
200
+ });
201
+ }
202
+ if (poIs(c, "a:hueOff")) {
203
+ const v = poIntAttr(c, "val");
204
+ if (v !== void 0) mods.push({
205
+ kind: "hueOff",
206
+ val: v / 6e4
207
+ });
208
+ }
169
209
  }
170
210
  return mods;
171
211
  }
172
212
  /**
173
- * Resolve an `a:srgbClr` / `a:schemeClr` node to a hex string (with colour
174
- * transforms applied). Returns `undefined` when the node is some other element,
175
- * valueless, or the resolver does not know the colour. Container traversal policy
176
- * (stop at the first colour node vs continue past unresolved ones) stays with the
177
- * callers.
213
+ * §20.1.2.3.32 `a:sysClr` — the host system's own colour. `val` names it and
214
+ * `lastClr` carries what the producing application resolved it to, which is
215
+ * what a reader uses; the two names that matter without one are the window
216
+ * background and its text.
217
+ */
218
+ var SYSTEM_COLORS = new Map([
219
+ ["window", "FFFFFF"],
220
+ ["windowText", "000000"],
221
+ ["windowFrame", "000000"],
222
+ ["btnText", "000000"],
223
+ ["btnFace", "F0F0F0"],
224
+ ["highlight", "0078D7"],
225
+ ["highlightText", "FFFFFF"]
226
+ ]);
227
+ /**
228
+ * Resolve an `a:srgbClr` / `a:schemeClr` / `a:sysClr` node to a hex string (with
229
+ * colour transforms applied). Returns `undefined` when the node is some other
230
+ * element, valueless, or the resolver does not know the colour. Container
231
+ * traversal policy (stop at the first colour node vs continue past unresolved
232
+ * ones) stays with the callers.
178
233
  *
179
234
  * @param c The candidate colour node.
180
235
  * @param resolveColor The resolver mapping a {@link RawColor} to hex.
181
236
  * @returns The resolved RRGGBB, or `undefined`.
182
237
  */
183
238
  function resolveColorNode(c, resolveColor) {
239
+ if (poIs(c, "a:sysClr")) {
240
+ const last = poAttr(c, "lastClr");
241
+ if (last && /^[0-9A-Fa-f]{6}$/.test(last)) return last.toUpperCase();
242
+ return SYSTEM_COLORS.get(poAttr(c, "val") ?? "");
243
+ }
244
+ if (poIs(c, "a:prstClr")) {
245
+ const hex = PRESET_COLORS.get((poAttr(c, "val") ?? "").toLowerCase());
246
+ if (hex === void 0) return void 0;
247
+ const presetMods = readColorMods(c);
248
+ return presetMods.length > 0 ? applyColorMods(hex, presetMods) : hex;
249
+ }
184
250
  const isSrgb = poIs(c, "a:srgbClr");
185
251
  if (!isSrgb && !poIs(c, "a:schemeClr")) return void 0;
186
252
  const v = poAttr(c, "val");
@@ -193,4 +259,4 @@ function resolveColorNode(c, resolveColor) {
193
259
  } : raw);
194
260
  }
195
261
  //#endregion
196
- export { DEFAULT_THEME_PALETTE, defaultColorResolver, makeColorResolver, resolveColorNode };
262
+ export { DEFAULT_THEME_PALETTE, applyColorMods, defaultColorResolver, makeColorResolver, readColorMods, resolveColorNode };