@beyondwork/docx-react-component 1.0.48 → 1.0.49
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/README.md +16 -11
- package/package.json +30 -41
- package/src/api/public-types.ts +84 -12
- package/src/core/commands/index.ts +9 -1
- package/src/core/commands/text-commands.ts +3 -1
- package/src/core/selection/anchor-conversion.ts +112 -0
- package/src/core/selection/review-anchors.ts +108 -3
- package/src/core/state/text-transaction.ts +86 -2
- package/src/internal/harness-debug-ports.ts +168 -0
- package/src/io/chart-preview-resolver.ts +32 -1
- package/src/io/export/serialize-main-document.ts +9 -0
- package/src/io/export/serialize-paragraph-formatting.ts +8 -0
- package/src/io/export/serialize-run-formatting.ts +10 -1
- package/src/io/ooxml/chart/chart-style-table.ts +543 -0
- package/src/io/ooxml/chart/color-palette.ts +101 -0
- package/src/io/ooxml/chart/compose-series-color.ts +147 -0
- package/src/io/ooxml/chart/parse-chart-space.ts +118 -46
- package/src/io/ooxml/chart/parse-series.ts +76 -11
- package/src/io/ooxml/chart/resolve-color.ts +16 -6
- package/src/io/ooxml/chart/types.ts +30 -11
- package/src/io/ooxml/parse-complex-content.ts +6 -3
- package/src/io/ooxml/parse-main-document.ts +41 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +46 -0
- package/src/io/ooxml/parse-run-formatting.ts +49 -0
- package/src/io/ooxml/property-grab-bag.ts +211 -0
- package/src/model/canonical-document.ts +69 -3
- package/src/runtime/collab/index.ts +7 -0
- package/src/runtime/collab/runtime-collab-sync.ts +51 -0
- package/src/runtime/collab/workflow-shared.ts +247 -0
- package/src/runtime/document-locations.ts +1 -9
- package/src/runtime/document-outline.ts +1 -9
- package/src/runtime/document-runtime.ts +74 -49
- package/src/runtime/hyperlink-color-resolver.ts +119 -0
- package/src/runtime/surface-projection.ts +94 -36
- package/src/runtime/theme-color-resolver.ts +188 -0
- package/src/runtime/workflow-markup.ts +7 -18
- package/src/ui/WordReviewEditor.tsx +18 -2
- package/src/ui/editor-runtime-boundary.ts +36 -0
- package/src/ui/headless/selection-helpers.ts +10 -23
- package/src/ui/unsupported-previews-policy.ts +23 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +10 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +47 -0
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +88 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +16 -1
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOXML chart-style table (styles 1-48).
|
|
3
|
+
*
|
|
4
|
+
* Chart styles are Word's legacy mechanism for setting per-chart visual
|
|
5
|
+
* properties by a single integer id. They pre-date the 2013+ per-chart
|
|
6
|
+
* `style1.xml` + `colors1.xml` sidecar parts, but Word still emits
|
|
7
|
+
* `c:chart/c:style val="N"` (or `c14:style val="10N"` as the MC choice
|
|
8
|
+
* wrapper) for every chart, and the legacy interpretation is the
|
|
9
|
+
* rendering fallback when no sidecar is present.
|
|
10
|
+
*
|
|
11
|
+
* Microsoft does not publish a machine-readable table for styles 1-48.
|
|
12
|
+
* The semantics below were reconstructed from:
|
|
13
|
+
* - ECMA-376 Part 1 Annex K (chart-style IDs)
|
|
14
|
+
* - Microsoft's visual references in the XlsxWriter project
|
|
15
|
+
* - LibreOffice's `chart2/source/tools/ChartTypeTemplate.cxx`
|
|
16
|
+
* - Observed behavior of Word 2016 / M365 when a chart carries only
|
|
17
|
+
* a c:style element with no sidecar parts.
|
|
18
|
+
*
|
|
19
|
+
* Scope: styles 1-12. The remaining 13-48 are listed as "default" here
|
|
20
|
+
* and filled in by follow-up slices. A chart that references a style
|
|
21
|
+
* not yet in the table falls back to style 2 (the Office default) so
|
|
22
|
+
* the renderer always has a usable value.
|
|
23
|
+
*
|
|
24
|
+
* This file is pure data. No DOM, no React, no rendering code — any
|
|
25
|
+
* stage (Stage 3 SVG primitives, Stage 4 per-type renderers) can import
|
|
26
|
+
* from here without creating a layering cycle.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { FillSpec, StrokeSpec, TextProperties } from "./types.ts";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How each series gets a color.
|
|
33
|
+
*
|
|
34
|
+
* - `accent1To6`: round-robin through accent1..accent6, wrapping back
|
|
35
|
+
* to accent1 on series 7+. Default for styles 1-4 + most charts.
|
|
36
|
+
* - `monochromaticAccentN`: single accent hue with per-series tint/shade
|
|
37
|
+
* variations (first series at full strength, subsequent series
|
|
38
|
+
* lightened or darkened by fixed steps). Styles 5-10.
|
|
39
|
+
*/
|
|
40
|
+
export type SeriesColorMode =
|
|
41
|
+
| "accent1To6"
|
|
42
|
+
| "monochromaticAccent1"
|
|
43
|
+
| "monochromaticAccent2"
|
|
44
|
+
| "monochromaticAccent3"
|
|
45
|
+
| "monochromaticAccent4"
|
|
46
|
+
| "monochromaticAccent5"
|
|
47
|
+
| "monochromaticAccent6";
|
|
48
|
+
|
|
49
|
+
export interface ChartStyle {
|
|
50
|
+
/** How to assign a color to each series index. */
|
|
51
|
+
seriesColorMode: SeriesColorMode;
|
|
52
|
+
/** Background fill of the entire chart card (outside the plot area). */
|
|
53
|
+
backgroundFill?: FillSpec;
|
|
54
|
+
/** Plot area fill (behind the bars / lines / slices). */
|
|
55
|
+
plotAreaFill?: FillSpec;
|
|
56
|
+
/** Default title text properties. */
|
|
57
|
+
titleTxPr: TextProperties;
|
|
58
|
+
/** Default axis-label text properties. */
|
|
59
|
+
axisTxPr: TextProperties;
|
|
60
|
+
/** Default data-label text properties. */
|
|
61
|
+
dataLabelTxPr: TextProperties;
|
|
62
|
+
/** Default stroke around each series shape (bar/column/slice outline). */
|
|
63
|
+
seriesOutline?: StrokeSpec;
|
|
64
|
+
/** Default major gridline stroke. */
|
|
65
|
+
gridlineStroke?: StrokeSpec;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Style 2 is the Office default — it's what you get if you insert a
|
|
70
|
+
* chart in Word without touching the style picker. We use it as the
|
|
71
|
+
* fallback for unknown ids and as the anchor for derived styles.
|
|
72
|
+
*/
|
|
73
|
+
export const DEFAULT_CHART_STYLE_ID = 2;
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Common building blocks
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
const TEXT_BODY: TextProperties = {
|
|
80
|
+
fontFamily: "+mn-lt", // minor latin font from theme
|
|
81
|
+
fontSizePt: 9,
|
|
82
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
83
|
+
{ kind: "lumMod", value: 65000 },
|
|
84
|
+
{ kind: "lumOff", value: 35000 },
|
|
85
|
+
] },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const TEXT_TITLE: TextProperties = {
|
|
89
|
+
fontFamily: "+mn-lt",
|
|
90
|
+
fontSizePt: 14,
|
|
91
|
+
bold: false,
|
|
92
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
93
|
+
{ kind: "lumMod", value: 65000 },
|
|
94
|
+
{ kind: "lumOff", value: 35000 },
|
|
95
|
+
] },
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const GRIDLINE_STROKE_SUBTLE: StrokeSpec = {
|
|
99
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
100
|
+
{ kind: "lumMod", value: 15000 },
|
|
101
|
+
{ kind: "lumOff", value: 85000 },
|
|
102
|
+
] },
|
|
103
|
+
widthEmu: 9525, // 0.75 pt
|
|
104
|
+
dash: "solid",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const SERIES_OUTLINE_NONE: StrokeSpec = {
|
|
108
|
+
noFill: true,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const SERIES_OUTLINE_LIGHT: StrokeSpec = {
|
|
112
|
+
color: { kind: "scheme", value: "lt1" },
|
|
113
|
+
widthEmu: 9525,
|
|
114
|
+
dash: "solid",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Styles 1-12
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Style 1 — "Plain". Flat accent colors, no background, no outline,
|
|
123
|
+
* no gridlines. Used for minimal presentation.
|
|
124
|
+
*/
|
|
125
|
+
const STYLE_1: ChartStyle = {
|
|
126
|
+
seriesColorMode: "accent1To6",
|
|
127
|
+
backgroundFill: { kind: "none" },
|
|
128
|
+
plotAreaFill: { kind: "none" },
|
|
129
|
+
titleTxPr: TEXT_TITLE,
|
|
130
|
+
axisTxPr: TEXT_BODY,
|
|
131
|
+
dataLabelTxPr: TEXT_BODY,
|
|
132
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Style 2 — Office default. Flat accent colors, subtle gridlines,
|
|
137
|
+
* minor outline. This is what `c:style val="2"` means.
|
|
138
|
+
*/
|
|
139
|
+
const STYLE_2: ChartStyle = {
|
|
140
|
+
seriesColorMode: "accent1To6",
|
|
141
|
+
backgroundFill: { kind: "none" },
|
|
142
|
+
plotAreaFill: { kind: "none" },
|
|
143
|
+
titleTxPr: TEXT_TITLE,
|
|
144
|
+
axisTxPr: TEXT_BODY,
|
|
145
|
+
dataLabelTxPr: TEXT_BODY,
|
|
146
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
147
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Style 3 — Outlined variant of style 2. Subtle white outline on each
|
|
152
|
+
* series shape for card-style separation.
|
|
153
|
+
*/
|
|
154
|
+
const STYLE_3: ChartStyle = {
|
|
155
|
+
seriesColorMode: "accent1To6",
|
|
156
|
+
backgroundFill: { kind: "none" },
|
|
157
|
+
plotAreaFill: { kind: "none" },
|
|
158
|
+
titleTxPr: TEXT_TITLE,
|
|
159
|
+
axisTxPr: TEXT_BODY,
|
|
160
|
+
dataLabelTxPr: TEXT_BODY,
|
|
161
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
162
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Style 4 — Colorful with a light plot-area background.
|
|
167
|
+
*/
|
|
168
|
+
const STYLE_4: ChartStyle = {
|
|
169
|
+
seriesColorMode: "accent1To6",
|
|
170
|
+
backgroundFill: { kind: "none" },
|
|
171
|
+
plotAreaFill: {
|
|
172
|
+
kind: "solid",
|
|
173
|
+
color: { kind: "scheme", value: "lt1", mods: [
|
|
174
|
+
{ kind: "lumMod", value: 95000 },
|
|
175
|
+
] },
|
|
176
|
+
},
|
|
177
|
+
titleTxPr: TEXT_TITLE,
|
|
178
|
+
axisTxPr: TEXT_BODY,
|
|
179
|
+
dataLabelTxPr: TEXT_BODY,
|
|
180
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
181
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Styles 5-10 — Monochromatic. Each uses a single accent hue with
|
|
186
|
+
* per-series luminance variations. Style 5 = accent1, 6 = accent2, etc.
|
|
187
|
+
* ECMA-376 Annex K documents this six-way split.
|
|
188
|
+
*/
|
|
189
|
+
function monochromaticStyle(mode: SeriesColorMode): ChartStyle {
|
|
190
|
+
return {
|
|
191
|
+
seriesColorMode: mode,
|
|
192
|
+
backgroundFill: { kind: "none" },
|
|
193
|
+
plotAreaFill: { kind: "none" },
|
|
194
|
+
titleTxPr: TEXT_TITLE,
|
|
195
|
+
axisTxPr: TEXT_BODY,
|
|
196
|
+
dataLabelTxPr: TEXT_BODY,
|
|
197
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
198
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const STYLE_5 = monochromaticStyle("monochromaticAccent1");
|
|
203
|
+
const STYLE_6 = monochromaticStyle("monochromaticAccent2");
|
|
204
|
+
const STYLE_7 = monochromaticStyle("monochromaticAccent3");
|
|
205
|
+
const STYLE_8 = monochromaticStyle("monochromaticAccent4");
|
|
206
|
+
const STYLE_9 = monochromaticStyle("monochromaticAccent5");
|
|
207
|
+
const STYLE_10 = monochromaticStyle("monochromaticAccent6");
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Styles 11-12 — Colorful with darker plot-area background variations.
|
|
211
|
+
* These are less common in practice; rendered as decorated accent1To6.
|
|
212
|
+
*/
|
|
213
|
+
const STYLE_11: ChartStyle = {
|
|
214
|
+
seriesColorMode: "accent1To6",
|
|
215
|
+
backgroundFill: { kind: "none" },
|
|
216
|
+
plotAreaFill: {
|
|
217
|
+
kind: "solid",
|
|
218
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
219
|
+
{ kind: "lumMod", value: 5000 },
|
|
220
|
+
{ kind: "lumOff", value: 95000 },
|
|
221
|
+
] },
|
|
222
|
+
},
|
|
223
|
+
titleTxPr: TEXT_TITLE,
|
|
224
|
+
axisTxPr: TEXT_BODY,
|
|
225
|
+
dataLabelTxPr: TEXT_BODY,
|
|
226
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
227
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const STYLE_12: ChartStyle = {
|
|
231
|
+
seriesColorMode: "accent1To6",
|
|
232
|
+
backgroundFill: { kind: "none" },
|
|
233
|
+
plotAreaFill: {
|
|
234
|
+
kind: "solid",
|
|
235
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
236
|
+
{ kind: "lumMod", value: 15000 },
|
|
237
|
+
{ kind: "lumOff", value: 85000 },
|
|
238
|
+
] },
|
|
239
|
+
},
|
|
240
|
+
titleTxPr: TEXT_TITLE,
|
|
241
|
+
axisTxPr: TEXT_BODY,
|
|
242
|
+
dataLabelTxPr: TEXT_BODY,
|
|
243
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
244
|
+
gridlineStroke: GRIDLINE_STROKE_SUBTLE,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Styles 13-48
|
|
249
|
+
//
|
|
250
|
+
// Semantics below follow ECMA-376 Annex K groupings. The table mirrors the
|
|
251
|
+
// visual grid from Microsoft's XlsxWriter reference:
|
|
252
|
+
// 13-16: accent1To6 with decorated plot-area or darker gridlines
|
|
253
|
+
// 17-22: accent1To6 with various plot-area backgrounds
|
|
254
|
+
// 23-28: accent1To6 with dark (tx1-tinted) plot-area backgrounds
|
|
255
|
+
// 29-34: monochromatic on accent1..accent6 (second pass with decoration)
|
|
256
|
+
// 35-40: monochromatic on accent1..accent6 (third pass, darker)
|
|
257
|
+
// 41-48: accent1To6 with mixed backgrounds/decorations
|
|
258
|
+
//
|
|
259
|
+
// Undocumented decoration details (gradient stops, precise luminance
|
|
260
|
+
// ratios) are approximated. Fidelity refinement happens per-fixture once
|
|
261
|
+
// the renderer lands in Stage 3-4.
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
function accentDecoratedStyle(options: {
|
|
265
|
+
plotAreaFill?: FillSpec;
|
|
266
|
+
seriesOutline?: StrokeSpec;
|
|
267
|
+
gridlineStroke?: StrokeSpec;
|
|
268
|
+
} = {}): ChartStyle {
|
|
269
|
+
return {
|
|
270
|
+
seriesColorMode: "accent1To6",
|
|
271
|
+
backgroundFill: { kind: "none" },
|
|
272
|
+
plotAreaFill: options.plotAreaFill ?? { kind: "none" },
|
|
273
|
+
titleTxPr: TEXT_TITLE,
|
|
274
|
+
axisTxPr: TEXT_BODY,
|
|
275
|
+
dataLabelTxPr: TEXT_BODY,
|
|
276
|
+
seriesOutline: options.seriesOutline ?? SERIES_OUTLINE_NONE,
|
|
277
|
+
gridlineStroke: options.gridlineStroke ?? GRIDLINE_STROKE_SUBTLE,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const PLOT_AREA_FILL_LIGHT: FillSpec = {
|
|
282
|
+
kind: "solid",
|
|
283
|
+
color: { kind: "scheme", value: "lt1", mods: [
|
|
284
|
+
{ kind: "lumMod", value: 95000 },
|
|
285
|
+
] },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const PLOT_AREA_FILL_MEDIUM: FillSpec = {
|
|
289
|
+
kind: "solid",
|
|
290
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
291
|
+
{ kind: "lumMod", value: 25000 },
|
|
292
|
+
{ kind: "lumOff", value: 75000 },
|
|
293
|
+
] },
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const PLOT_AREA_FILL_DARK: FillSpec = {
|
|
297
|
+
kind: "solid",
|
|
298
|
+
color: { kind: "scheme", value: "tx1", mods: [
|
|
299
|
+
{ kind: "lumMod", value: 50000 },
|
|
300
|
+
{ kind: "lumOff", value: 50000 },
|
|
301
|
+
] },
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const GRIDLINE_STROKE_LIGHT: StrokeSpec = {
|
|
305
|
+
color: { kind: "scheme", value: "lt1", mods: [
|
|
306
|
+
{ kind: "lumMod", value: 80000 },
|
|
307
|
+
] },
|
|
308
|
+
widthEmu: 9525,
|
|
309
|
+
dash: "solid",
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Styles 13-16: decorated accent1To6 with outline variants.
|
|
313
|
+
const STYLE_13 = accentDecoratedStyle({ seriesOutline: SERIES_OUTLINE_LIGHT });
|
|
314
|
+
const STYLE_14 = accentDecoratedStyle({
|
|
315
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
316
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
317
|
+
});
|
|
318
|
+
const STYLE_15 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_LIGHT });
|
|
319
|
+
const STYLE_16 = accentDecoratedStyle({
|
|
320
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
321
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Styles 17-22: accent1To6 with progressively darker plot-area fills.
|
|
325
|
+
const STYLE_17 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_MEDIUM });
|
|
326
|
+
const STYLE_18 = accentDecoratedStyle({
|
|
327
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
328
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
329
|
+
});
|
|
330
|
+
const STYLE_19 = accentDecoratedStyle({
|
|
331
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
332
|
+
gridlineStroke: GRIDLINE_STROKE_LIGHT,
|
|
333
|
+
});
|
|
334
|
+
const STYLE_20 = accentDecoratedStyle({
|
|
335
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
336
|
+
gridlineStroke: GRIDLINE_STROKE_LIGHT,
|
|
337
|
+
});
|
|
338
|
+
const STYLE_21 = accentDecoratedStyle({
|
|
339
|
+
plotAreaFill: PLOT_AREA_FILL_DARK,
|
|
340
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
341
|
+
gridlineStroke: GRIDLINE_STROKE_LIGHT,
|
|
342
|
+
});
|
|
343
|
+
const STYLE_22 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_DARK });
|
|
344
|
+
|
|
345
|
+
// Styles 23-28: dark theme — dark plot-area background with light text.
|
|
346
|
+
const TEXT_BODY_LIGHT: TextProperties = {
|
|
347
|
+
fontFamily: "+mn-lt",
|
|
348
|
+
fontSizePt: 9,
|
|
349
|
+
color: { kind: "scheme", value: "lt1" },
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const TEXT_TITLE_LIGHT: TextProperties = {
|
|
353
|
+
fontFamily: "+mn-lt",
|
|
354
|
+
fontSizePt: 14,
|
|
355
|
+
color: { kind: "scheme", value: "lt1" },
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
function darkThemeStyle(mode: SeriesColorMode): ChartStyle {
|
|
359
|
+
return {
|
|
360
|
+
seriesColorMode: mode,
|
|
361
|
+
backgroundFill: { kind: "none" },
|
|
362
|
+
plotAreaFill: PLOT_AREA_FILL_DARK,
|
|
363
|
+
titleTxPr: TEXT_TITLE_LIGHT,
|
|
364
|
+
axisTxPr: TEXT_BODY_LIGHT,
|
|
365
|
+
dataLabelTxPr: TEXT_BODY_LIGHT,
|
|
366
|
+
seriesOutline: SERIES_OUTLINE_NONE,
|
|
367
|
+
gridlineStroke: GRIDLINE_STROKE_LIGHT,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const STYLE_23 = darkThemeStyle("accent1To6");
|
|
372
|
+
const STYLE_24 = darkThemeStyle("accent1To6");
|
|
373
|
+
const STYLE_25 = darkThemeStyle("accent1To6");
|
|
374
|
+
const STYLE_26 = darkThemeStyle("accent1To6");
|
|
375
|
+
const STYLE_27 = darkThemeStyle("accent1To6");
|
|
376
|
+
const STYLE_28 = darkThemeStyle("accent1To6");
|
|
377
|
+
|
|
378
|
+
// Styles 29-34: monochromatic second pass (accent1..accent6 with decoration).
|
|
379
|
+
const STYLE_29: ChartStyle = {
|
|
380
|
+
...monochromaticStyle("monochromaticAccent1"),
|
|
381
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
382
|
+
};
|
|
383
|
+
const STYLE_30: ChartStyle = {
|
|
384
|
+
...monochromaticStyle("monochromaticAccent2"),
|
|
385
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
386
|
+
};
|
|
387
|
+
const STYLE_31: ChartStyle = {
|
|
388
|
+
...monochromaticStyle("monochromaticAccent3"),
|
|
389
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
390
|
+
};
|
|
391
|
+
const STYLE_32: ChartStyle = {
|
|
392
|
+
...monochromaticStyle("monochromaticAccent4"),
|
|
393
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
394
|
+
};
|
|
395
|
+
const STYLE_33: ChartStyle = {
|
|
396
|
+
...monochromaticStyle("monochromaticAccent5"),
|
|
397
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
398
|
+
};
|
|
399
|
+
const STYLE_34: ChartStyle = {
|
|
400
|
+
...monochromaticStyle("monochromaticAccent6"),
|
|
401
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Styles 35-40: monochromatic third pass (darker, with medium background).
|
|
405
|
+
const STYLE_35: ChartStyle = {
|
|
406
|
+
...monochromaticStyle("monochromaticAccent1"),
|
|
407
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
408
|
+
};
|
|
409
|
+
const STYLE_36: ChartStyle = {
|
|
410
|
+
...monochromaticStyle("monochromaticAccent2"),
|
|
411
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
412
|
+
};
|
|
413
|
+
const STYLE_37: ChartStyle = {
|
|
414
|
+
...monochromaticStyle("monochromaticAccent3"),
|
|
415
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
416
|
+
};
|
|
417
|
+
const STYLE_38: ChartStyle = {
|
|
418
|
+
...monochromaticStyle("monochromaticAccent4"),
|
|
419
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
420
|
+
};
|
|
421
|
+
const STYLE_39: ChartStyle = {
|
|
422
|
+
...monochromaticStyle("monochromaticAccent5"),
|
|
423
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
424
|
+
};
|
|
425
|
+
const STYLE_40: ChartStyle = {
|
|
426
|
+
...monochromaticStyle("monochromaticAccent6"),
|
|
427
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Styles 41-48: accent1To6 mixed decoration.
|
|
431
|
+
const STYLE_41 = accentDecoratedStyle();
|
|
432
|
+
const STYLE_42 = accentDecoratedStyle({ seriesOutline: SERIES_OUTLINE_LIGHT });
|
|
433
|
+
const STYLE_43 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_LIGHT });
|
|
434
|
+
const STYLE_44 = accentDecoratedStyle({
|
|
435
|
+
plotAreaFill: PLOT_AREA_FILL_LIGHT,
|
|
436
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
437
|
+
});
|
|
438
|
+
const STYLE_45 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_MEDIUM });
|
|
439
|
+
const STYLE_46 = accentDecoratedStyle({
|
|
440
|
+
plotAreaFill: PLOT_AREA_FILL_MEDIUM,
|
|
441
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
442
|
+
});
|
|
443
|
+
const STYLE_47 = accentDecoratedStyle({ plotAreaFill: PLOT_AREA_FILL_DARK });
|
|
444
|
+
const STYLE_48: ChartStyle = {
|
|
445
|
+
seriesColorMode: "accent1To6",
|
|
446
|
+
backgroundFill: { kind: "none" },
|
|
447
|
+
plotAreaFill: PLOT_AREA_FILL_DARK,
|
|
448
|
+
titleTxPr: TEXT_TITLE_LIGHT,
|
|
449
|
+
axisTxPr: TEXT_BODY_LIGHT,
|
|
450
|
+
dataLabelTxPr: TEXT_BODY_LIGHT,
|
|
451
|
+
seriesOutline: SERIES_OUTLINE_LIGHT,
|
|
452
|
+
gridlineStroke: GRIDLINE_STROKE_LIGHT,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
// Exported lookup
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Chart-style lookup keyed by the numeric style id. Styles not yet in
|
|
461
|
+
* the table are absent; use `getChartStyle()` for safe default fallback.
|
|
462
|
+
*
|
|
463
|
+
* Styles 13-48 are intentionally absent for now. They'll land in
|
|
464
|
+
* follow-up slices as we encounter fixtures that reference them.
|
|
465
|
+
*/
|
|
466
|
+
export const CHART_STYLES: Readonly<Record<number, ChartStyle>> = {
|
|
467
|
+
1: STYLE_1,
|
|
468
|
+
2: STYLE_2,
|
|
469
|
+
3: STYLE_3,
|
|
470
|
+
4: STYLE_4,
|
|
471
|
+
5: STYLE_5,
|
|
472
|
+
6: STYLE_6,
|
|
473
|
+
7: STYLE_7,
|
|
474
|
+
8: STYLE_8,
|
|
475
|
+
9: STYLE_9,
|
|
476
|
+
10: STYLE_10,
|
|
477
|
+
11: STYLE_11,
|
|
478
|
+
12: STYLE_12,
|
|
479
|
+
13: STYLE_13,
|
|
480
|
+
14: STYLE_14,
|
|
481
|
+
15: STYLE_15,
|
|
482
|
+
16: STYLE_16,
|
|
483
|
+
17: STYLE_17,
|
|
484
|
+
18: STYLE_18,
|
|
485
|
+
19: STYLE_19,
|
|
486
|
+
20: STYLE_20,
|
|
487
|
+
21: STYLE_21,
|
|
488
|
+
22: STYLE_22,
|
|
489
|
+
23: STYLE_23,
|
|
490
|
+
24: STYLE_24,
|
|
491
|
+
25: STYLE_25,
|
|
492
|
+
26: STYLE_26,
|
|
493
|
+
27: STYLE_27,
|
|
494
|
+
28: STYLE_28,
|
|
495
|
+
29: STYLE_29,
|
|
496
|
+
30: STYLE_30,
|
|
497
|
+
31: STYLE_31,
|
|
498
|
+
32: STYLE_32,
|
|
499
|
+
33: STYLE_33,
|
|
500
|
+
34: STYLE_34,
|
|
501
|
+
35: STYLE_35,
|
|
502
|
+
36: STYLE_36,
|
|
503
|
+
37: STYLE_37,
|
|
504
|
+
38: STYLE_38,
|
|
505
|
+
39: STYLE_39,
|
|
506
|
+
40: STYLE_40,
|
|
507
|
+
41: STYLE_41,
|
|
508
|
+
42: STYLE_42,
|
|
509
|
+
43: STYLE_43,
|
|
510
|
+
44: STYLE_44,
|
|
511
|
+
45: STYLE_45,
|
|
512
|
+
46: STYLE_46,
|
|
513
|
+
47: STYLE_47,
|
|
514
|
+
48: STYLE_48,
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Look up a chart style by id. Falls back to the Office default (style 2)
|
|
519
|
+
* when the id is undefined or not in the table.
|
|
520
|
+
*/
|
|
521
|
+
export function getChartStyle(id: number | undefined): ChartStyle {
|
|
522
|
+
if (id === undefined) return CHART_STYLES[DEFAULT_CHART_STYLE_ID]!;
|
|
523
|
+
return CHART_STYLES[id] ?? CHART_STYLES[DEFAULT_CHART_STYLE_ID]!;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Resolve a raw chart-style id as it appears in OOXML to the canonical
|
|
528
|
+
* 1-48 range.
|
|
529
|
+
*
|
|
530
|
+
* Word 2013+ emits the newer scheme `c14:style val="10N"` (where 10N =
|
|
531
|
+
* 100 + legacy id) inside an `mc:AlternateContent/Choice` block, with
|
|
532
|
+
* the legacy `c:style val="N"` in the `mc:Fallback`. When a chart has
|
|
533
|
+
* only the c14 choice value and no sidecar `style1.xml`, we map
|
|
534
|
+
* 101-148 back to the legacy 1-48 range.
|
|
535
|
+
*
|
|
536
|
+
* Any value outside 1-48 or 101-148 falls back to the Office default.
|
|
537
|
+
*/
|
|
538
|
+
export function resolveChartStyleId(raw: number | undefined): number {
|
|
539
|
+
if (raw === undefined) return DEFAULT_CHART_STYLE_ID;
|
|
540
|
+
if (Number.isInteger(raw) && raw >= 1 && raw <= 48) return raw;
|
|
541
|
+
if (Number.isInteger(raw) && raw >= 101 && raw <= 148) return raw - 100;
|
|
542
|
+
return DEFAULT_CHART_STYLE_ID;
|
|
543
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default series color palette.
|
|
3
|
+
*
|
|
4
|
+
* Given a `SeriesColorMode` (from the resolved chart style) and a series
|
|
5
|
+
* index, produce a `ColorRef` describing the color Word would use for
|
|
6
|
+
* that series when no explicit `c:ser/spPr` fill is declared.
|
|
7
|
+
*
|
|
8
|
+
* This layer is purely symbolic — it returns `ColorRef` values (scheme
|
|
9
|
+
* references with optional OOXML modifiers), not concrete hex. Downstream
|
|
10
|
+
* renderer stages compose `paletteColorRef(...)` with `resolveColor(ref,
|
|
11
|
+
* theme)` to produce a final sRGB string.
|
|
12
|
+
*
|
|
13
|
+
* Two modes:
|
|
14
|
+
*
|
|
15
|
+
* - **accent1To6** — round-robin across accent1..accent6, wrapping at 6.
|
|
16
|
+
* Matches Word's default palette for styles 1-4, 11-22, 41-48.
|
|
17
|
+
*
|
|
18
|
+
* - **monochromaticAccentN** — single accent hue with per-series tint/
|
|
19
|
+
* shade variation. Series 0 is the accent at full strength; subsequent
|
|
20
|
+
* series lighten via (lumMod, lumOff) then darken via shade, producing
|
|
21
|
+
* a 6-step tint/shade ramp per accent. Matches Word's monochromatic
|
|
22
|
+
* palette for styles 5-10, 29-40.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { SeriesColorMode } from "./chart-style-table.ts";
|
|
26
|
+
import type { ColorMod, ColorRef } from "./types.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Luminance modifier ladder used for monochromatic palettes. Index 0 is
|
|
30
|
+
* the full-strength accent; subsequent entries are progressively lighter
|
|
31
|
+
* tints and then darker shades. Values are in OOXML's parts-per-100,000
|
|
32
|
+
* convention. The ladder matches Word's observed output for monochromatic
|
|
33
|
+
* styles 5-10 on real-world charts.
|
|
34
|
+
*/
|
|
35
|
+
const MONOCHROMATIC_LADDER: Array<ColorMod[] | undefined> = [
|
|
36
|
+
undefined, // series 0: accent at full strength
|
|
37
|
+
[
|
|
38
|
+
{ kind: "lumMod", value: 60000 },
|
|
39
|
+
{ kind: "lumOff", value: 40000 },
|
|
40
|
+
], // series 1: 40% tint
|
|
41
|
+
[
|
|
42
|
+
{ kind: "lumMod", value: 80000 },
|
|
43
|
+
{ kind: "lumOff", value: 20000 },
|
|
44
|
+
], // series 2: 20% tint
|
|
45
|
+
[{ kind: "shade", value: 75000 }], // series 3: 25% shade
|
|
46
|
+
[{ kind: "shade", value: 50000 }], // series 4: 50% shade
|
|
47
|
+
[
|
|
48
|
+
{ kind: "lumMod", value: 40000 },
|
|
49
|
+
{ kind: "lumOff", value: 60000 },
|
|
50
|
+
], // series 5: 60% tint (very light)
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return the ColorRef for a given palette mode and series index. Pure
|
|
55
|
+
* function — no theme lookup. Callers compose with `resolveColor`.
|
|
56
|
+
*/
|
|
57
|
+
export function paletteColorRef(
|
|
58
|
+
mode: SeriesColorMode,
|
|
59
|
+
seriesIdx: number,
|
|
60
|
+
): ColorRef {
|
|
61
|
+
const idx = Math.max(0, Math.floor(seriesIdx));
|
|
62
|
+
if (mode === "accent1To6") {
|
|
63
|
+
const slot = `accent${(idx % 6) + 1}`;
|
|
64
|
+
return { kind: "scheme", value: slot };
|
|
65
|
+
}
|
|
66
|
+
// monochromaticAccentN
|
|
67
|
+
const accentSlot = monochromaticAccentSlot(mode);
|
|
68
|
+
const mods = MONOCHROMATIC_LADDER[idx % MONOCHROMATIC_LADDER.length];
|
|
69
|
+
const ref: ColorRef = { kind: "scheme", value: accentSlot };
|
|
70
|
+
if (mods && mods.length > 0) ref.mods = mods;
|
|
71
|
+
return ref;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Factory variant — returns a closure for callers that want to bind a
|
|
76
|
+
* mode once and call `palette(seriesIdx)` repeatedly in a render loop.
|
|
77
|
+
*/
|
|
78
|
+
export function makePalette(
|
|
79
|
+
mode: SeriesColorMode,
|
|
80
|
+
): (seriesIdx: number) => ColorRef {
|
|
81
|
+
return (seriesIdx: number) => paletteColorRef(mode, seriesIdx);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function monochromaticAccentSlot(mode: SeriesColorMode): string {
|
|
85
|
+
switch (mode) {
|
|
86
|
+
case "monochromaticAccent1":
|
|
87
|
+
return "accent1";
|
|
88
|
+
case "monochromaticAccent2":
|
|
89
|
+
return "accent2";
|
|
90
|
+
case "monochromaticAccent3":
|
|
91
|
+
return "accent3";
|
|
92
|
+
case "monochromaticAccent4":
|
|
93
|
+
return "accent4";
|
|
94
|
+
case "monochromaticAccent5":
|
|
95
|
+
return "accent5";
|
|
96
|
+
case "monochromaticAccent6":
|
|
97
|
+
return "accent6";
|
|
98
|
+
default:
|
|
99
|
+
return "accent1";
|
|
100
|
+
}
|
|
101
|
+
}
|