reamkit 1.15.4 → 1.16.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 (94) hide show
  1. package/dist/esm/core/converter/facade.d.ts +5 -0
  2. package/dist/esm/core/converter/ream.d.ts +6 -0
  3. package/dist/esm/core/converter/ream.js +34 -8
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +187 -1
  6. package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
  7. package/dist/esm/core/drawingml/chart-geometry.js +371 -88
  8. package/dist/esm/core/drawingml/chart-parser.js +262 -11
  9. package/dist/esm/core/drawingml/colors.d.ts +6 -6
  10. package/dist/esm/core/drawingml/colors.js +58 -18
  11. package/dist/esm/core/drawingml/preset-geometry.js +7 -6
  12. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +74 -1
  15. package/dist/esm/core/font/ttf-subset.js +26 -1
  16. package/dist/esm/core/images.js +172 -43
  17. package/dist/esm/core/indexed-colors.d.ts +11 -0
  18. package/dist/esm/core/indexed-colors.js +81 -0
  19. package/dist/esm/core/ir/sheet.d.ts +61 -0
  20. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  21. package/dist/esm/core/number-format.js +800 -0
  22. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  23. package/dist/esm/core/opc/alternate-content.js +72 -0
  24. package/dist/esm/core/opc/core-properties.js +1 -0
  25. package/dist/esm/core/opc/package.d.ts +14 -1
  26. package/dist/esm/core/opc/package.js +43 -4
  27. package/dist/esm/core/opc/relationships.js +1 -0
  28. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  29. package/dist/esm/core/opc/xml-entities.js +91 -0
  30. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  31. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  32. package/dist/esm/core/vector.d.ts +13 -0
  33. package/dist/esm/excel/activex-parser.js +1 -0
  34. package/dist/esm/excel/column-bands.d.ts +26 -1
  35. package/dist/esm/excel/column-bands.js +156 -20
  36. package/dist/esm/excel/comments-parser.js +5 -3
  37. package/dist/esm/excel/conditional-format.d.ts +25 -2
  38. package/dist/esm/excel/conditional-format.js +63 -19
  39. package/dist/esm/excel/escaped-text.d.ts +9 -0
  40. package/dist/esm/excel/escaped-text.js +20 -0
  41. package/dist/esm/excel/form-control-parser.js +1 -0
  42. package/dist/esm/excel/formula/dates.js +1 -1
  43. package/dist/esm/excel/header-footer.d.ts +9 -1
  44. package/dist/esm/excel/header-footer.js +162 -14
  45. package/dist/esm/excel/index.d.ts +1 -1
  46. package/dist/esm/excel/index.js +2 -2
  47. package/dist/esm/excel/pivot-table-parser.js +1 -0
  48. package/dist/esm/excel/print-model.d.ts +75 -0
  49. package/dist/esm/excel/print-model.js +898 -76
  50. package/dist/esm/excel/printer-settings.d.ts +16 -0
  51. package/dist/esm/excel/printer-settings.js +36 -0
  52. package/dist/esm/excel/rich-value.d.ts +13 -0
  53. package/dist/esm/excel/rich-value.js +120 -0
  54. package/dist/esm/excel/shared-strings-parser.js +11 -7
  55. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  56. package/dist/esm/excel/sheet-drawing.js +26 -3
  57. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  58. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  59. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  60. package/dist/esm/excel/sheet-to-flow.js +551 -36
  61. package/dist/esm/excel/slicer-parser.js +1 -0
  62. package/dist/esm/excel/styles-parser.d.ts +30 -2
  63. package/dist/esm/excel/styles-parser.js +177 -40
  64. package/dist/esm/excel/table-parser.js +1 -0
  65. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  66. package/dist/esm/excel/vml-drawing.js +158 -0
  67. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  68. package/dist/esm/excel/workbook-parser.js +5 -1
  69. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  70. package/dist/esm/excel/worksheet-parser.js +150 -61
  71. package/dist/esm/excel/xlsx-reader.js +242 -11
  72. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  73. package/dist/esm/html/html-writer.js +11 -2
  74. package/dist/esm/layout/page-doc.d.ts +26 -0
  75. package/dist/esm/layout/styled-layout.d.ts +2 -0
  76. package/dist/esm/layout/styled-layout.js +493 -64
  77. package/dist/esm/pdf/cid-font.js +20 -9
  78. package/dist/esm/pdf/objects.d.ts +13 -0
  79. package/dist/esm/pdf/objects.js +18 -1
  80. package/dist/esm/pdf/styled-page-emitter.js +164 -32
  81. package/dist/esm/pdf/text-page-renderer.js +1 -0
  82. package/dist/esm/pdf/vector-graphics.d.ts +3 -1
  83. package/dist/esm/pdf/vector-graphics.js +19 -2
  84. package/dist/esm/pptx/pptx-reader.js +1 -0
  85. package/dist/esm/pptx/slide-parser.js +12 -1
  86. package/dist/esm/svg/svg-writer.js +23 -4
  87. package/dist/esm/word/document-parser.js +1 -0
  88. package/dist/esm/word/drawing-parser.d.ts +34 -1
  89. package/dist/esm/word/drawing-parser.js +52 -2
  90. package/dist/esm/word/numbering-parser.js +1 -0
  91. package/dist/esm/word/settings-parser.js +1 -0
  92. package/dist/esm/word/styles-parser.js +1 -0
  93. package/package.json +7 -3
  94. 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,85 @@ 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 spaceSpPr = poChildren(tree.find((c) => poIs(c, "c:chartSpace"))).find((c) => poIs(c, "c:spPr"));
