@beyondwork/docx-react-component 1.0.47 → 1.0.48
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/package.json +1 -1
- package/src/api/public-types.ts +115 -1
- package/src/compare/diff-engine.ts +4 -0
- package/src/core/commands/add-scope.ts +257 -0
- package/src/core/commands/formatting-commands.ts +2 -0
- package/src/core/schema/text-schema.ts +95 -1
- package/src/core/state/text-transaction.ts +17 -5
- package/src/io/chart-preview-resolver.ts +27 -0
- package/src/io/docx-session.ts +226 -38
- package/src/io/export/serialize-main-document.ts +37 -0
- package/src/io/export/serialize-settings.ts +421 -0
- package/src/io/export/serialize-styles.ts +10 -0
- package/src/io/normalize/normalize-text.ts +1 -0
- package/src/io/ooxml/chart/parse-axis.ts +277 -0
- package/src/io/ooxml/chart/parse-chart-space.ts +813 -0
- package/src/io/ooxml/chart/parse-series.ts +570 -0
- package/src/io/ooxml/chart/resolve-color.ts +251 -0
- package/src/io/ooxml/chart/types.ts +420 -0
- package/src/io/ooxml/parse-block-structure.ts +99 -0
- package/src/io/ooxml/parse-complex-content.ts +87 -2
- package/src/io/ooxml/parse-main-document.ts +115 -1
- package/src/io/ooxml/parse-scope-markers.ts +184 -0
- package/src/io/ooxml/parse-settings-blueprint.ts +349 -0
- package/src/io/ooxml/parse-settings.ts +97 -1
- package/src/io/ooxml/parse-styles.ts +65 -0
- package/src/io/ooxml/parse-theme.ts +2 -127
- package/src/io/ooxml/xml-attr-helpers.ts +59 -1
- package/src/io/ooxml/xml-parser.ts +142 -0
- package/src/model/canonical-document.ts +94 -0
- package/src/model/scope-markers.ts +144 -0
- package/src/runtime/collab/base-doc-fingerprint.ts +99 -0
- package/src/runtime/collab/checkpoint-election.ts +75 -0
- package/src/runtime/collab/checkpoint-scheduler.ts +204 -0
- package/src/runtime/collab/checkpoint-store.ts +115 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +167 -0
- package/src/runtime/collab/runtime-collab-sync.ts +279 -0
- package/src/runtime/document-runtime.ts +214 -16
- package/src/runtime/editor-surface/capabilities.ts +63 -50
- package/src/runtime/layout/layout-engine-version.ts +8 -1
- package/src/runtime/prerender/cache-envelope.ts +19 -7
- package/src/runtime/prerender/cache-key.ts +25 -14
- package/src/runtime/prerender/canonical-document-hash.ts +63 -0
- package/src/runtime/prerender/customxml-cache.ts +211 -0
- package/src/runtime/prerender/customxml-probe.ts +78 -0
- package/src/runtime/prerender/prerender-document.ts +74 -7
- package/src/runtime/scope-resolver.ts +148 -0
- package/src/runtime/scope-tag-registry.ts +10 -0
- package/src/runtime/surface-projection.ts +8 -1
- package/src/ui/WordReviewEditor.tsx +30 -0
- package/src/ui/editor-runtime-boundary.ts +6 -1
- package/src/ui/runtime-shortcut-dispatch.ts +12 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a `ColorRef` into a concrete sRGB hex string.
|
|
3
|
+
*
|
|
4
|
+
* Implements the second layer of the OOXML chart-color cascade
|
|
5
|
+
* (docs/plans/lane-5-charts.md § 4 Task 2.2): scheme-color and
|
|
6
|
+
* themeLinked references are looked up in `ResolvedTheme.colors`, and
|
|
7
|
+
* OOXML color modifiers (`lumMod`, `lumOff`, `shade`, `tint`, `satMod`,
|
|
8
|
+
* `hueMod`) are applied in declaration order via HSL color math per
|
|
9
|
+
* ECMA-376 § 20.1.2.3.x.
|
|
10
|
+
*
|
|
11
|
+
* The output is always a valid `#RRGGBB` uppercase hex string. Bad or
|
|
12
|
+
* missing inputs fall through to a legible default (#808080) rather than
|
|
13
|
+
* throwing — renderers must never see `undefined` for a color slot.
|
|
14
|
+
*
|
|
15
|
+
* Alpha modifiers are intentionally NOT applied to the sRGB output;
|
|
16
|
+
* callers that want opacity must emit it as a separate channel. This
|
|
17
|
+
* keeps the return type a pure color and makes the resolver idempotent.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ColorMod, ColorRef } from "./types.ts";
|
|
21
|
+
import type { ResolvedTheme } from "../../../model/canonical-document.ts";
|
|
22
|
+
|
|
23
|
+
const FALLBACK_COLOR = "#808080";
|
|
24
|
+
const OOXML_UNIT = 100_000; // modifier val is in parts per 100,000
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scheme-alias map. Word often emits aliases that refer to the primary
|
|
28
|
+
* color slots: `tx1` → `dk1`, `bg1` → `lt1`, `tx2` → `dk2`, `bg2` → `lt2`.
|
|
29
|
+
* The mapping is standardized by DrawingML.
|
|
30
|
+
*/
|
|
31
|
+
const SCHEME_ALIASES: Record<string, string> = {
|
|
32
|
+
tx1: "dk1",
|
|
33
|
+
bg1: "lt1",
|
|
34
|
+
tx2: "dk2",
|
|
35
|
+
bg2: "lt2",
|
|
36
|
+
phClr: "accent1", // placeholder color — callers usually supply their own
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function resolveColor(ref: ColorRef, theme: ResolvedTheme): string {
|
|
40
|
+
const baseHex = baseColor(ref, theme);
|
|
41
|
+
const rgb = hexToRgb(baseHex);
|
|
42
|
+
const withMods = applyMods(rgb, "mods" in ref ? ref.mods : undefined);
|
|
43
|
+
return rgbToHex(withMods);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Base-color lookup (before modifiers)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
function baseColor(ref: ColorRef, theme: ResolvedTheme): string {
|
|
51
|
+
switch (ref.kind) {
|
|
52
|
+
case "srgb":
|
|
53
|
+
return normalizeHex(ref.value);
|
|
54
|
+
case "scheme":
|
|
55
|
+
case "themeLinked": {
|
|
56
|
+
const slot = SCHEME_ALIASES[ref.value] ?? ref.value;
|
|
57
|
+
const hex = theme.colors[slot];
|
|
58
|
+
return hex ? normalizeHex(hex) : FALLBACK_COLOR;
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
return FALLBACK_COLOR;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Normalize any `#RRGGBB` or `RRGGBB` string to uppercase with a leading
|
|
67
|
+
* `#`. Returns the fallback color on any malformed input so the output
|
|
68
|
+
* contract (`/^#[0-9A-F]{6}$/`) is always satisfied.
|
|
69
|
+
*/
|
|
70
|
+
function normalizeHex(raw: string): string {
|
|
71
|
+
const m = /^#?([0-9A-Fa-f]{6})$/.exec(raw);
|
|
72
|
+
if (!m) return FALLBACK_COLOR;
|
|
73
|
+
return `#${m[1]!.toUpperCase()}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Modifier pipeline
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
interface Rgb {
|
|
81
|
+
r: number;
|
|
82
|
+
g: number;
|
|
83
|
+
b: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function applyMods(rgb: Rgb, mods: readonly ColorMod[] | undefined): Rgb {
|
|
87
|
+
if (!mods || mods.length === 0) return rgb;
|
|
88
|
+
let current = rgb;
|
|
89
|
+
for (const mod of mods) {
|
|
90
|
+
current = applyMod(current, mod);
|
|
91
|
+
}
|
|
92
|
+
return current;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function applyMod(rgb: Rgb, mod: ColorMod): Rgb {
|
|
96
|
+
const frac = mod.value / OOXML_UNIT;
|
|
97
|
+
switch (mod.kind) {
|
|
98
|
+
case "lumMod": {
|
|
99
|
+
// Scale luminance.
|
|
100
|
+
const hsl = rgbToHsl(rgb);
|
|
101
|
+
hsl.l = clamp01(hsl.l * frac);
|
|
102
|
+
return hslToRgb(hsl);
|
|
103
|
+
}
|
|
104
|
+
case "lumOff": {
|
|
105
|
+
// Add to luminance.
|
|
106
|
+
const hsl = rgbToHsl(rgb);
|
|
107
|
+
hsl.l = clamp01(hsl.l + frac);
|
|
108
|
+
return hslToRgb(hsl);
|
|
109
|
+
}
|
|
110
|
+
case "satMod": {
|
|
111
|
+
// Scale saturation.
|
|
112
|
+
const hsl = rgbToHsl(rgb);
|
|
113
|
+
hsl.s = clamp01(hsl.s * frac);
|
|
114
|
+
return hslToRgb(hsl);
|
|
115
|
+
}
|
|
116
|
+
case "hueMod": {
|
|
117
|
+
// Rotate hue. val is in OOXML's angle-like units (1/100,000 turn →
|
|
118
|
+
// degrees = frac * 360). The spec is slightly ambiguous here; our
|
|
119
|
+
// interpretation is that hueMod=1.0 is a full rotation.
|
|
120
|
+
const hsl = rgbToHsl(rgb);
|
|
121
|
+
hsl.h = (hsl.h + frac * 360) % 360;
|
|
122
|
+
if (hsl.h < 0) hsl.h += 360;
|
|
123
|
+
return hslToRgb(hsl);
|
|
124
|
+
}
|
|
125
|
+
case "shade": {
|
|
126
|
+
// Scale channels toward black. Per ECMA-376 § 20.1.2.3.31, shade is
|
|
127
|
+
// applied in the sRGB color space, not HSL — this is important
|
|
128
|
+
// because it preserves hue and saturation while darkening.
|
|
129
|
+
return {
|
|
130
|
+
r: clamp255(rgb.r * frac),
|
|
131
|
+
g: clamp255(rgb.g * frac),
|
|
132
|
+
b: clamp255(rgb.b * frac),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
case "tint": {
|
|
136
|
+
// Scale channels toward white. Per ECMA-376 § 20.1.2.3.34, tint is
|
|
137
|
+
// applied in sRGB space: C' = C * frac + 255 * (1 - frac).
|
|
138
|
+
// Note: this is equivalent to "lighten by (1-frac)".
|
|
139
|
+
const inv = 1 - frac;
|
|
140
|
+
return {
|
|
141
|
+
r: clamp255(rgb.r * frac + 255 * inv),
|
|
142
|
+
g: clamp255(rgb.g * frac + 255 * inv),
|
|
143
|
+
b: clamp255(rgb.b * frac + 255 * inv),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case "alpha":
|
|
147
|
+
// Opacity — not applied to sRGB output. Callers that need opacity
|
|
148
|
+
// must query the mods list directly.
|
|
149
|
+
return rgb;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Color-space conversions
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
function hexToRgb(hex: string): Rgb {
|
|
158
|
+
const n = Number.parseInt(hex.slice(1), 16);
|
|
159
|
+
return {
|
|
160
|
+
r: (n >>> 16) & 0xff,
|
|
161
|
+
g: (n >>> 8) & 0xff,
|
|
162
|
+
b: n & 0xff,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rgbToHex(rgb: Rgb): string {
|
|
167
|
+
const r = Math.round(clamp255(rgb.r));
|
|
168
|
+
const g = Math.round(clamp255(rgb.g));
|
|
169
|
+
const b = Math.round(clamp255(rgb.b));
|
|
170
|
+
return `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toHex2(n: number): string {
|
|
174
|
+
return n.toString(16).padStart(2, "0").toUpperCase();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface Hsl {
|
|
178
|
+
/** Hue in degrees [0, 360). */
|
|
179
|
+
h: number;
|
|
180
|
+
/** Saturation in [0, 1]. */
|
|
181
|
+
s: number;
|
|
182
|
+
/** Luminance in [0, 1]. */
|
|
183
|
+
l: number;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function rgbToHsl(rgb: Rgb): Hsl {
|
|
187
|
+
const r = rgb.r / 255;
|
|
188
|
+
const g = rgb.g / 255;
|
|
189
|
+
const b = rgb.b / 255;
|
|
190
|
+
const max = Math.max(r, g, b);
|
|
191
|
+
const min = Math.min(r, g, b);
|
|
192
|
+
const l = (max + min) / 2;
|
|
193
|
+
let h = 0;
|
|
194
|
+
let s = 0;
|
|
195
|
+
if (max !== min) {
|
|
196
|
+
const delta = max - min;
|
|
197
|
+
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
198
|
+
if (max === r) h = ((g - b) / delta) % 6;
|
|
199
|
+
else if (max === g) h = (b - r) / delta + 2;
|
|
200
|
+
else h = (r - g) / delta + 4;
|
|
201
|
+
h *= 60;
|
|
202
|
+
if (h < 0) h += 360;
|
|
203
|
+
}
|
|
204
|
+
return { h, s, l };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function hslToRgb(hsl: Hsl): Rgb {
|
|
208
|
+
const { h, s, l } = hsl;
|
|
209
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
210
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
211
|
+
const m = l - c / 2;
|
|
212
|
+
let r1 = 0;
|
|
213
|
+
let g1 = 0;
|
|
214
|
+
let b1 = 0;
|
|
215
|
+
if (h < 60) {
|
|
216
|
+
r1 = c;
|
|
217
|
+
g1 = x;
|
|
218
|
+
} else if (h < 120) {
|
|
219
|
+
r1 = x;
|
|
220
|
+
g1 = c;
|
|
221
|
+
} else if (h < 180) {
|
|
222
|
+
g1 = c;
|
|
223
|
+
b1 = x;
|
|
224
|
+
} else if (h < 240) {
|
|
225
|
+
g1 = x;
|
|
226
|
+
b1 = c;
|
|
227
|
+
} else if (h < 300) {
|
|
228
|
+
r1 = x;
|
|
229
|
+
b1 = c;
|
|
230
|
+
} else {
|
|
231
|
+
r1 = c;
|
|
232
|
+
b1 = x;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
r: (r1 + m) * 255,
|
|
236
|
+
g: (g1 + m) * 255,
|
|
237
|
+
b: (b1 + m) * 255,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function clamp01(x: number): number {
|
|
242
|
+
if (x < 0) return 0;
|
|
243
|
+
if (x > 1) return 1;
|
|
244
|
+
return x;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function clamp255(x: number): number {
|
|
248
|
+
if (x < 0) return 0;
|
|
249
|
+
if (x > 255) return 255;
|
|
250
|
+
return x;
|
|
251
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart data-model types (Stage 1).
|
|
3
|
+
*
|
|
4
|
+
* Pure type declarations. No runtime code, no imports from runtime modules.
|
|
5
|
+
* `ChartModel` is a discriminated union (by `kind`) covering every chart
|
|
6
|
+
* family we parse or intentionally mark as unsupported. Parsed models retain
|
|
7
|
+
* the source XML in `rawXml` so the export path (which re-emits the
|
|
8
|
+
* original drawing verbatim) stays byte-identical regardless of renderer
|
|
9
|
+
* coverage.
|
|
10
|
+
*
|
|
11
|
+
* Stage 2 will add the theme/colors/style cascade that turns `ColorRef`
|
|
12
|
+
* values into concrete sRGB strings. Until then, colors are declared as
|
|
13
|
+
* references; consumers of Stage 1 models should treat color resolution as
|
|
14
|
+
* "not yet available."
|
|
15
|
+
*
|
|
16
|
+
* See docs/plans/lane-5-charts.md §3 Task 1.1 for the full specification.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Top-level union
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export type ChartModel =
|
|
24
|
+
| BarChartModel
|
|
25
|
+
| LineChartModel
|
|
26
|
+
| PieChartModel
|
|
27
|
+
| AreaChartModel
|
|
28
|
+
| ScatterChartModel
|
|
29
|
+
| BubbleChartModel
|
|
30
|
+
| ComboChartModel
|
|
31
|
+
| UnsupportedChartModel;
|
|
32
|
+
|
|
33
|
+
export interface ChartCommon {
|
|
34
|
+
/** Chart title, if present. */
|
|
35
|
+
title?: Title;
|
|
36
|
+
/** Legend configuration; absent if the chart has no legend. */
|
|
37
|
+
legend?: Legend;
|
|
38
|
+
/** Whether to plot only visible cells (c:plotVisOnly). */
|
|
39
|
+
plotVisOnly: boolean;
|
|
40
|
+
/** How to render blank/null values (c:dispBlanksAs). */
|
|
41
|
+
dispBlanksAs: "gap" | "zero" | "span";
|
|
42
|
+
/** Legacy chart style id (c:style val, 1–48), if present. */
|
|
43
|
+
styleId?: number;
|
|
44
|
+
/** Theme color scheme slot names available to this chart (accent1..6 etc.). */
|
|
45
|
+
themeColorScheme?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* The raw chart-space XML that produced this model. Preserved so the
|
|
48
|
+
* export path can re-emit the original drawing without depending on the
|
|
49
|
+
* renderer's coverage of a given chart family.
|
|
50
|
+
*/
|
|
51
|
+
rawXml: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BarChartModel extends ChartCommon {
|
|
55
|
+
kind: "bar";
|
|
56
|
+
/** "bar" = horizontal; "column" = vertical. */
|
|
57
|
+
direction: "bar" | "column";
|
|
58
|
+
grouping: "clustered" | "stacked" | "percentStacked" | "standard";
|
|
59
|
+
/** Gap between categories in percent of bar width (c:gapWidth). */
|
|
60
|
+
gapWidth: number;
|
|
61
|
+
/** Overlap within a category in percent, -100..100 (c:overlap). */
|
|
62
|
+
overlap: number;
|
|
63
|
+
series: Series[];
|
|
64
|
+
categoryAxis: CategoryAxis;
|
|
65
|
+
valueAxis: ValueAxis;
|
|
66
|
+
/** Present when any series/group targeted the secondary axis. */
|
|
67
|
+
secondaryValueAxis?: ValueAxis;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface LineChartModel extends ChartCommon {
|
|
71
|
+
kind: "line";
|
|
72
|
+
grouping: "standard" | "stacked" | "percentStacked";
|
|
73
|
+
/** Global smooth flag; per-series values can override. */
|
|
74
|
+
smooth: boolean;
|
|
75
|
+
/** Global marker flag; per-series values can override. */
|
|
76
|
+
marker: boolean;
|
|
77
|
+
series: LineSeries[];
|
|
78
|
+
categoryAxis: CategoryAxis;
|
|
79
|
+
valueAxis: ValueAxis;
|
|
80
|
+
secondaryValueAxis?: ValueAxis;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PieChartModel extends ChartCommon {
|
|
84
|
+
kind: "pie";
|
|
85
|
+
doughnut: boolean;
|
|
86
|
+
/** Doughnut hole size in percent, typical 10–75 (c:holeSize). */
|
|
87
|
+
holeSizePercent?: number;
|
|
88
|
+
/** Clockwise degrees from 12 o'clock, 0–360 (c:firstSliceAngle / 60000). */
|
|
89
|
+
firstSliceAngle: number;
|
|
90
|
+
varyColors: boolean;
|
|
91
|
+
/** Almost always one series; pie-of-pie is length 2 (deferred). */
|
|
92
|
+
series: PieSeries[];
|
|
93
|
+
/** Category labels (from the first series' c:cat). */
|
|
94
|
+
categoryLabels: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface AreaChartModel extends ChartCommon {
|
|
98
|
+
kind: "area";
|
|
99
|
+
grouping: "standard" | "stacked" | "percentStacked";
|
|
100
|
+
series: Series[];
|
|
101
|
+
categoryAxis: CategoryAxis;
|
|
102
|
+
valueAxis: ValueAxis;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ScatterChartModel extends ChartCommon {
|
|
106
|
+
kind: "scatter";
|
|
107
|
+
style: "line" | "lineMarker" | "marker" | "smooth" | "smoothMarker";
|
|
108
|
+
series: ScatterSeries[];
|
|
109
|
+
/** Scatter uses a numeric x-axis, not a category axis. */
|
|
110
|
+
xAxis: ValueAxis;
|
|
111
|
+
yAxis: ValueAxis;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface BubbleChartModel extends ChartCommon {
|
|
115
|
+
kind: "bubble";
|
|
116
|
+
bubble3D: boolean;
|
|
117
|
+
series: BubbleSeries[];
|
|
118
|
+
xAxis: ValueAxis;
|
|
119
|
+
yAxis: ValueAxis;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ComboChartModel extends ChartCommon {
|
|
123
|
+
kind: "combo";
|
|
124
|
+
/**
|
|
125
|
+
* Each type-group (bar/line/area) contributes its own series list. Axes
|
|
126
|
+
* may be shared or split into primary/secondary pairs.
|
|
127
|
+
*/
|
|
128
|
+
groups: Array<BarChartModel | LineChartModel | AreaChartModel>;
|
|
129
|
+
/** True if any group targeted a secondary value axis. */
|
|
130
|
+
hasSecondaryAxis: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface UnsupportedChartModel extends ChartCommon {
|
|
134
|
+
kind: "unsupported";
|
|
135
|
+
reason: UnsupportedReason;
|
|
136
|
+
/** Human-readable detail (e.g. "Chart family lineChart not yet implemented"). */
|
|
137
|
+
detail: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Discriminator values for `UnsupportedChartModel.reason`.
|
|
142
|
+
*
|
|
143
|
+
* - `not-yet-implemented`: the parser recognises the chart family but a
|
|
144
|
+
* subsequent slice is responsible for implementing it (line/pie/area/
|
|
145
|
+
* scatter/bubble/combo in this slice).
|
|
146
|
+
* - `pivot`/`stock`/`surface`/…: families that are intentionally deferred
|
|
147
|
+
* indefinitely per the plan's non-goals (rare in legal/finance corpora,
|
|
148
|
+
* disproportionate renderer cost).
|
|
149
|
+
* - `no-plot-area`: the chart XML had no c:plotArea child.
|
|
150
|
+
* - `parse-error`: unexpected structure or exception during parse.
|
|
151
|
+
*/
|
|
152
|
+
export type UnsupportedReason =
|
|
153
|
+
| "not-yet-implemented"
|
|
154
|
+
| "pivot"
|
|
155
|
+
| "stock"
|
|
156
|
+
| "surface"
|
|
157
|
+
| "treemap"
|
|
158
|
+
| "sunburst"
|
|
159
|
+
| "histogram"
|
|
160
|
+
| "waterfall"
|
|
161
|
+
| "funnel"
|
|
162
|
+
| "map"
|
|
163
|
+
| "no-plot-area"
|
|
164
|
+
| "parse-error";
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Series
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
export interface SeriesBase {
|
|
171
|
+
/** c:idx — render order. */
|
|
172
|
+
idx: number;
|
|
173
|
+
/** c:order — data order. */
|
|
174
|
+
order: number;
|
|
175
|
+
/** Series display name (c:tx/c:strRef/c:strCache or c:v). */
|
|
176
|
+
name?: string;
|
|
177
|
+
/** Explicit fill/stroke overrides from c:spPr. */
|
|
178
|
+
spPr?: ShapeProperties;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface Series extends SeriesBase {
|
|
182
|
+
/** Category labels; string-form even when c:numRef was used. */
|
|
183
|
+
categories: string[];
|
|
184
|
+
/** Numeric values; `null` for blank/missing points (sparse cache). */
|
|
185
|
+
values: Array<number | null>;
|
|
186
|
+
dataLabels?: DataLabelsSpec;
|
|
187
|
+
/** Per-data-point overrides (c:dPt). */
|
|
188
|
+
dataPoints?: DataPointOverride[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface LineSeries extends Series {
|
|
192
|
+
/** Per-series smooth override. */
|
|
193
|
+
smooth?: boolean;
|
|
194
|
+
marker?: MarkerSpec;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface PieSeries extends SeriesBase {
|
|
198
|
+
values: Array<number | null>;
|
|
199
|
+
/** Per-series default explosion percent. */
|
|
200
|
+
explosion?: number;
|
|
201
|
+
dataPoints?: DataPointOverride[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface ScatterSeries extends SeriesBase {
|
|
205
|
+
xValues: Array<number | null>;
|
|
206
|
+
yValues: Array<number | null>;
|
|
207
|
+
smooth?: boolean;
|
|
208
|
+
marker?: MarkerSpec;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface BubbleSeries extends SeriesBase {
|
|
212
|
+
xValues: Array<number | null>;
|
|
213
|
+
yValues: Array<number | null>;
|
|
214
|
+
sizes: Array<number | null>;
|
|
215
|
+
bubbleScale?: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Axes
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
export type Axis = CategoryAxis | ValueAxis | DateAxis | SeriesAxis;
|
|
223
|
+
|
|
224
|
+
export interface AxisBase {
|
|
225
|
+
/** c:axId — unique within the plot area. */
|
|
226
|
+
id: string;
|
|
227
|
+
/** c:crossAx — the id of the perpendicular axis this one crosses. */
|
|
228
|
+
crossAxisId?: string;
|
|
229
|
+
/** Value at which the perpendicular axis crosses this one (c:crossesAt). */
|
|
230
|
+
crossesAt?: number | "autoZero" | "max" | "min";
|
|
231
|
+
/** c:axPos: b = bottom, t = top, l = left, r = right. */
|
|
232
|
+
position: "b" | "t" | "l" | "r";
|
|
233
|
+
/**
|
|
234
|
+
* Whether the axis is visible. Note: c:delete has inverted semantics —
|
|
235
|
+
* `val="1"` means invisible; we store the normal-sense boolean here.
|
|
236
|
+
*/
|
|
237
|
+
visible: boolean;
|
|
238
|
+
title?: Title;
|
|
239
|
+
majorGridlines?: boolean;
|
|
240
|
+
minorGridlines?: boolean;
|
|
241
|
+
majorUnit?: number;
|
|
242
|
+
minorUnit?: number;
|
|
243
|
+
/** c:numFmt/@formatCode. */
|
|
244
|
+
numberFormat?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface CategoryAxis extends AxisBase {
|
|
248
|
+
kind: "category";
|
|
249
|
+
/** c:auto — whether category width is chosen automatically. */
|
|
250
|
+
auto: boolean;
|
|
251
|
+
labelAlign?: "ctr" | "l" | "r";
|
|
252
|
+
labelOffset?: number;
|
|
253
|
+
tickMark?: "none" | "in" | "out" | "cross";
|
|
254
|
+
tickLabelSkip?: number;
|
|
255
|
+
tickMarkSkip?: number;
|
|
256
|
+
/** Category labels resolved from the first series' c:cat cache. */
|
|
257
|
+
categoryLabels: string[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface ValueAxis extends AxisBase {
|
|
261
|
+
kind: "value";
|
|
262
|
+
min?: number;
|
|
263
|
+
max?: number;
|
|
264
|
+
logBase?: number;
|
|
265
|
+
/** True when c:scaling/c:orientation val="maxMin". */
|
|
266
|
+
reverse: boolean;
|
|
267
|
+
crossBetween?: "between" | "midCat";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface DateAxis extends AxisBase {
|
|
271
|
+
kind: "date";
|
|
272
|
+
baseTimeUnit?: "days" | "months" | "years";
|
|
273
|
+
/** Serial date (days since 1899-12-30). */
|
|
274
|
+
min?: number;
|
|
275
|
+
max?: number;
|
|
276
|
+
majorTimeUnit?: "days" | "months" | "years";
|
|
277
|
+
minorTimeUnit?: "days" | "months" | "years";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface SeriesAxis extends AxisBase {
|
|
281
|
+
kind: "series";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Other chart elements
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
export interface Title {
|
|
289
|
+
/**
|
|
290
|
+
* Plain-text rendering of the title. Rich-text formatting is not preserved
|
|
291
|
+
* at Stage 1; Stage 2/3 may extend this with per-run styling.
|
|
292
|
+
*/
|
|
293
|
+
text?: string;
|
|
294
|
+
overlay: boolean;
|
|
295
|
+
spPr?: ShapeProperties;
|
|
296
|
+
txPr?: TextProperties;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface Legend {
|
|
300
|
+
position: "b" | "t" | "l" | "r" | "tr";
|
|
301
|
+
overlay: boolean;
|
|
302
|
+
spPr?: ShapeProperties;
|
|
303
|
+
txPr?: TextProperties;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface DataLabelsSpec {
|
|
307
|
+
showVal: boolean;
|
|
308
|
+
showCatName: boolean;
|
|
309
|
+
showSerName: boolean;
|
|
310
|
+
showPercent: boolean;
|
|
311
|
+
showBubbleSize: boolean;
|
|
312
|
+
showLegendKey: boolean;
|
|
313
|
+
position?: "b" | "ctr" | "l" | "r" | "t" | "bestFit" | "inBase" | "inEnd" | "outEnd";
|
|
314
|
+
separator?: string;
|
|
315
|
+
numberFormat?: string;
|
|
316
|
+
txPr?: TextProperties;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export interface MarkerSpec {
|
|
320
|
+
symbol:
|
|
321
|
+
| "circle"
|
|
322
|
+
| "square"
|
|
323
|
+
| "diamond"
|
|
324
|
+
| "triangle"
|
|
325
|
+
| "x"
|
|
326
|
+
| "star"
|
|
327
|
+
| "dot"
|
|
328
|
+
| "dash"
|
|
329
|
+
| "plus"
|
|
330
|
+
| "picture"
|
|
331
|
+
| "none"
|
|
332
|
+
| "auto";
|
|
333
|
+
/** Point size 2..72. */
|
|
334
|
+
size?: number;
|
|
335
|
+
spPr?: ShapeProperties;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface DataPointOverride {
|
|
339
|
+
/** Index of the data point within the series. */
|
|
340
|
+
idx: number;
|
|
341
|
+
spPr?: ShapeProperties;
|
|
342
|
+
marker?: MarkerSpec;
|
|
343
|
+
/** Pie only: per-slice explosion percent. */
|
|
344
|
+
explosion?: number;
|
|
345
|
+
/** Bar only: render negative values with inverted fill. */
|
|
346
|
+
invertIfNegative?: boolean;
|
|
347
|
+
/** Bubble only: 3D bubble flag (rendered as 2D regardless). */
|
|
348
|
+
bubble3D?: boolean;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// Shape / fill / stroke / color primitives
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
export interface ShapeProperties {
|
|
356
|
+
fill?: FillSpec;
|
|
357
|
+
stroke?: StrokeSpec;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export type FillSpec =
|
|
361
|
+
| { kind: "solid"; color: ColorRef }
|
|
362
|
+
| { kind: "gradient"; stops: Array<{ pos: number; color: ColorRef }>; angle?: number }
|
|
363
|
+
| { kind: "none" }
|
|
364
|
+
| { kind: "auto" };
|
|
365
|
+
|
|
366
|
+
export interface StrokeSpec {
|
|
367
|
+
color?: ColorRef;
|
|
368
|
+
/** Stroke width in EMU (English Metric Units). */
|
|
369
|
+
widthEmu?: number;
|
|
370
|
+
dash?: "solid" | "dash" | "dashDot" | "lgDash" | "lgDashDot" | "sysDash" | "sysDashDot";
|
|
371
|
+
noFill?: boolean;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* An unresolved color reference. Stage 2 adds `resolveColor(ref, theme)`
|
|
376
|
+
* which turns this into a concrete sRGB string via the chart-color cascade
|
|
377
|
+
* (explicit sRGB → scheme color → theme color scheme).
|
|
378
|
+
*/
|
|
379
|
+
export type ColorRef =
|
|
380
|
+
| { kind: "srgb"; value: string } // "#RRGGBB"
|
|
381
|
+
| { kind: "scheme"; value: string; mods?: ColorMod[] }
|
|
382
|
+
| { kind: "themeLinked"; value: string; mods?: ColorMod[] };
|
|
383
|
+
|
|
384
|
+
export interface ColorMod {
|
|
385
|
+
kind: "lumMod" | "lumOff" | "shade" | "tint" | "satMod" | "hueMod" | "alpha";
|
|
386
|
+
/** Parts per 100,000 (OOXML convention). */
|
|
387
|
+
value: number;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface TextProperties {
|
|
391
|
+
fontFamily?: string;
|
|
392
|
+
fontSizePt?: number;
|
|
393
|
+
bold?: boolean;
|
|
394
|
+
italic?: boolean;
|
|
395
|
+
color?: ColorRef;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
// Exhaustiveness canary (compile-time only)
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Compile-time list of every ChartModel kind. If a new family is added to
|
|
404
|
+
* the union without a matching `kind` literal added here, tsgo fails.
|
|
405
|
+
*/
|
|
406
|
+
type _ChartModelKindExhaustive = ChartModel["kind"];
|
|
407
|
+
type _ExpectedKinds =
|
|
408
|
+
| "bar"
|
|
409
|
+
| "line"
|
|
410
|
+
| "pie"
|
|
411
|
+
| "area"
|
|
412
|
+
| "scatter"
|
|
413
|
+
| "bubble"
|
|
414
|
+
| "combo"
|
|
415
|
+
| "unsupported";
|
|
416
|
+
// Bi-directional assignability asserts union equality.
|
|
417
|
+
const _kindA: _ChartModelKindExhaustive = "bar" as _ExpectedKinds;
|
|
418
|
+
const _kindB: _ExpectedKinds = "bar" as _ChartModelKindExhaustive;
|
|
419
|
+
void _kindA;
|
|
420
|
+
void _kindB;
|