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.
- package/dist/esm/core/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +187 -1
- package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
- package/dist/esm/core/drawingml/chart-geometry.js +371 -88
- package/dist/esm/core/drawingml/chart-parser.js +262 -11
- package/dist/esm/core/drawingml/colors.d.ts +6 -6
- package/dist/esm/core/drawingml/colors.js +58 -18
- package/dist/esm/core/drawingml/preset-geometry.js +7 -6
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
- package/dist/esm/core/drawingml/theme-parser.js +74 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.js +172 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/sheet.d.ts +61 -0
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/vector.d.ts +13 -0
- package/dist/esm/excel/activex-parser.js +1 -0
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +898 -76
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +551 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +150 -61
- package/dist/esm/excel/xlsx-reader.js +242 -11
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +11 -2
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +493 -64
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/styled-page-emitter.js +164 -32
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +3 -1
- package/dist/esm/pdf/vector-graphics.js +19 -2
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.js +12 -1
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.js +1 -0
- package/dist/esm/word/drawing-parser.d.ts +34 -1
- package/dist/esm/word/drawing-parser.js +52 -2
- package/dist/esm/word/numbering-parser.js +1 -0
- package/dist/esm/word/settings-parser.js +1 -0
- package/dist/esm/word/styles-parser.js +1 -0
- package/package.json +7 -3
- 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
|
|
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
|
|
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
|
|
47
|
-
const
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
64
|
-
* transforms applied). Returns `undefined` when the node is some other
|
|
65
|
-
* valueless, or the resolver does not know the colour. Container
|
|
66
|
-
* (stop at the first colour node vs continue past unresolved
|
|
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))
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
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
|
|
177
|
-
const st = h
|
|
178
|
-
const hl = head *
|
|
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
|
|
203
|
-
const sr = w
|
|
204
|
-
const hh = head *
|
|
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
|
|
68
|
-
const
|
|
69
|
-
|
|
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>;
|