104
+ const frameFillHex = fillColorOf(spaceSpPr, resolveColor);
105
+ const frameLine = spaceSpPr ? poChildren(spaceSpPr).find((c) => poIs(c, "a:ln")) : void 0;
106
+ const frameLineHex = frameLine ? fillColorOf(frameLine, resolveColor) : void 0;
107
+ const numberFormat = valueFormatCode(plotArea);
62
108
  const legend = poChildren(chart).find((c) => poIs(c, "c:legend"));
63
109
  const legendPos = legend ? poVal(poChildren(legend).find((c) => poIs(c, "c:legendPos"))) : void 0;
64
- const title = chartTitle(chart);
110
+ const title = chartTitle(chart, series);
111
+ const gapNode = poChildren(plotArea).flatMap((g) => poChildren(g)).find((c) => poIs(c, "c:gapWidth"));
112
+ const gapRaw = gapNode ? poVal(gapNode) : void 0;
113
+ const gapPercent = gapRaw === void 0 ? NaN : Number(gapRaw);
65
114
  return {
66
115
  type,
67
116
  ...title ? { title } : {},
117
+ ...Number.isFinite(gapPercent) && gapPercent >= 0 ? { gapPercent } : {},
68
118
  categories,
119
+ ...categoriesRef ? { categoriesRef } : {},
69
120
  series,
70
121
  hasLegend: legend !== void 0,
71
122
  ...isLegendPos(legendPos) ? { legendPos } : {},
@@ -74,7 +125,20 @@ function parseChart(chartXml, resolveColor) {
74
125
  ...doughnut ? { doughnut: true } : {},
75
126
  ...showValues ? { showValues: true } : {},
76
127
  ...catAxisTitle ? { catAxisTitle } : {},
77
- ...valAxisTitle ? { valAxisTitle } : {}
128
+ ...catAxisReversed ? { catAxisReversed } : {},
129
+ ...valAxisTitle ? { valAxisTitle } : {},
130
+ ...secondaryValAxisTitle ? { secondaryValAxisTitle } : {},
131
+ ...scatterStyle ? { scatterStyle } : {},
132
+ ...lineMarkers ? { lineMarkers: true } : {},
133
+ ...catAxisLine ? { catAxisLine } : {},
134
+ ...valAxisLine ? { valAxisLine } : {},
135
+ ...secondaryValAxisLine ? { secondaryValAxisLine } : {},
136
+ ...gridLine ? { gridLine } : {},
137
+ ...valAxisMin !== void 0 ? { valAxisMin } : {},
138
+ ...valAxisMax !== void 0 ? { valAxisMax } : {},
139
+ ...frameFillHex ? { frameFillHex } : {},
140
+ ...frameLineHex ? { frameLineHex } : {},
141
+ ...numberFormat ? { numberFormat } : {}
78
142
  };
79
143
  }
80
144
  function parseSeries(ser, resolveColor) {
@@ -85,12 +149,49 @@ function parseSeries(ser, resolveColor) {
85
149
  const name = seriesName(ser);
86
150
  const colorHex = fillColorOf(poChildren(ser).find((c) => poIs(c, "c:spPr")), resolveColor);
87
151
  const pointColors = dataPointColors(ser, resolveColor);
152
+ const pointLabels = customDataLabels(ser);
153
+ const marker = seriesMarker(ser);
154
+ const line = lineStyleOf(ser, resolveColor);
155
+ const valuesRef = valNode ? refFormula(valNode) : void 0;
156
+ const nameRef = refFormula(poChildren(ser).find((c) => poIs(c, "c:tx")));
88
157
  return {
89
158
  values,
159
+ ...valuesRef ? { valuesRef } : {},
160
+ ...nameRef ? { nameRef } : {},
90
161
  ...xValues && xValues.length > 0 ? { xValues } : {},
91
162
  ...name ? { name } : {},
92
163
  ...colorHex ? { colorHex } : {},
93
- ...pointColors.length > 0 ? { pointColors } : {}
164
+ ...pointColors.length > 0 ? { pointColors } : {},
165
+ ...pointLabels.length > 0 ? { pointLabels } : {},
166
+ ...marker ? { marker } : {},
167
+ ...line ? { line } : {}
168
+ };
169
+ }
170
+ var MARKER_SYMBOLS = new Set([
171
+ "circle",
172
+ "dash",
173
+ "diamond",
174
+ "dot",
175
+ "none",
176
+ "plus",
177
+ "square",
178
+ "star",
179
+ "triangle",
180
+ "x"
181
+ ]);
182
+ /**
183
+ * §21.2.2.106 `c:marker` — the series' own point symbol. `auto` (and the
184
+ * picture marker we cannot draw) read as absent, leaving the reader's default.
185
+ */
186
+ function seriesMarker(ser) {
187
+ const node = poChildren(ser).find((c) => poIs(c, "c:marker"));
188
+ if (!node) return void 0;
189
+ const symbol = poVal(poChildren(node).find((c) => poIs(c, "c:symbol")));
190
+ if (!symbol || !MARKER_SYMBOLS.has(symbol)) return void 0;
191
+ const sizePt = poIntAttr(poChildren(node).find((c) => poIs(c, "c:size")), "val");
192
+ return {
193
+ symbol,
194
+ ...sizePt !== void 0 && sizePt > 0 ? { sizePt } : {}
94
195
  };
95
196
  }
96
197
  function seriesName(ser) {
@@ -100,6 +201,29 @@ function seriesName(ser) {
100
201
  if (direct) return poText(direct) || void 0;
101
202
  return readPts(tx)[0]?.v || void 0;
102
203
  }
204
+ /**
205
+ * §21.2.2.49 — the labels the author typed, by point index. A `<c:dLbl>` with
206
+ * its own `<c:tx><c:rich>` replaces whatever the chart would have computed for
207
+ * that point, and it is the only place that text exists.
208
+ */
209
+ function customDataLabels(ser) {
210
+ const dLbls = poChildren(ser).find((c) => poIs(c, "c:dLbls"));
211
+ if (!dLbls) return [];
212
+ const out = [];
213
+ for (const dLbl of poChildren(dLbls)) {
214
+ if (!poIs(dLbl, "c:dLbl")) continue;
215
+ const idxNode = poChildren(dLbl).find((c) => poIs(c, "c:idx"));
216
+ const tx = poChildren(dLbl).find((c) => poIs(c, "c:tx"));
217
+ if (!tx) continue;
218
+ const text = collectAT(tx).trim();
219
+ if (text.length === 0) continue;
220
+ out.push({
221
+ idx: idxNode ? poIntAttr(idxNode, "val") ?? 0 : 0,
222
+ text
223
+ });
224
+ }
225
+ return out;
226
+ }
103
227
  function dataPointColors(ser, resolveColor) {
104
228
  const out = [];
105
229
  for (const dPt of poChildren(ser)) {
@@ -119,6 +243,10 @@ function fillColorOf(spPr, resolveColor) {
119
243
  const directFill = poChildren(spPr).find((c) => poIs(c, "a:solidFill"));
120
244
  const fromFill = directFill ? colorFromSolidFill(directFill, resolveColor) : void 0;
121
245
  if (fromFill) return fromFill;
246
+ const grad = poChildren(spPr).find((c) => poIs(c, "a:gradFill"));
247
+ const firstStop = grad ? poChildren(grad).filter((c) => poIs(c, "a:gsLst")).flatMap((lst) => poChildren(lst)).find((gs) => poIs(gs, "a:gs")) : void 0;
248
+ const fromGrad = firstStop ? colorFromSolidFill(firstStop, resolveColor) : void 0;
249
+ if (fromGrad) return fromGrad;
122
250
  const ln = poChildren(spPr).find((c) => poIs(c, "a:ln"));
123
251
  const lnFill = ln ? poChildren(ln).find((c) => poIs(c, "a:solidFill")) : void 0;
124
252
  return lnFill ? colorFromSolidFill(lnFill, resolveColor) : void 0;
@@ -139,14 +267,109 @@ function collectAT(node) {
139
267
  walk(node);
140
268
  return text;
141
269
  }
142
- function chartTitle(chart) {
270
+ /**
271
+ * §21.2.2.213 `c:title` — the chart's title, authored or generated.
272
+ *
273
+ * `c:tx` is OPTIONAL: a `<c:title>` with none asks the application to make the
274
+ * title up, and §21.2.2.10's `c:autoTitleDeleted val="0"` says that generated
275
+ * title has not been removed. Excel and LibreOffice agree on the rule — one
276
+ * series means the series' name, anything else the placeholder "Chart Title" —
277
+ * and reading only the `a:t` runs gave 56557.xlsx a blank page where the
278
+ * reference draws its title. Eleven chart parts across eight corpus files are
279
+ * written this way.
280
+ *
281
+ * The placeholder is an application string and therefore a locale: this file
282
+ * was authored in Swedish, where Excel prints "Diagramrubrik". English is what
283
+ * both references print here, and what we have to pick.
284
+ *
285
+ * @param chart The `c:chart` element.
286
+ * @param series The chart's series, for the single-series case.
287
+ * @returns The title text, or undefined when there is no title to draw.
288
+ */
289
+ function chartTitle(chart, series) {
143
290
  const title = poChildren(chart).find((c) => poIs(c, "c:title"));
144
- return title ? collectAT(title) || void 0 : void 0;
291
+ if (!title) return void 0;
292
+ const authored = collectAT(title) || cachedTitleText(title);
293
+ if (authored) return authored;
294
+ if (poVal(poChildren(chart).find((c) => poIs(c, "c:autoTitleDeleted"))) === "1") return void 0;
295
+ return (series.length === 1 ? series[0]?.name : void 0) ?? "Chart Title";
296
+ }
297
+ /**
298
+ * §21.2.2.215 — a title the author gave as a FORMULA: `c:tx/c:strRef` with the
299
+ * text in its `c:strCache`, and not one `a:t` run anywhere. Read for rich text
300
+ * alone, such a title came out as the generated placeholder —
301
+ * chartTitle_withTitleFormula.xlsx printed "Chart Title" where both references
302
+ * print "Formula Title from Excel 2016".
303
+ *
304
+ * @param title The `c:title` node.
305
+ * @returns The cached text, or undefined when the title carries no cache.
306
+ */
307
+ function cachedTitleText(title) {
308
+ const tx = poChildren(title).find((c) => poIs(c, "c:tx"));
309
+ const ref = tx ? poChildren(tx).find((c) => poIs(c, "c:strRef")) : void 0;
310
+ if (!ref) return void 0;
311
+ const cached = denseStrings(ref).filter((t) => t.length > 0);
312
+ return cached.length > 0 ? cached.join(" ") : void 0;
145
313
  }
146
314
  function axisTitle(plotArea, axTag) {
147
315
  const ax = poChildren(plotArea).find((c) => poIs(c, axTag));
148
316
  const title = ax ? poChildren(ax).find((c) => poIs(c, "c:title")) : void 0;
149
- return title ? collectAT(title) || void 0 : void 0;
317
+ if (!title) return void 0;
318
+ return collectAT(title) || cachedTitleText(title) || "Axis Title";
319
+ }
320
+ /**
321
+ * §21.2.2.121 `c:numFmt` on the value axis — the number format its tick labels
322
+ * and the chart's data labels are drawn in. It is the same code grammar cells
323
+ * use (§18.8.31), so a currency axis reads as currency: without it a monthly
324
+ * budget's axis ran 0/1000/2000 where every other reader shows $0/$1,000/$2,000.
325
+ *
326
+ * `sourceLinked="1"` means "whatever the source cells use", which the chart part
327
+ * does not carry — those keep the plain numeric render.
328
+ */
329
+ /** §21.2.2.134 `c:scaling/c:orientation` — `minMax` (the default) or `maxMin`. */
330
+ function axisOrientation(ax) {
331
+ const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
332
+ return scaling ? poVal(poChildren(scaling).find((c) => poIs(c, "c:orientation"))) : void 0;
333
+ }
334
+ function axisScaling(plotArea, tag) {
335
+ const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
336
+ const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
337
+ const node = scaling ? poChildren(scaling).find((c) => poIs(c, tag)) : void 0;
338
+ const v = node ? Number(poAttr(node, "val")) : NaN;
339
+ return Number.isFinite(v) ? v : void 0;
340
+ }
341
+ /**
342
+ * §21.2.2.196 `c:spPr/a:ln` — an axis's (or a series') own rule.
343
+ * `<a:ln><a:noFill/>` means it draws nothing, which is not the same as having
344
+ * no `c:spPr` at all: the first hides the line, the second leaves it to the
345
+ * renderer's default.
346
+ *
347
+ * @param owner The `c:catAx` / `c:valAx` / `c:majorGridlines` / `c:ser`
348
+ * node.
349
+ * @param resolveColor Maps a DrawingML colour reference to 6 hex digits.
350
+ * @returns The rule, or undefined when the node says nothing about it.
351
+ */
352
+ function lineStyleOf(owner, resolveColor) {
353
+ const spPr = owner ? poChildren(owner).find((c) => poIs(c, "c:spPr")) : void 0;
354
+ const ln = spPr ? poChildren(spPr).find((c) => poIs(c, "a:ln")) : void 0;
355
+ if (!ln) return void 0;
356
+ if (poChildren(ln).some((c) => poIs(c, "a:noFill"))) return { none: true };
357
+ const solid = poChildren(ln).find((c) => poIs(c, "a:solidFill"));
358
+ const colorHex = solid ? colorFromSolidFill(solid, resolveColor) : void 0;
359
+ const emu = Number(poAttr(ln, "w"));
360
+ const widthPt = Number.isFinite(emu) && emu > 0 ? emu / 12700 : void 0;
361
+ if (!colorHex && widthPt === void 0) return void 0;
362
+ return {
363
+ ...colorHex ? { colorHex } : {},
364
+ ...widthPt !== void 0 ? { widthPt } : {}
365
+ };
366
+ }
367
+ function valueFormatCode(plotArea) {
368
+ const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
369
+ const numFmt = ax ? poChildren(ax).find((c) => poIs(c, "c:numFmt")) : void 0;
370
+ const code = numFmt ? poAttr(numFmt, "formatCode") : void 0;
371
+ if (code === void 0 || code.trim().length === 0) return void 0;
372
+ return code.trim().toLowerCase() === "general" ? void 0 : code;
150
373
  }
151
374
  function dLblsShowVal(dLbls) {
152
375
  if (!dLbls) return false;
@@ -158,6 +381,13 @@ function chartShowsValues(group) {
158
381
  for (const ser of poChildren(group)) if (poIs(ser, "c:ser") && dLblsShowVal(poChildren(ser).find((c) => poIs(c, "c:dLbls")))) return true;
159
382
  return false;
160
383
  }
384
+ /** The `<c:f>` inside a c:val / c:cat / c:tx, or undefined when there is none. */
385
+ function refFormula(container) {
386
+ if (!container) return void 0;
387
+ const f = poFindDescendant(container, "c:f");
388
+ const text = f ? poText(f).trim() : "";
389
+ return text.length > 0 ? text : void 0;
390
+ }
161
391
  function readPts(container) {
162
392
  const cache = poFindDescendant(container, "c:numCache") ?? poFindDescendant(container, "c:strCache");
163
393
  if (!cache) return [];
@@ -249,8 +479,29 @@ function withChartColorStyle(chart, pkg, chartPartPath, resolveColor) {
249
479
  seriesColorCycle: cycle
250
480
  };
251
481
  }
252
- return chart;
482
+ const themed = ACCENT_SLOTS.map((scheme) => resolveColor({ scheme }));
483
+ return themed.every((c) => c !== void 0) ? {
484
+ ...chart,
485
+ seriesColorCycle: themed
486
+ } : chart;
253
487
  }
488
+ /** §20.1.4.1.5 — the accent slots a chart cycles its series through. */
489
+ var ACCENT_SLOTS = [
490
+ "accent1",
491
+ "accent2",
492
+ "accent3",
493
+ "accent4",
494
+ "accent5",
495
+ "accent6"
496
+ ];
497
+ var SCATTER_STYLES = new Set([
498
+ "none",
499
+ "line",
500
+ "lineMarker",
501
+ "marker",
502
+ "smooth",
503
+ "smoothMarker"
504
+ ]);
254
505
  var REL_CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
255
506
  //#endregion
256
507
  export { parseChart, withChartColorStyle };
@@ -6,7 +6,7 @@ import { PoNode } from '../po-helpers.js';
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,15 @@ 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 === "satMod") {
106
+ const [h, s, l] = rgbToHsl(r, g, b);
107
+ [r, g, b] = hslToRgb(h, clamp01(s * m.val), l);
99
108
  }
100
109
  const toHex = (x) => Math.round(clamp01(x) * 255).toString(16).padStart(2, "0");
101
110
  return (toHex(r) + toHex(g) + toHex(b)).toUpperCase();
@@ -154,33 +163,64 @@ var defaultColorResolver = makeColorResolver(DEFAULT_THEME_PALETTE);
154
163
  */
155
164
  function readColorMods(colorNode) {
156
165
  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
- });
166
+ for (const c of poChildren(colorNode)) {
167
+ for (const kind of [
168
+ "lumMod",
169
+ "lumOff",
170
+ "shade",
171
+ "tint",
172
+ "alpha",
173
+ "satMod",
174
+ "satOff"
175
+ ]) if (poIs(c, `a:${kind}`)) {
176
+ const v = poIntAttr(c, "val");
177
+ if (v !== void 0) mods.push({
178
+ kind,
179
+ val: v / 1e5
180
+ });
181
+ }
182
+ if (poIs(c, "a:hueOff")) {
183
+ const v = poIntAttr(c, "val");
184
+ if (v !== void 0) mods.push({
185
+ kind: "hueOff",
186
+ val: v / 6e4
187
+ });
188
+ }
169
189
  }
170
190
  return mods;
171
191
  }
172
192
  /**
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.
193
+ * §20.1.2.3.32 `a:sysClr` — the host system's own colour. `val` names it and
194
+ * `lastClr` carries what the producing application resolved it to, which is
195
+ * what a reader uses; the two names that matter without one are the window
196
+ * background and its text.
197
+ */
198
+ var SYSTEM_COLORS = new Map([
199
+ ["window", "FFFFFF"],
200
+ ["windowText", "000000"],
201
+ ["windowFrame", "000000"],
202
+ ["btnText", "000000"],
203
+ ["btnFace", "F0F0F0"],
204
+ ["highlight", "0078D7"],
205
+ ["highlightText", "FFFFFF"]
206
+ ]);
207
+ /**
208
+ * Resolve an `a:srgbClr` / `a:schemeClr` / `a:sysClr` node to a hex string (with
209
+ * colour transforms applied). Returns `undefined` when the node is some other
210
+ * element, valueless, or the resolver does not know the colour. Container
211
+ * traversal policy (stop at the first colour node vs continue past unresolved
212
+ * ones) stays with the callers.
178
213
  *
179
214
  * @param c The candidate colour node.
180
215
  * @param resolveColor The resolver mapping a {@link RawColor} to hex.
181
216
  * @returns The resolved RRGGBB, or `undefined`.
182
217
  */
183
218
  function resolveColorNode(c, resolveColor) {
219
+ if (poIs(c, "a:sysClr")) {
220
+ const last = poAttr(c, "lastClr");
221
+ if (last && /^[0-9A-Fa-f]{6}$/.test(last)) return last.toUpperCase();
222
+ return SYSTEM_COLORS.get(poAttr(c, "val") ?? "");
223
+ }
184
224
  const isSrgb = poIs(c, "a:srgbClr");
185
225
  if (!isSrgb && !poIs(c, "a:schemeClr")) return void 0;
186
226
  const v = poAttr(c, "val");
@@ -193,4 +233,4 @@ function resolveColorNode(c, resolveColor) {
193
233
  } : raw);
194
234
  }
195
235
  //#endregion
196
- export { DEFAULT_THEME_PALETTE, defaultColorResolver, makeColorResolver, resolveColorNode };
236
+ export { DEFAULT_THEME_PALETTE, applyColorMods, defaultColorResolver, makeColorResolver, readColorMods, resolveColorNode };
@@ -172,10 +172,11 @@ function regularPolygon(w, h, n) {
172
172
  function blockArrow(w, h, adjust, dir) {
173
173
  const thick = clamp(frac(adjust, "adj1", 5e4), 0, 1);
174
174
  const head = clamp(frac(adjust, "adj2", 5e4), 0, 1);
175
+ const ss = Math.min(w, h);
175
176
  if (dir === "right" || dir === "left") {
176
- const sb = h * (1 - thick) / 2;
177
- const st = h * (1 + thick) / 2;
178
- const hl = head * w;
177
+ const sb = (h - ss * thick) / 2;
178
+ const st = (h + ss * thick) / 2;
179
+ const hl = head * ss;
179
180
  if (dir === "right") {
180
181
  const bx = w - hl;
181
182
  return polygon([
@@ -199,9 +200,9 @@ function blockArrow(w, h, adjust, dir) {
199
200
  [w, st]
200
201
  ]);
201
202
  }
202
- const sl = w * (1 - thick) / 2;
203
- const sr = w * (1 + thick) / 2;
204
- const hh = head * h;
203
+ const sl = (w - ss * thick) / 2;
204
+ const sr = (w + ss * thick) / 2;
205
+ const hh = head * ss;
205
206
  if (dir === "up") {
206
207
  const by = h - hh;
207
208
  return polygon([
@@ -59,14 +59,16 @@ function columnSpark(values, w, h, color) {
59
59
  const slot = w / n;
60
60
  const bw = slot * .72;
61
61
  const off = (slot - bw) / 2;
62
- const pad = h * .06;
63
62
  const paths = [];
63
+ const at = (v) => (v - min) / span * h;
64
+ const zero = Math.min(h, Math.max(0, at(0)));
64
65
  for (let i = 0; i < n; i++) {
65
66
  const v = values[i];
66
67
  if (v === null || v === void 0) continue;
67
- const frac = (v - min) / span;
68
- const bh = Math.max(.4, pad + frac * (h - pad));
69
- paths.push(rectPath(i * slot + off, 0, bw, bh));
68
+ const y = at(v);
69
+ const bottom = Math.min(zero, y);
70
+ const bh = Math.max(.4, Math.abs(y - zero));
71
+ paths.push(rectPath(i * slot + off, bottom, bw, bh));
70
72
  }
71
73
  return paths.length > 0 ? [{
72
74
  paths,
@@ -1,3 +1,4 @@
1
+ import { PoNode } from '../po-helpers.js';
1
2
  /**
2
3
  * Parse a DrawingML theme part (ECMA-376 §20.1.6.2, `a:clrScheme`) into a
3
4
  * `name → hex` colour map. Reads the twelve scheme slots — `dk1`/`lt1`/`dk2`/
@@ -10,3 +11,43 @@
10
11
  * empty when no `a:clrScheme` is present.
11
12
  */
12
13
  export declare function parseTheme(themeXml: Uint8Array): Map<string, string>;
14
+ /**
15
+ * Parse a theme's line-style widths (ECMA-376 §20.1.4.1.21 `a:lnStyleLst`).
16
+ *
17
+ * A shape drawn from the gallery keeps its outline as `<a:lnRef idx="N">`,
18
+ * which is a 1-based index into this list — the reference names the colour and
19
+ * the list holds the width. The standard Office theme is 0.75pt / 2pt / 3pt.
20
+ *
21
+ * @param themeXml The raw theme part bytes, UTF-8.
22
+ * @returns The widths in points, in list order; empty when the theme declares
23
+ * no `a:fmtScheme`.
24
+ */
25
+ export declare function parseThemeLineWidths(themeXml: Uint8Array): Array<number>;
26
+ /**
27
+ * Parse a theme's fill styles (§20.1.4.1.13 `a:fillStyleLst`), as the raw nodes.
28
+ *
29
+ * A shape drawn from the gallery carries no fill of its own — only
30
+ * `<a:fillRef idx="N">`, a 1-based index into this list, and a colour to put
31
+ * where the styles say `phClr`. The standard Office theme's slots are a solid,
32
+ * a subtle gradient and a stronger one, so reading the reference's colour alone
33
+ * paints slot 3 flat: 47504.xlsx's rectangle is a gradient in both references
34
+ * and a single blue in ours.
35
+ *
36
+ * The nodes are handed back unparsed because what they hold is a whole fill —
37
+ * solid, gradient, pattern — which the shape readers already know how to read.
38
+ *
39
+ * @param themeXml The raw theme part bytes, UTF-8.
40
+ * @returns The `a:fillStyleLst` children in list order; empty when the theme
41
+ * declares no `a:fmtScheme`.
42
+ */
43
+ export declare function parseThemeFillStyles(themeXml: Uint8Array): Array<PoNode>;
44
+ /**
45
+ * Parse a theme's effect styles (§20.1.4.1.15 `a:effectStyleLst`), as the raw
46
+ * nodes. `<a:effectRef idx="N">` is a 1-based index into this list, exactly as
47
+ * the fill and line references index theirs.
48
+ *
49
+ * @param themeXml The raw theme part bytes, UTF-8.
50
+ * @returns The `a:effectStyle` children in list order; empty when the theme
51
+ * declares no `a:fmtScheme`.
52
+ */
53
+ export declare function parseThemeEffectStyles(themeXml: Uint8Array): Array<PoNode>;