@silurus/ooxml 0.70.1 → 0.71.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.
@@ -64,6 +64,11 @@ export declare type BodyElement = {
64
64
  * (size + margins). Absent when the sectPr inherits both pgSz and pgMar
65
65
  * (the renderer then falls back to the body-level section geometry). */
66
66
  geom?: SectionGeom;
67
+ /** ECMA-376 §17.6.12 `<w:pgNumType>` — this ENDING section's page-numbering
68
+ * settings (start / fmt). Absent ⇒ numbering continues; decimal. Carried
69
+ * separately from `geom` because a section may inherit its geometry yet
70
+ * still restart / re-format its page numbers. */
71
+ pageNumType?: PageNumType | null;
67
72
  };
68
73
 
69
74
  export declare interface BorderSpec {
@@ -72,6 +77,21 @@ export declare interface BorderSpec {
72
77
  style: string;
73
78
  }
74
79
 
80
+ /**
81
+ * Populate a highlight overlay layer with one box per matched run-slice.
82
+ *
83
+ * @param layer the overlay div (cleared and re-sized to the canvas here).
84
+ * @param runs the page's runs (same array the page was rendered/text-layered from).
85
+ * @param matches the page's matches (run-slices + active flag).
86
+ * @param canvasCssWidth the rendered canvas's CSS width (e.g. `"700px"`).
87
+ * @param canvasCssHeight the rendered canvas's CSS height.
88
+ * @param measureForFont returns a width-measurer primed with a run's `font`
89
+ * (the viewer closes over a canvas 2d context). Kept as a
90
+ * factory so the font is set once per run, not per glyph.
91
+ * @param colors optional colour overrides.
92
+ */
93
+ export declare function buildDocxHighlightLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], matches: DocxHighlightMatch[], canvasCssWidth: string, canvasCssHeight: string, measureForFont: (font: string) => (s: string) => number, colors?: DocxHighlightColors): void;
94
+
75
95
  /**
76
96
  * Build the transparent text-selection overlay for a rendered docx page: one
77
97
  * absolutely-positioned, color-transparent `<span>` per {@link DocxTextRunInfo}
@@ -79,16 +99,35 @@ export declare interface BorderSpec {
79
99
  * lands on the drawn glyphs. Extracted verbatim from `DocxViewer._buildTextLayer`
80
100
  * so both the pager (DocxViewer) and the continuous-scroll viewer (DocxScrollViewer)
81
101
  * share one implementation; also public API for integrators building their own
82
- * overlay (design §10). MAIN render mode only `onTextRun` cannot cross the
83
- * worker boundary.
102
+ * overlay (design §10). IX6 usable in BOTH render modes: worker mode collects
103
+ * the same `DocxTextRunInfo[]` off-thread and ships it back beside the bitmap, so
104
+ * the overlay is built from identical geometry regardless of thread.
84
105
  *
85
- * @param layer the overlay div (position:relative parent expected).
86
- * @param runs per-run geometry from `renderPage({ onTextRun })`.
87
- * @param canvasCssWidth the rendered canvas's CSS width (e.g. `"700px"`), used
88
- * to size the overlay to match the canvas.
89
- * @param canvasCssHeight the rendered canvas's CSS height.
106
+ * @param layer the overlay div (position:relative parent expected).
107
+ * @param runs per-run geometry from `renderPage({ onTextRun })`.
108
+ * @param canvasCssWidth the rendered canvas's CSS width (e.g. `"700px"`), used
109
+ * to size the overlay to match the canvas.
110
+ * @param canvasCssHeight the rendered canvas's CSS height.
111
+ * @param onHyperlinkClick IX1 — invoked when a run carrying a resolved
112
+ * {@link HyperlinkTarget} is clicked. A hyperlink run's
113
+ * span keeps its transparent glyphs (the visible link
114
+ * colour/underline is already drawn on the canvas) but
115
+ * gains `cursor:pointer`, a `title` tooltip (the URL or
116
+ * bookmark ref) and this click handler. A plain
117
+ * `<span>` — not an `<a href>` — is used deliberately so
118
+ * the browser's own navigation can never bypass the
119
+ * caller's URL sanitisation. When omitted, link runs are
120
+ * rendered exactly like plain runs (no click affordance).
121
+ * @param measureForFont optional width-measurer factory (primed with a run's
122
+ * `font`), used ONLY to clamp a §17.3.2.10 縦中横
123
+ * (eastAsianVert) span to its drawn one-em cell (#836):
124
+ * the span composes a `scaleX(run.w / naturalWidth)` so
125
+ * its selection extent matches the compressed glyphs
126
+ * instead of the run's natural ~2× width. When omitted,
127
+ * a 縦中横 span keeps the bare rotate (no regression for
128
+ * callers that do not thread a measurer).
90
129
  */
91
- export declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], canvasCssWidth: string, canvasCssHeight: string): void;
130
+ export declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], canvasCssWidth: string, canvasCssHeight: string, onHyperlinkClick?: (target: HyperlinkTarget) => void, measureForFont?: (font: string) => (s: string) => number): void;
92
131
 
93
132
  export declare interface CellBorders {
94
133
  top: BorderSpec | null;
@@ -112,6 +151,760 @@ export declare type CellElement = {
112
151
  type: 'table';
113
152
  } & DocTable;
114
153
 
154
+ declare interface ChartDataLabelOverride {
155
+ idx: number;
156
+ /** Empty string = label deleted (skip drawing). */
157
+ text: string;
158
+ /** "l"|"r"|"t"|"b"|"ctr"|"outEnd"|"bestFit". undefined = inherit. */
159
+ position?: string;
160
+ fontColor?: string;
161
+ fontSizeHpt?: number;
162
+ /** `<a:defRPr b="1">` inside the per-idx rich text. */
163
+ fontBold?: boolean;
164
+ /** Per-point callout box (`<c:dLbl><c:spPr>`, ECMA-376 §21.2.2.47/§21.2.2.197):
165
+ * overrides the series-default box for this one slice. */
166
+ labelBox?: ChartLabelBox;
167
+ /**
168
+ * Per-point label-content flags (`<c:dLbl>` §21.2.2.47 carries the same
169
+ * show-flag group as the series `<c:dLbls>` §21.2.2.49: §21.2.2.189
170
+ * `<c:showVal>`, §21.2.2.177 `<c:showCatName>`, §21.2.2.180 `<c:showSerName>`,
171
+ * §21.2.2.187 `<c:showPercent>`). When present they OVERRIDE the series-level
172
+ * defaults for that one point (e.g. sample-14 slide-7's pie sets
173
+ * `showCatName=0 showPercent=1` per slice while the series default is
174
+ * `showCatName=1`, so each label is percent only). undefined = inherit the
175
+ * series default for that flag.
176
+ */
177
+ showVal?: boolean;
178
+ showCatName?: boolean;
179
+ showSerName?: boolean;
180
+ showPercent?: boolean;
181
+ /**
182
+ * `<c:dLbl><c:delete val="1"/>` (ECMA-376 §21.2.2.43) — the point's label is
183
+ * removed. Distinguishes a genuine delete from a `<c:dLbl>` that only carries
184
+ * style / flag overrides with no `<c:tx>` (both otherwise present as
185
+ * `text === ''`). true = skip the label; undefined/absent = not deleted.
186
+ */
187
+ deleted?: boolean;
188
+ }
189
+
190
+ declare interface ChartDataPointOverride {
191
+ idx: number;
192
+ /** Resolved fill hex (no `#`). */
193
+ color?: string;
194
+ markerSymbol?: string;
195
+ markerSize?: number;
196
+ markerFill?: string;
197
+ markerLine?: string;
198
+ /**
199
+ * `<c:dPt><c:explosion val>` (ECMA-376 §21.2.2.61) — the amount this
200
+ * pie/doughnut slice is moved out from the center. The schema type is
201
+ * `CT_UnsignedInt` (unbounded `xsd:unsignedInt`); the spec text only says
202
+ * "the amount the data point shall be moved from the center of the pie"
203
+ * and does not itself define units or a 0–100 range. We treat it as a
204
+ * de-facto percentage of the outer radius (0–100 typical), matching
205
+ * Office's UI (the Point Explosion slider caps at 100%) rather than a
206
+ * spec-mandated bound. undefined/absent = 0 (no explosion, flush with the
207
+ * ring). Only consulted by the pie/doughnut renderer.
208
+ */
209
+ explosion?: number;
210
+ }
211
+
212
+ declare interface ChartErrBars {
213
+ /** "x" | "y". */
214
+ dir: string;
215
+ /** "plus" | "minus" | "both". */
216
+ barType: string;
217
+ plus: (number | null)[];
218
+ minus: (number | null)[];
219
+ noEndCap: boolean;
220
+ /** Resolved hex (no `#`). */
221
+ color?: string;
222
+ lineWidthEmu?: number;
223
+ /** "solid"|"dash"|"dot"|"dashDot"|... */
224
+ dash?: string;
225
+ }
226
+
227
+ /**
228
+ * One box-and-whisker series (chartEx `boxWhisker`, MS 2014 chartex ext). Each
229
+ * `<cx:series>` references its own raw sample points via `<cx:dataId>`; the
230
+ * parser groups them by category and threads the `<cx:layoutPr>` flags. The
231
+ * renderer derives the statistics.
232
+ */
233
+ declare interface ChartexBoxSeries {
234
+ /** Series display name (`<cx:tx><cx:v>`). */
235
+ name: string;
236
+ /** Fill (hex, no '#') — theme accent cycled by series index. null = fall
237
+ * back to the renderer palette. */
238
+ color?: string | null;
239
+ /** Raw sample values grouped by category (outer = category index parallel to
240
+ * {@link ChartexBoxWhisker.categories}, inner = the points in that group). */
241
+ valuesByCategory: number[][];
242
+ /** `<cx:visibility meanMarker>` — draw the mean `×`. */
243
+ meanMarker: boolean;
244
+ /** `<cx:visibility meanLine>` — draw a mean connector line across categories. */
245
+ meanLine: boolean;
246
+ /** `<cx:visibility outliers>` — draw outlier points. */
247
+ showOutliers: boolean;
248
+ /** `<cx:visibility nonoutliers>` — draw the interior (non-outlier) sample
249
+ * points as jittered dots on top of the box. Flag parsed; interior-dot
250
+ * rendering is pending a fixture that enables it (every sample-24 series
251
+ * ships `nonoutliers="0"`, so there is nothing to verify against yet). */
252
+ showNonoutliers: boolean;
253
+ /** `<cx:statistics quartileMethod>` — "exclusive" (Excel default) | "inclusive". */
254
+ quartileMethod: string;
255
+ }
256
+
257
+ /** A chartEx box-and-whisker chart: unique categories + one series per column. */
258
+ declare interface ChartexBoxWhisker {
259
+ /** Unique category labels in first-seen order. */
260
+ categories: string[];
261
+ /** One entry per `<cx:series>`. */
262
+ series: ChartexBoxSeries[];
263
+ }
264
+
265
+ /** A chartEx sunburst: the flat rows the renderer folds into a ring tree. */
266
+ declare interface ChartexSunburst {
267
+ rows: ChartexSunburstRow[];
268
+ }
269
+
270
+ /**
271
+ * One row of a chartEx `sunburst`: the branch→…→leaf label chain (empty
272
+ * trailing segments trimmed) and its size value.
273
+ */
274
+ declare interface ChartexSunburstRow {
275
+ /** Label chain root→leaf. */
276
+ path: string[];
277
+ /** `<cx:numDim type="size">` value attaching to the deepest node in `path`. */
278
+ size: number;
279
+ }
280
+
281
+ /** Callout-box style for a pie/doughnut data label — the white (or themed)
282
+ * rounded rectangle with a thin border Word draws around a `bestFit` label
283
+ * placed outside its slice. From the label's `<c:spPr>` (§21.2.2.197). All
284
+ * fields optional: absent → transparent / unbordered. Mirror of Rust
285
+ * `ChartLabelBox`. */
286
+ declare interface ChartLabelBox {
287
+ /** `<a:solidFill>` resolved hex (no `#`). Box background. */
288
+ fill?: string;
289
+ /** `<a:ln><a:solidFill>` resolved hex (no `#`). Border stroke. */
290
+ borderColor?: string;
291
+ /** `<a:ln w>` border width in EMU (12700 EMU = 1 pt). */
292
+ borderWidthEmu?: number;
293
+ }
294
+
295
+ /**
296
+ * `<c:manualLayout>` block. Fractions are of the chart-space rect.
297
+ * `xMode`/`yMode`: "edge" = absolute fraction from top-left, "factor" =
298
+ * fraction offset from default position.
299
+ */
300
+ declare interface ChartManualLayout {
301
+ xMode: string;
302
+ yMode: string;
303
+ layoutTarget?: string;
304
+ x: number;
305
+ y: number;
306
+ w?: number;
307
+ h?: number;
308
+ }
309
+
310
+ declare interface ChartModel {
311
+ chartType: ChartType;
312
+ title: string | null;
313
+ categories: string[];
314
+ series: ChartSeries[];
315
+ /** Show data labels on bars / points / slices. */
316
+ showDataLabels: boolean;
317
+ /** Explicit Y-axis minimum (OOXML `<c:valAx><c:min>`). */
318
+ valMin: number | null;
319
+ /** Explicit Y-axis maximum (OOXML `<c:valAx><c:max>`). */
320
+ valMax: number | null;
321
+ catAxisTitle: string | null;
322
+ valAxisTitle: string | null;
323
+ /** `<c:catAx><c:delete val="1"/>`. */
324
+ catAxisHidden: boolean;
325
+ /** `<c:valAx><c:delete val="1"/>`. */
326
+ valAxisHidden: boolean;
327
+ /** `<c:catAx><c:spPr><a:ln><a:noFill>` — hide just the axis LINE; labels
328
+ * and tick marks still render. Distinct from `catAxisHidden` (which
329
+ * removes everything via `<c:delete val="1"/>`). */
330
+ catAxisLineHidden: boolean;
331
+ /** `<c:valAx><c:spPr><a:ln><a:noFill>` — hide just the axis LINE; labels
332
+ * and tick marks still render. */
333
+ valAxisLineHidden: boolean;
334
+ /** Hex without '#'. From `<c:plotArea><c:spPr><a:solidFill>`. */
335
+ plotAreaBg: string | null;
336
+ /** Outer chartSpace background (hex without '#'). null when noFill/absent. */
337
+ chartBg: string | null;
338
+ /** True when `<c:legend>` is declared in the chart XML. False = no legend. */
339
+ showLegend: boolean;
340
+ /** `<c:legend><c:legendPos val>` — "r"|"l"|"t"|"b"|"tr". null = default (r). */
341
+ legendPos: 'r' | 'l' | 't' | 'b' | 'tr' | null;
342
+ /** `<c:catAx><c:crossBetween val="..."/>`. "between" inserts 0.5-step padding
343
+ * on each end of the category axis; "midCat" anchors endpoints to the axes. */
344
+ catAxisCrossBetween: 'between' | 'midCat' | string;
345
+ /** `<c:valAx><c:majorTickMark>`. ECMA-376 default is "cross". */
346
+ valAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
347
+ /** `<c:catAx><c:majorTickMark>`. */
348
+ catAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
349
+ /** `<c:valAx | catAx><c:minorTickMark>`. ECMA-376 default is "none". */
350
+ valAxisMinorTickMark?: 'cross' | 'out' | 'in' | 'none' | string | null;
351
+ catAxisMinorTickMark?: 'cross' | 'out' | 'in' | 'none' | string | null;
352
+ /** Title font size in OOXML hundredths of a point (1600 = 16pt). null = default. */
353
+ titleFontSizeHpt: number | null;
354
+ /** Title font color as a hex string without '#' (e.g. "1B4332"). null = default. */
355
+ titleFontColor: string | null;
356
+ /** Title font family from `<a:latin typeface>` (ECMA-376 §20.1.4.2.24). null = default. */
357
+ titleFontFace: string | null;
358
+ /** `<c:catAx><c:txPr>` font size (hpt). null = fall back to proportional default. */
359
+ catAxisFontSizeHpt: number | null;
360
+ /** `<c:valAx><c:txPr>` font size (hpt). null = fall back to proportional default. */
361
+ valAxisFontSizeHpt: number | null;
362
+ /** `<c:catAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#').
363
+ * null = renderer default. Lets templates color category labels gray. */
364
+ catAxisFontColor?: string | null;
365
+ /** `<c:valAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
366
+ valAxisFontColor?: string | null;
367
+ /** `<c:dLbls><c:txPr>` font size (hpt) for data-point value labels. */
368
+ dataLabelFontSizeHpt: number | null;
369
+ /** Waterfall subtotal category indices. */
370
+ subtotalIndices: number[];
371
+ /** `<c:legend><c:manualLayout>` absolute placement fractions of the chart
372
+ * space (ECMA-376 §21.2.2.31). Overrides the default side-based legend
373
+ * rectangle while still letting `legendPos` decide which side of the plot
374
+ * gets the reserved band. null = use default layout. */
375
+ legendManualLayout?: LegendManualLayout | null;
376
+ /**
377
+ * `<c:valAx><c:numFmt@formatCode>` — format code applied to value-axis tick
378
+ * labels (ECMA-376 §21.2.2.21). null = plain numeric formatting.
379
+ */
380
+ valAxisFormatCode?: string | null;
381
+ /**
382
+ * `<c:barChart><c:gapWidth>` — space between category groups as a
383
+ * percentage of bar width (ECMA-376 §21.2.2.13). Default per spec is 150.
384
+ * null = renderer default.
385
+ */
386
+ barGapWidth?: number | null;
387
+ /**
388
+ * `<c:barChart><c:overlap>` — signed percentage overlap between bars in the
389
+ * same category cluster (ECMA-376 §21.2.2.25). Negative = gap, positive =
390
+ * overlap, 0 = flush. Range [-100, 100]. null = renderer default (0).
391
+ */
392
+ barOverlap?: number | null;
393
+ /**
394
+ * `<c:dLbls><c:dLblPos>` — data label position (ECMA-376 §21.2.2.16).
395
+ * "ctr"|"inBase"|"inEnd"|"outEnd"|"l"|"r"|"t"|"b"|"bestFit" etc.
396
+ */
397
+ dataLabelPosition?: string | null;
398
+ /** Hex (no `#`) for data label text, resolved from `<c:dLbls><c:txPr>`. */
399
+ dataLabelFontColor?: string | null;
400
+ /**
401
+ * `<c:dLbls><c:numFmt@formatCode>` — chart-level override for data label
402
+ * number format (ECMA-376 §21.2.2.35). When absent, `valFormatCode` on each
403
+ * series is used.
404
+ */
405
+ dataLabelFormatCode?: string | null;
406
+ /** `<c:title>...defRPr@b>` chart title bold flag. */
407
+ titleFontBold?: boolean | null;
408
+ /** `<c:catAx><c:txPr>...defRPr@b>` X-axis tick label bold flag. */
409
+ catAxisFontBold?: boolean | null;
410
+ /** `<c:valAx><c:txPr>...defRPr@b>` Y-axis tick label bold flag. */
411
+ valAxisFontBold?: boolean | null;
412
+ /** `<c:catAx><c:title>` run-prop font size (hpt). Distinct from
413
+ * `catAxisFontSizeHpt` (tick labels). null = renderer default. */
414
+ catAxisTitleFontSizeHpt?: number | null;
415
+ /** `<c:catAx><c:title>` run-prop bold flag. null = not bold. */
416
+ catAxisTitleFontBold?: boolean | null;
417
+ /** `<c:catAx><c:title>` run-prop color (hex without '#'). null = default. */
418
+ catAxisTitleFontColor?: string | null;
419
+ /** `<c:valAx><c:title>` run-prop font size (hpt). null = renderer default. */
420
+ valAxisTitleFontSizeHpt?: number | null;
421
+ /** `<c:valAx><c:title>` run-prop bold flag. null = not bold. */
422
+ valAxisTitleFontBold?: boolean | null;
423
+ /** `<c:valAx><c:title>` run-prop color (hex without '#'). null = default. */
424
+ valAxisTitleFontColor?: string | null;
425
+ /** `<c:catAx><c:txPr>…<a:latin typeface>` tick-label font. */
426
+ catAxisFontFace?: string | null;
427
+ /** `<c:valAx><c:txPr>…<a:latin typeface>` tick-label font. */
428
+ valAxisFontFace?: string | null;
429
+ /** `<c:catAx><c:title>…<a:latin typeface>` axis-title font. */
430
+ catAxisTitleFontFace?: string | null;
431
+ /** `<c:valAx><c:title>…<a:latin typeface>` axis-title font. */
432
+ valAxisTitleFontFace?: string | null;
433
+ /** `<c:dLbls><c:txPr>…<a:latin typeface>` data-label font. */
434
+ dataLabelFontFace?: string | null;
435
+ /** `<c:legend><c:txPr>…<a:latin typeface>` legend font. */
436
+ legendFontFace?: string | null;
437
+ /** `<c:legend><c:txPr>…<a:solidFill>` legend text color (hex without '#'). */
438
+ legendFontColor?: string | null;
439
+ /** `<c:legend><c:txPr>` legend font size (OOXML hundredths of a point). */
440
+ legendFontSizeHpt?: number | null;
441
+ /** `<c:legend><c:txPr>…defRPr@b` legend bold flag. */
442
+ legendFontBold?: boolean | null;
443
+ /**
444
+ * Theme font-scheme faces (`<a:fontScheme>`, ECMA-376 §20.1.4.2). Latin
445
+ * heading (majorFont) and body (minorFont) typefaces, used as the fallback
446
+ * for any chart text element whose own `<c:txPr>` supplies no `<a:latin>`.
447
+ * null when the theme is not threaded to the chart (then the renderer's
448
+ * built-in sans-serif remains, byte-stable). Axis titles / chart title use
449
+ * the major (heading) face; tick labels / data labels / legend use the
450
+ * minor (body) face — matching Office's default chart text styling.
451
+ */
452
+ themeMajorFontLatin?: string | null;
453
+ themeMinorFontLatin?: string | null;
454
+ /** Explicit chart border color (hex without '#') from
455
+ * `<c:chartSpace><c:spPr><a:ln><a:solidFill><a:srgbClr>`. Only set when the
456
+ * XML explicitly declares a paintable line; null otherwise (no default
457
+ * border is drawn). */
458
+ chartBorderColor?: string | null;
459
+ /** `<c:chartSpace><c:spPr><a:ln@w>` border width in EMU. null = 1px hairline
460
+ * when a color is present. */
461
+ chartBorderWidthEmu?: number | null;
462
+ /**
463
+ * `<c:catAx><c:crosses val>` (`autoZero` | `min` | `max`). Drives the Y
464
+ * coordinate where the X axis is drawn. Default `autoZero` puts the X
465
+ * axis at y=0 — that's how Excel "Project Timeline" templates split
466
+ * milestones (positive Y) above and tasks (negative Y) below the axis.
467
+ */
468
+ catAxisCrosses?: string | null;
469
+ /** `<c:catAx><c:crossesAt val>` — explicit numeric override for the
470
+ * crossing point. Takes precedence over `catAxisCrosses`. */
471
+ catAxisCrossesAt?: number | null;
472
+ valAxisCrosses?: string | null;
473
+ valAxisCrossesAt?: number | null;
474
+ /** Axis line color (hex without `#`) and width in EMU from
475
+ * `<c:catAx|valAx><c:spPr><a:ln>`. */
476
+ catAxisLineColor?: string | null;
477
+ catAxisLineWidthEmu?: number | null;
478
+ valAxisLineColor?: string | null;
479
+ valAxisLineWidthEmu?: number | null;
480
+ /**
481
+ * `<c:catAx><c:numFmt@formatCode>` (or scatter X-axis valAx). When set,
482
+ * the renderer formats X-axis tick labels with this code (e.g. dates).
483
+ */
484
+ catAxisFormatCode?: string | null;
485
+ /**
486
+ * `<c:catAx><c:scaling><c:min/max>` — explicit X-axis range. Used by
487
+ * scatter / bubble charts whose X axis is numeric. null = derive from
488
+ * data extents.
489
+ */
490
+ catAxisMin?: number | null;
491
+ catAxisMax?: number | null;
492
+ /**
493
+ * `<c:title><c:layout><c:manualLayout>` (ECMA-376 §21.2.2.27) absolute
494
+ * placement for the chart title.
495
+ */
496
+ titleManualLayout?: ChartManualLayout | null;
497
+ /**
498
+ * `<c:plotArea><c:layout><c:manualLayout>` absolute placement for the
499
+ * plot area. `layoutTarget="inner"` (default) describes the inner plot
500
+ * rect (no axes / labels); `outer` describes the outer rect (axes
501
+ * included).
502
+ */
503
+ plotAreaManualLayout?: ChartManualLayout | null;
504
+ /**
505
+ * `<c:scatterChart><c:scatterStyle val>` (ECMA-376 §21.2.2.42). Drives
506
+ * whether scatter charts connect points with lines and whether those
507
+ * lines are smoothed. Values: "marker" (markers only — Excel default
508
+ * "Scatter"), "line" / "lineMarker" (straight segments), "smooth" /
509
+ * "smoothMarker" (cubic Bézier through points), "lineNoMarker",
510
+ * "smoothNoMarker". null = renderer default ("marker"). Only consulted
511
+ * for `chartType === "scatter"`; bubble ignores it.
512
+ */
513
+ scatterStyle?: string | null;
514
+ /**
515
+ * `<c:radarChart><c:radarStyle val>` (ECMA-376 §21.2.3.10). Controls
516
+ * whether radar series render as line + markers ("standard" / "marker")
517
+ * or as a closed polygon with area fill ("filled"). null = default
518
+ * ("standard" — line, no fill). Only consulted for `chartType === "radar"`.
519
+ */
520
+ radarStyle?: string | null;
521
+ /**
522
+ * Secondary value axis for combo charts (bar + line). When present, series
523
+ * with `useSecondaryAxis` are plotted against this axis's independent scale
524
+ * and the axis is drawn on the right edge of the plot. null/absent = single
525
+ * value axis (the common case). See {@link SecondaryValueAxis}.
526
+ */
527
+ secondaryValAxis?: SecondaryValueAxis | null;
528
+ /**
529
+ * `<c:date1904>` (ECMA-376 §21.2.2.38). When true the chart's serial
530
+ * date-times resolve against the 1904 date system (base 1904-01-01) instead
531
+ * of the default 1900 system. Threaded to the date formatters for date-axis
532
+ * category labels and value-axis tick labels. Omitted/false ⇒ 1900 system.
533
+ * Note: per §21.2.2.38 the element's `val` defaults to true when present but
534
+ * the attribute is omitted, so `<c:date1904/>` alone means date1904=true.
535
+ */
536
+ date1904?: boolean;
537
+ /**
538
+ * `<c:doughnutChart><c:holeSize val>` (ECMA-376 §21.2.2.60,
539
+ * `ST_HoleSizePercent` §21.2.3.55) — the doughnut hole diameter as a
540
+ * percentage 1–90 of the outer diameter. Ignored for pie (which has no
541
+ * hole). null/undefined = use the renderer's doughnut default when the
542
+ * element is absent. Note the ECMA `CT_HoleSize` schema default is 10%, but
543
+ * a real doughnut file always writes an explicit `<c:holeSize>` (Excel /
544
+ * PowerPoint emit 50–75%); the renderer falls back to 50% only for the
545
+ * pathological absent case.
546
+ */
547
+ holeSize?: number | null;
548
+ /**
549
+ * `<c:pieChart | doughnutChart><c:firstSliceAng val>` (ECMA-376 §21.2.2.52,
550
+ * `ST_FirstSliceAng` §21.2.3.15) — the angle in degrees (0–360, clockwise
551
+ * from the 12 o'clock position) at which the first slice begins.
552
+ * null/undefined = 0 (start at 12 o'clock), which matches the renderer's
553
+ * historical fixed −90° (canvas up) start.
554
+ */
555
+ firstSliceAngle?: number | null;
556
+ /**
557
+ * `<c:chartSpace><c:chart><c:dispBlanksAs val>` (ECMA-376 §21.2.2.42,
558
+ * `ST_DispBlanksAs` §21.2.3.10) — how blank (null) cells are plotted on
559
+ * line/area charts:
560
+ * - "gap" → leave a gap (break the line). The renderer's historical
561
+ * behavior and the model default when the element is absent.
562
+ * - "zero" → plot the blank as the value 0 (the point drops to the axis).
563
+ * - "span" → skip the blank but connect its neighbours with a straight
564
+ * line (bridge the gap).
565
+ * Note the XSD `@val` default is "zero" (applies when `<c:dispBlanksAs/>` is
566
+ * present but the attribute is omitted); when the ELEMENT is absent entirely
567
+ * Office falls back to "gap", which is what we model as the default. Only
568
+ * consulted for the line and area families. null/undefined = "gap".
569
+ */
570
+ dispBlanksAs?: string | null;
571
+ /**
572
+ * `<c:valAx><c:majorGridlines>` presence (ECMA-376 §21.2.2.100). `false` when
573
+ * the value axis exists but omits the element (Office suppresses value
574
+ * gridlines). null/undefined ⇒ the renderer's historical always-on value
575
+ * gridlines (byte-stable). `true` is redundant with the default but honored.
576
+ */
577
+ valAxisMajorGridlines?: boolean | null;
578
+ /**
579
+ * `<c:catAx><c:majorGridlines>` presence (§21.2.2.100). `true` turns on
580
+ * category-axis gridlines (Office omits them by default). null/undefined/false
581
+ * ⇒ no category gridlines (the historical default, byte-stable).
582
+ */
583
+ catAxisMajorGridlines?: boolean | null;
584
+ /**
585
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
586
+ * color (hex without `#`) — ECMA-376 §21.2.2.100. When set, the value-axis
587
+ * major gridlines are stroked in this color instead of the renderer's faint
588
+ * `#e0e0e0` default (e.g. sample-1 slide 5's `accent3` gridlines). null/absent
589
+ * ⇒ the historical default (byte-stable).
590
+ */
591
+ valAxisGridlineColor?: string | null;
592
+ /**
593
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. When
594
+ * set, the value-axis gridline stroke width is derived from this (floored so a
595
+ * hairline stays visible). null/absent ⇒ the renderer's 0.5 px default.
596
+ */
597
+ valAxisGridlineWidthEmu?: number | null;
598
+ /**
599
+ * `<c:catAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
600
+ * color (hex without `#`). Only meaningful when {@link catAxisMajorGridlines}
601
+ * is on. null/absent ⇒ the faint default.
602
+ */
603
+ catAxisGridlineColor?: string | null;
604
+ /** `<c:catAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. */
605
+ catAxisGridlineWidthEmu?: number | null;
606
+ /** `<c:valAx><c:minorGridlines>` presence (§21.2.2.109). Only drawn when a
607
+ * minor step is resolvable (see {@link valAxisMinorUnit}). */
608
+ valAxisMinorGridlines?: boolean | null;
609
+ /**
610
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between major
611
+ * gridlines/ticks, overriding the Excel-style auto "nice" step. null/undefined
612
+ * ⇒ auto step (byte-stable).
613
+ */
614
+ valAxisMajorUnit?: number | null;
615
+ /** `<c:valAx><c:minorUnit val>` (§21.2.2.112) — explicit minor step. Drives
616
+ * minor gridlines/ticks when present. null ⇒ no minor divisions. */
617
+ valAxisMinorUnit?: number | null;
618
+ /**
619
+ * `<c:valAx><c:scaling><c:logBase val>` (§21.2.2.98, `ST_LogBase` §21.2.3.25)
620
+ * — logarithmic value-axis base (>= 2). When set, values map to pixels in log
621
+ * space and gridlines fall on powers of the base. null/undefined ⇒ linear
622
+ * (byte-stable).
623
+ */
624
+ valAxisLogBase?: number | null;
625
+ /**
626
+ * `<c:valAx><c:scaling><c:orientation val>` (§21.2.2.130, `ST_Orientation`
627
+ * §21.2.3.30) — "minMax" (normal) | "maxMin" (reversed, so the value axis runs
628
+ * top→bottom max→min). null/undefined/"minMax" ⇒ normal (byte-stable).
629
+ */
630
+ valAxisOrientation?: 'minMax' | 'maxMin' | string | null;
631
+ /** `<c:catAx><c:scaling><c:orientation val>` — "maxMin" reverses the category
632
+ * axis left↔right. null/"minMax" ⇒ normal. */
633
+ catAxisOrientation?: 'minMax' | 'maxMin' | string | null;
634
+ /**
635
+ * `<c:catAx><c:tickLblPos val>` (§21.2.2.207, `ST_TickLblPos` §21.2.3.47) —
636
+ * "nextTo" (default) | "low" | "high" | "none". "none" hides the category tick
637
+ * labels. null/undefined ⇒ nextTo (byte-stable).
638
+ */
639
+ catAxisTickLabelPos?: string | null;
640
+ /** `<c:valAx><c:tickLblPos val>` (§21.2.2.207). "none" hides value tick labels. */
641
+ valAxisTickLabelPos?: string | null;
642
+ /**
643
+ * `<c:catAx><c:txPr><a:bodyPr rot>` (DrawingML `ST_Angle`, 60000ths of a
644
+ * degree) — category tick-label rotation. e.g. -2700000 = -45°. null/undefined
645
+ * /0 ⇒ horizontal labels (byte-stable).
646
+ */
647
+ catAxisLabelRotation?: number | null;
648
+ /**
649
+ * `<c:stockChart><c:hiLowLines>` presence (ECMA-376 §21.2.2.60). When true
650
+ * the stock renderer draws a vertical line spanning each category's low↔high
651
+ * value. Only set for `chartType === "stock"`; null/undefined on every other
652
+ * chart type (byte-stable).
653
+ */
654
+ stockHiLowLines?: boolean | null;
655
+ /**
656
+ * `<c:hiLowLines><c:spPr><a:ln><a:solidFill>` resolved color (hex, no `#`).
657
+ * null = the renderer's default gray hi-lo line.
658
+ */
659
+ stockHiLowLineColor?: string | null;
660
+ /**
661
+ * `<c:stockChart><c:upDownBars>` presence (ECMA-376 §21.2.2.227). Parsed so a
662
+ * stock file carrying open-close up/down bars is recognized; the renderer does
663
+ * NOT yet draw them (tracked follow-up). null/undefined when absent.
664
+ */
665
+ stockUpDownBars?: boolean | null;
666
+ /**
667
+ * Structured box-and-whisker data (`chartType === 'boxWhisker'`). Present
668
+ * ONLY for boxWhisker charts; null/absent otherwise so the flat
669
+ * `categories`/`series` model the other chartEx renderers consume is
670
+ * untouched. The renderer computes quartiles / mean / whiskers / outliers.
671
+ */
672
+ chartexBox?: ChartexBoxWhisker | null;
673
+ /**
674
+ * Structured sunburst hierarchy (`chartType === 'sunburst'`). Present ONLY
675
+ * for sunburst charts; null/absent otherwise.
676
+ */
677
+ chartexSunburst?: ChartexSunburst | null;
678
+ /**
679
+ * Theme accent palette (`accent1..6`, hex without '#') for chartEx charts
680
+ * that color by branch/series index (boxWhisker series, sunburst branches).
681
+ * null/absent when the resolver supplies no default palette (pptx); the
682
+ * renderer then falls back to its own `CHART_PALETTE`.
683
+ */
684
+ chartexAccents?: string[] | null;
685
+ }
686
+
687
+ /** ECMA-376 §21.2 — a DrawingML chart embedded in the run flow via
688
+ * `<w:drawing><wp:inline|wp:anchor>…<a:graphicData uri=".../chart"><c:chart r:id>`.
689
+ * Mirrors the Rust `ChartRun`. `chart` is the shared {@link ChartModel} the
690
+ * core `renderChart` consumes (identical to what pptx/xlsx pass), so a docx
691
+ * chart draws at the same quality through the same code path. `widthPt`/
692
+ * `heightPt` are the `<wp:extent>` natural size. An inline chart flows as an
693
+ * inline box of that size; an anchored chart (§20.4.2.3) is painted at its
694
+ * absolute page box by `renderAnchorImages` — both via `renderChart`. */
695
+ export declare interface ChartRun {
696
+ chart: ChartModel;
697
+ widthPt: number;
698
+ heightPt: number;
699
+ /** true = `<wp:anchor>` (absolute page position, drawn by the anchor path);
700
+ * false = `<wp:inline>` (flows with text). */
701
+ anchor: boolean;
702
+ anchorXPt?: number;
703
+ anchorYPt?: number;
704
+ anchorXFromMargin?: boolean;
705
+ anchorYFromPara?: boolean;
706
+ }
707
+
708
+ declare interface ChartSeries {
709
+ name: string;
710
+ /** Hex without '#'. null = fall back to palette. */
711
+ color: string | null;
712
+ /** Numeric values; null = missing data point. */
713
+ values: (number | null)[];
714
+ /**
715
+ * Per-data-point colors (pie / doughnut). Hex without '#'. null inside the
716
+ * array = use palette for that slice. Omit entirely for non-pie series.
717
+ */
718
+ dataPointColors?: (string | null)[] | null;
719
+ /**
720
+ * Per-data-point data-label text colors. Used by chartEx (`<cx:dataLabel idx>`)
721
+ * to override label colour per bar — sample-2's waterfall paints negative
722
+ * △ values in red while positive values stay black. Null inside the array =
723
+ * fall back to the chart-level `dataLabelFontColor`.
724
+ */
725
+ dataLabelColors?: (string | null)[] | null;
726
+ /**
727
+ * Series-level data-label text colour (`<c:ser><c:dLbls><c:txPr>…solidFill`,
728
+ * ECMA-376 §21.2.2.216). Hex without '#'. Stacked-bar charts colour each
729
+ * segment's label independently (e.g. white on the dark segment, black on
730
+ * the light one), which a single chart-level `dataLabelFontColor` can't
731
+ * express. Takes precedence over `dataLabelFontColor`; null = no override.
732
+ */
733
+ labelColor?: string | null;
734
+ /**
735
+ * Mixed chart: per-series chart type override. Currently only "line" (XLSX
736
+ * and PPTX combo charts) is honoured; other values are treated as the
737
+ * chart's primary type.
738
+ */
739
+ seriesType?: string | null;
740
+ /**
741
+ * Combo chart: this series is plotted against the SECONDARY value axis
742
+ * (`ChartModel.secondaryValAxis`) — the `<c:valAx>` with `axPos="r"` /
743
+ * `<c:crosses val="max">`. When false/absent the series uses the primary
744
+ * (left) value-axis scale. PowerPoint's "Revenue vs. gross margin" combo
745
+ * (sample-14 slide-8) puts the margin line on a 0–100% secondary axis.
746
+ */
747
+ useSecondaryAxis?: boolean | null;
748
+ /**
749
+ * Scatter-only X values (as strings). When null the series uses
750
+ * `ChartModel.categories` as X.
751
+ */
752
+ categories?: string[] | null;
753
+ /**
754
+ * Resolved marker visibility for line/scatter series. ECMA-376 §21.2.2.32
755
+ * `<c:marker><c:symbol>` defaults to "none" for line charts unless the
756
+ * chart-level `<c:marker val="1"/>` or a per-series symbol opts in. When
757
+ * undefined/null the renderer uses its own default (visible) so callers
758
+ * that don't parse markers (e.g. pptx today) keep their existing behavior.
759
+ */
760
+ showMarker?: boolean | null;
761
+ /**
762
+ * Excel number-format code for this series' values (ECMA-376 §21.2.2.37,
763
+ * `<c:val>/<c:numRef>/<c:formatCode>`). Used to format data labels when the
764
+ * chart-level `<c:dLbls><c:numFmt>` is not set. null = no series-level code.
765
+ */
766
+ valFormatCode?: string | null;
767
+ /**
768
+ * `<c:marker><c:symbol val>` (ECMA-376 §21.2.2.32) — point marker shape.
769
+ * One of "circle"|"square"|"diamond"|"triangle"|"x"|"plus"|"star"|
770
+ * "dot"|"dash"|"picture"|"none". null = renderer default (circle when
771
+ * showMarker is true).
772
+ */
773
+ markerSymbol?: string | null;
774
+ /**
775
+ * `<c:marker><c:size val>` (ECMA-376 §21.2.2.34) — marker side length in
776
+ * points. null = renderer default (~5 pt).
777
+ */
778
+ markerSize?: number | null;
779
+ /** `<c:marker><c:spPr><a:solidFill>` resolved hex (no `#`). */
780
+ markerFill?: string | null;
781
+ /** `<c:marker><c:spPr><a:ln><a:solidFill>` resolved hex (no `#`). */
782
+ markerLine?: string | null;
783
+ /**
784
+ * Per-data-point overrides (ECMA-376 §21.2.2.39 `<c:dPt>`). Keyed by point
785
+ * index. Any unset field falls back to the series-level value.
786
+ */
787
+ dataPointOverrides?: ChartDataPointOverride[] | null;
788
+ /**
789
+ * Per-data-point custom labels (ECMA-376 §21.2.2.45 `<c:dLbl idx>`).
790
+ * `text` is the resolved plain string — `<a:fld type="CELLRANGE">`
791
+ * placeholders are already substituted at parse time. An empty string
792
+ * means the point's label was deleted with `<c:delete val="1"/>` and
793
+ * the renderer should skip it.
794
+ */
795
+ dataLabelOverrides?: ChartDataLabelOverride[] | null;
796
+ /**
797
+ * Series-level `<c:dLbls>` block (showVal / showSerName / position).
798
+ * Applied to every point lacking its own `<c:dLbl>` override.
799
+ */
800
+ seriesDataLabels?: ChartSeriesDataLabels | null;
801
+ /**
802
+ * `<c:errBars>` per-series error bars (ECMA-376 §21.2.2.20). Up to two
803
+ * (one per direction). Plus / minus deltas are absolute per-point values
804
+ * regardless of `errValType`.
805
+ */
806
+ errBars?: ChartErrBars[] | null;
807
+ /**
808
+ * `<c:bubbleSize>` per-point sizes for bubble charts (ECMA-376 §21.2.2.4).
809
+ * Drives marker radius — renderer treats the values as areas (radius
810
+ * scales by sqrt) so visual area is proportional to value, matching
811
+ * Excel. null / empty array = uniform marker size. Ignored for non-bubble
812
+ * series.
813
+ */
814
+ bubbleSizes?: (number | null)[] | null;
815
+ /**
816
+ * `<c:ser><c:smooth val>` (ECMA-376 §21.2.2.194) — line/area series flag
817
+ * requesting a smoothed (spline) curve through the points instead of straight
818
+ * segments. Only consulted for the line and area families (scatter carries its
819
+ * smoothing in `ChartModel.scatterStyle`). null/undefined/false = straight
820
+ * polyline (the default; byte-stable for series that never set it).
821
+ */
822
+ smooth?: boolean | null;
823
+ /**
824
+ * `<c:ser><c:trendline>` per-series trendlines (ECMA-376 §21.2.2.211,
825
+ * `CT_Trendline`). A series can carry several (e.g. a linear fit + a moving
826
+ * average). null/undefined/empty = no trendline (the default; byte-stable for
827
+ * series that never declare one).
828
+ */
829
+ trendLines?: ChartTrendline[] | null;
830
+ /**
831
+ * `<c:ser><c:spPr><a:ln><a:noFill/>` (ECMA-376 §21.2.2.198 CT_ShapeProperties
832
+ * → DrawingML §20.1.2.2.24 CT_LineProperties). true when the series connecting
833
+ * line is explicitly turned OFF. For a scatter/line series this OVERRIDES the
834
+ * chart-group `<c:scatterStyle>` (§21.2.2.42) / line default — Excel and
835
+ * PowerPoint draw markers only (no connecting line) even when the group style
836
+ * is `lineMarker`. null/undefined = no explicit line-off, so the group default
837
+ * governs (byte-stable for series that carry a paintable line).
838
+ */
839
+ lineHidden?: boolean | null;
840
+ }
841
+
842
+ declare interface ChartSeriesDataLabels {
843
+ showVal: boolean;
844
+ showCatName: boolean;
845
+ showSerName: boolean;
846
+ showPercent: boolean;
847
+ position?: string;
848
+ fontColor?: string;
849
+ formatCode?: string;
850
+ /** Series-level bold default for data labels. */
851
+ fontBold?: boolean;
852
+ /** Series-level font size for data labels (OOXML hundredths of a point). */
853
+ fontSizeHpt?: number;
854
+ /** Series-default callout box (`<c:dLbls><c:spPr>`, ECMA-376 §21.2.2.49/
855
+ * §21.2.2.197). When present the pie/doughnut renderer draws Word's boxed
856
+ * callout layout (box + optional leader line) instead of plain text. */
857
+ labelBox?: ChartLabelBox;
858
+ /** `<c:dLbls><c:showLeaderLines val>` (§21.2.2.183) — draw leader lines from
859
+ * a pulled-away label back to its slice. Default false. */
860
+ showLeaderLines?: boolean;
861
+ /** `<c:leaderLines><c:spPr><a:ln><a:solidFill>` (§21.2.2.92) resolved hex
862
+ * (no `#`). undefined → renderer uses a neutral grey. */
863
+ leaderLineColor?: string;
864
+ /** `<c:leaderLines><c:spPr><a:ln w>` leader-line width in EMU. */
865
+ leaderLineWidthEmu?: number;
866
+ }
867
+
868
+ /**
869
+ * `<c:ser><c:trendline>` (ECMA-376 §21.2.2.211). A regression/smoothing curve
870
+ * fitted to the series' data points.
871
+ */
872
+ declare interface ChartTrendline {
873
+ /**
874
+ * `<c:trendlineType val>` (§21.2.2.213, `ST_TrendlineType` §21.2.3.50):
875
+ * "linear" | "exp" | "log" | "power" | "poly" | "movingAvg". The renderer
876
+ * currently draws "linear" (least squares) and "movingAvg"; other types parse
877
+ * but are not yet plotted (tracked as a follow-up).
878
+ */
879
+ trendlineType: string;
880
+ /** `<c:order val>` — polynomial order (`poly`, default 2). */
881
+ order?: number | null;
882
+ /** `<c:period val>` — moving-average window (`movingAvg`, default 2). */
883
+ period?: number | null;
884
+ /** `<c:forward val>` — units to extend the line past the last point. */
885
+ forward?: number | null;
886
+ /** `<c:backward val>` — units to extend the line before the first point. */
887
+ backward?: number | null;
888
+ /** `<c:intercept val>` — forced y-intercept (linear/exp). null = free fit. */
889
+ intercept?: number | null;
890
+ /** `<c:dispRSqr val="1">` — show the R² value (label; not yet rendered). */
891
+ dispRSqr?: boolean | null;
892
+ /** `<c:dispEq val="1">` — show the fit equation (label; not yet rendered). */
893
+ dispEq?: boolean | null;
894
+ /** `<c:spPr><a:ln><a:solidFill>` trendline color (hex without '#'). null =
895
+ * inherit the series color. */
896
+ lineColor?: string | null;
897
+ /** `<c:spPr><a:ln w>` trendline width in EMU. */
898
+ lineWidthEmu?: number | null;
899
+ }
900
+
901
+ /**
902
+ * Canonical chart type vocabulary. Embeds direction (`H` = horizontal) and
903
+ * grouping (`Pct` = percent-stacked) so renderers do not need to inspect
904
+ * separate `barDir`/`grouping` fields.
905
+ */
906
+ declare type ChartType = 'line' | 'stackedLine' | 'stackedLinePct' | 'clusteredBar' | 'clusteredBarH' | 'stackedBar' | 'stackedBarH' | 'stackedBarPct' | 'stackedBarHPct' | 'area' | 'stackedArea' | 'stackedAreaPct' | 'pie' | 'doughnut' | 'scatter' | 'bubble' | 'radar' | 'waterfall' | 'stock' | 'boxWhisker' | 'sunburst' | string;
907
+
115
908
  /** ECMA-376 §17.6.3 `<w:col>` — one column's width and trailing space (pt). */
116
909
  export declare interface ColSpec {
117
910
  widthPt: number;
@@ -159,7 +952,7 @@ export declare interface DocParagraph {
159
952
  * both, distribute. Other values (kashida variants, numTab, thaiDistribute)
160
953
  * are treated as start-aligned.
161
954
  */
162
- alignment: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | 'both' | 'distribute' | string;
955
+ alignment: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | 'both' | 'distribute' | 'lowKashida' | 'mediumKashida' | 'highKashida' | 'thaiDistribute' | string;
163
956
  indentLeft: number;
164
957
  indentRight: number;
165
958
  indentFirst: number;
@@ -169,6 +962,15 @@ export declare interface DocParagraph {
169
962
  numbering: NumberingInfo | null;
170
963
  tabStops: TabStop[];
171
964
  runs: DocRun[];
965
+ /**
966
+ * ECMA-376 §17.13.6.2 `<w:bookmarkStart w:name>` — names of the bookmarks that
967
+ * start within (or at the head of) this paragraph, in document order. A
968
+ * `<w:hyperlink w:anchor="X">` internal link (§17.16.23) targets the paragraph
969
+ * whose `bookmarks` contains `"X"`; {@link buildBookmarkPageMap} turns these
970
+ * into a `bookmarkName → pageIndex` map after pagination. Absent (`undefined`)
971
+ * for the common paragraph that anchors nothing.
972
+ */
973
+ bookmarks?: string[];
172
974
  /** Paragraph background hex color (w:shd fill) */
173
975
  shading?: string | null;
174
976
  /** Force a page break before this paragraph (w:pageBreakBefore) */
@@ -230,6 +1032,8 @@ export declare type DocRun = {
230
1032
  } & DocxTextRun | {
231
1033
  type: 'image';
232
1034
  } & ImageRun | {
1035
+ type: 'chart';
1036
+ } & ChartRun | {
233
1037
  type: 'break';
234
1038
  breakType: 'line' | 'page' | 'column';
235
1039
  } | {
@@ -242,7 +1046,9 @@ export declare type DocRun = {
242
1046
  display: boolean;
243
1047
  fontSize: number;
244
1048
  jc?: string;
245
- };
1049
+ } | {
1050
+ type: 'ptab';
1051
+ } & PTabRun;
246
1052
 
247
1053
  export declare interface DocSettings {
248
1054
  /** §17.15.1.58 `w:kinsoku` — East-Asian line-breaking toggle. `undefined`
@@ -275,6 +1081,14 @@ export declare interface DocTable {
275
1081
  cellMarginRight: number;
276
1082
  /** table horizontal alignment on the page: 'left' | 'center' | 'right'. */
277
1083
  jc: string;
1084
+ /** ECMA-376 §17.4.50 `<w:tblInd>` — indentation added before the table's
1085
+ * LEADING edge (left in an LTR table, right in an RTL/`bidiVisual` table), in
1086
+ * pt. SIGNED: a negative value pulls the table outward past the leading margin
1087
+ * toward the page edge (Word writes this for a header banner that must reach
1088
+ * the physical page edge). `type="dxa"` only; `pct`/`auto` are dropped by the
1089
+ * parser per §17.4.50. Absent ⇒ no direct indent. The renderer applies it only
1090
+ * when the resolved `jc` is left/leading (§17.4.50). */
1091
+ tblInd?: number;
278
1092
  /** ECMA-376 §17.4.52 `<w:tblLayout w:type>` — 'fixed' | 'autofit'. Absent
279
1093
  * (undefined) ⇒ spec default 'autofit'. Both paths size columns from the
280
1094
  * tblGrid (§17.4.48) scaled to fit: 'fixed' uses the grid verbatim; 'autofit'
@@ -342,10 +1156,27 @@ export declare class DocxDocument {
342
1156
  private _document;
343
1157
  private _meta;
344
1158
  private _pages;
1159
+ /** Lazily-built `bookmarkName → 0-based page index` map for internal hyperlink
1160
+ * anchors (IX-nav). Built on first {@link getBookmarkPage} from the paginated
1161
+ * pages (main) or the worker meta's `bookmarkPages` (worker). Nulled by
1162
+ * {@link destroy} so a reused reference never serves a stale document. */
1163
+ private _bookmarkPages;
345
1164
  private _mode;
346
1165
  private _worker;
347
1166
  private _bridge;
348
1167
  private _imageCache;
1168
+ /** Embedded `FontFace` objects this document registered into `document.fonts`
1169
+ * (main mode only — in worker mode the worker owns them and terminates with
1170
+ * its own FontFaceSet). Released in {@link destroy} so they do not leak into
1171
+ * the shared FontFaceSet for the lifetime of the SPA (deduped + refcounted in
1172
+ * core, so a font shared with another open document survives until both go). */
1173
+ private _embeddedFontFaces;
1174
+ /** Google-Fonts `FontFace` objects this document preloaded into `document.fonts`
1175
+ * (main mode only — in worker mode the worker owns them and terminates with its
1176
+ * own FontFaceSet). Released in {@link destroy} so they do not leak into the
1177
+ * shared FontFaceSet for the lifetime of the SPA (deduped + refcounted in core,
1178
+ * so a web font shared with another open document survives until both go). */
1179
+ private _googleFontFaces;
349
1180
  /** One stable closure per instance: core's path-keyed SVG cache namespaces on
350
1181
  * this identity, so two open documents never swap a shared zip path (e.g.
351
1182
  * word/media/image1.svg). Reusing one reference also lets the SVG cache hit
@@ -364,6 +1195,32 @@ export declare class DocxDocument {
364
1195
  * are decoded lazily rather than inlined as base64 at parse time.
365
1196
  */
366
1197
  getImage(imagePath: string, mimeType: string): Promise<Blob>;
1198
+ /**
1199
+ * Extract raw bytes for an embedded font part by zip path (e.g.
1200
+ * `word/fonts/font1.odttf`). Routes through the SAME persistent-worker
1201
+ * `extractImage` message as {@link getImage} — `DocxArchive.extract_image`
1202
+ * reads ANY zip entry, not just media — returning the raw (still obfuscated)
1203
+ * `.odttf` bytes rather than a Blob. Consumed by {@link loadEmbeddedFonts},
1204
+ * which de-obfuscates (ECMA-376 §17.8.1) and registers each as a FontFace.
1205
+ */
1206
+ getFontBytes(partPath: string): Promise<Uint8Array>;
1207
+ /**
1208
+ * Project the document to GitHub-flavoured markdown: headings (from
1209
+ * `<w:outlineLvl>`), bullet / numbered lists, tables (with vMerge
1210
+ * continuation), and rich-text formatting (bold / italic / strikethrough /
1211
+ * hyperlink), with footnotes / endnotes / comments collated at the end.
1212
+ * Positioning, section properties, fonts, and drawing shapes are discarded —
1213
+ * the projection is meant for AI ingestion and full-text search, not layout.
1214
+ *
1215
+ * Runs entirely in the worker off the archive opened at {@link load} (no
1216
+ * re-copy of the file, no re-parse of the model on the main thread), so it
1217
+ * works in BOTH `mode: 'main'` and `mode: 'worker'`.
1218
+ *
1219
+ * @example
1220
+ * const doc = await DocxDocument.load(buffer);
1221
+ * const md = await doc.toMarkdown();
1222
+ */
1223
+ toMarkdown(): Promise<string>;
367
1224
  get pageCount(): number;
368
1225
  /** The render mode this engine was loaded with ('main' | 'worker'). A fact for
369
1226
  * integrators and the scroll viewer: an injected engine's mode decides whether
@@ -399,6 +1256,23 @@ export declare class DocxDocument {
399
1256
  */
400
1257
  get endnotes(): DocNote[];
401
1258
  private _getPages;
1259
+ /** Lazily build (and cache) the `bookmarkName → page index` map from either
1260
+ * the worker meta (worker mode) or the paginated pages (main mode). */
1261
+ private _getBookmarkPages;
1262
+ /**
1263
+ * ECMA-376 §17.13.6.2 / §17.16.23 — resolve a bookmark name (a
1264
+ * `<w:hyperlink w:anchor>` internal-link target) to the 0-based index of the
1265
+ * page its `<w:bookmarkStart w:name>` destination falls on, or `undefined`
1266
+ * when the document has no bookmark of that name. When a bookmark's paragraph
1267
+ * spans a page break, the page where it *begins* is returned.
1268
+ *
1269
+ * This is the map an internal-hyperlink click resolves against: a viewer's
1270
+ * `onHyperlinkClick` default (or an integrator) turns the anchor into a page
1271
+ * and calls {@link DocxViewer.goToPage} (or scrolls the scroll viewer to it).
1272
+ * Works in BOTH `main` and `worker` mode (the map rides along in the worker
1273
+ * meta, built from the same paginated pages as `pageSizes`).
1274
+ */
1275
+ getBookmarkPage(bookmarkName: string): number | undefined;
402
1276
  /**
403
1277
  * ECMA-376 §17.6.13 / §17.6.11 — the page size (pt) of page `pageIndex`, per
404
1278
  * section (a mixed portrait/landscape document returns different sizes per page).
@@ -422,8 +1296,23 @@ export declare class DocxDocument {
422
1296
  * The returned ImageBitmap is owned by the caller: pass it to
423
1297
  * `transferFromImageBitmap` (which consumes it) or call `bitmap.close()`
424
1298
  * when done, or its backing memory is held until GC.
1299
+ *
1300
+ * IX6 — an optional `onTextRun` in `opts` receives the page's text-run
1301
+ * geometry (the same stream `renderPage` emits in main mode), so a caller can
1302
+ * build the selection / find overlay from a worker-rendered page on the SAME
1303
+ * code path as main mode. In worker mode the runs ride back beside the bitmap
1304
+ * (one round-trip, no second render).
1305
+ */
1306
+ renderPageToBitmap(pageIndex: number, opts?: RenderPageToBitmapOptions): Promise<ImageBitmap>;
1307
+ /**
1308
+ * IX6 — collect a page's text-run geometry (`DocxTextRunInfo[]`) without
1309
+ * painting a visible canvas. Works in BOTH modes: worker mode renders the page
1310
+ * off-thread and ships only the runs (no bitmap transfer); main mode renders
1311
+ * to a throwaway offscreen canvas. Used by the find controller to scan every
1312
+ * page for matches. The geometry is identical to a `renderPage` of the same
1313
+ * page at the same width/dpr.
425
1314
  */
426
- renderPageToBitmap(pageIndex: number, opts?: WireRenderPageOptions): Promise<ImageBitmap>;
1315
+ collectPageRuns(pageIndex: number, opts?: WireRenderPageOptions): Promise<DocxTextRunInfo[]>;
427
1316
  }
428
1317
 
429
1318
  export declare interface DocxDocumentModel {
@@ -444,6 +1333,11 @@ export declare interface DocxDocumentModel {
444
1333
  * entry is absent or classified as "auto".
445
1334
  */
446
1335
  fontFamilyClasses?: Record<string, string>;
1336
+ /** ECMA-376 §17.8.3.3-.6 — embedded fonts from `word/fontTable.xml`, resolved
1337
+ * to their `.odttf` part paths + fontKey. The viewer de-obfuscates (§17.8.1)
1338
+ * and registers each as a FontFace before pagination so text measures/draws
1339
+ * with the authored typeface. */
1340
+ embeddedFonts?: EmbeddedFontRef[];
447
1341
  /** ECMA-376 §17.13.5 — flat list of `<w:ins>` / `<w:del>` events in the
448
1342
  * body. Each entry carries author / date / text. The renderer marks
449
1343
  * runs inline via {@link DocxTextRun.revision}; this array is primarily for
@@ -463,6 +1357,31 @@ export declare interface DocxDocumentModel {
463
1357
  * (kinsoku) configuration. Absent when settings.xml has no relevant
464
1358
  * elements (the renderer then uses spec defaults: kinsoku ON). */
465
1359
  settings?: DocSettings;
1360
+ /** RB7 partial degradation: set when `word/document.xml` (the body part) could
1361
+ * not be read or parsed. The document still "opens" — `body` is empty and this
1362
+ * part-tagged error (e.g. `"word/document.xml: <detail>"`) is carried — so the
1363
+ * viewer shows a visible placeholder page instead of throwing. Absent
1364
+ * (`undefined`) for every healthy document. */
1365
+ parseError?: string;
1366
+ }
1367
+
1368
+ export declare interface DocxHighlightColors {
1369
+ /** Fill for non-active matches. */
1370
+ match?: string;
1371
+ /** Fill for the active match. */
1372
+ active?: string;
1373
+ }
1374
+
1375
+ /** One page's highlight input: the run-slices a match covers, and whether that
1376
+ * match is the active one (emphasis colour). */
1377
+ export declare interface DocxHighlightMatch {
1378
+ slices: MatchRunSlice[];
1379
+ active: boolean;
1380
+ }
1381
+
1382
+ /** Where a docx match lives: its 0-based page index. */
1383
+ export declare interface DocxMatchLocation {
1384
+ page: number;
466
1385
  }
467
1386
 
468
1387
  /** ECMA-376 §17.3.2.4 `<w:bdr>` — a run-level border drawn as a box around the
@@ -478,7 +1397,7 @@ export declare interface DocxRunBorder {
478
1397
  space: number;
479
1398
  }
480
1399
 
481
- export declare class DocxScrollViewer {
1400
+ export declare class DocxScrollViewer implements ZoomableViewer {
482
1401
  private _doc;
483
1402
  private readonly _injected;
484
1403
  private readonly _opts;
@@ -499,6 +1418,18 @@ export declare class DocxScrollViewer {
499
1418
  * than a `_scale === 1` sentinel because a fit scale of exactly 1 is a valid
500
1419
  * established state (a 1× fit would otherwise be re-fit forever). */
501
1420
  private _scaleEstablished;
1421
+ /**
1422
+ * IX9 F1 — a `setScale` factor requested BEFORE the base fit is established
1423
+ * (pre-load, or a zero-width container), already clamped to
1424
+ * `[zoomMin, zoomMax]`, or `null` when none is pending. The single-canvas
1425
+ * viewers latch a pre-load `setScale` and honour it on the first render; the
1426
+ * scroll viewers used to silently DROP it — the family-unified semantics are
1427
+ * "latch and apply once the layout establishes". `relayout()` applies (and
1428
+ * clears) this right after establishing the base, firing `onScaleChange` at
1429
+ * application time; `getScale()` reports it while pending so the caller sees
1430
+ * the same value a single-canvas viewer would show.
1431
+ */
1432
+ private _pendingScale;
502
1433
  /** Live slots keyed by page index. */
503
1434
  private readonly _slots;
504
1435
  /** Recyclable detached slots (canvas + textLayer reused across pages). */
@@ -512,6 +1443,27 @@ export declare class DocxScrollViewer {
512
1443
  * reporting an error so a rejection that lands after teardown is swallowed
513
1444
  * rather than surfaced to a `onError` on a dead viewer. */
514
1445
  private _destroyed;
1446
+ /** Throwaway 2D context reused to measure text for the §17.3.2.10 縦中横 overlay
1447
+ * clamp (#836). Lazily created; `null` when canvas metrics are unavailable
1448
+ * (headless), in which case the overlay degrades to the un-clamped span. */
1449
+ private _measureCtx;
1450
+ /**
1451
+ * Concurrent-load latch (generation token). Every self-loading `load()`
1452
+ * increments this and captures the value; after its engine finishes loading it
1453
+ * re-checks the live value and BAILS (destroying its own just-loaded engine) if
1454
+ * a newer `load()` has since started. Without it, two overlapping
1455
+ * `load(A)`/`load(B)` calls race the WASM parse / worker init, and whichever
1456
+ * RESOLVES last wins the swap — even the stale `load(A)` resolving after
1457
+ * `load(B)`; the loser's freshly created engine (never installed, or installed
1458
+ * then overwritten) then leaks its worker + pinned WASM allocation. The latch
1459
+ * composes with SC20: the check runs AFTER the new engine loads but BEFORE the
1460
+ * field assignment, `previous?.destroy()`, and the recycle/relayout post-load
1461
+ * work, so a superseded load never touches `this._doc` nor frees the current
1462
+ * (newer) engine. Only the self-loading path uses it — the injected path throws
1463
+ * up-front and never reaches here. `destroy()` also bumps it so a load in flight
1464
+ * at teardown is treated as superseded and its engine cleaned up.
1465
+ */
1466
+ private _loadGen;
515
1467
  /** Worker mode: page indices whose bitmap render is currently dispatched to the
516
1468
  * engine. Coalesces a scroll storm — we never dispatch a second render for a
517
1469
  * page whose first is still in flight — and lets us drop pages that scrolled
@@ -542,11 +1494,14 @@ export declare class DocxScrollViewer {
542
1494
  * is host-agnostic. */
543
1495
  private _settleTimer;
544
1496
  private _wheelListener;
545
- /** One-shot latch for the worker-mode text-selection warning. The overlay is a
546
- * main-mode-only feature: in worker mode the per-run `onTextRun` geometry
547
- * cannot cross the worker boundary, so an `enableTextSelection` overlay stays
548
- * empty. We warn once (parity with `DocxViewer`) rather than per slot. */
549
- private _warnedNoTextSelection;
1497
+ /** Gesture-only pointer anchor for the NEXT `setScale`, in scrollHost-viewport
1498
+ * px (`{ x, y }` from the wheel event, relative to the scroll host's top-left).
1499
+ * Set by the Ctrl/⌘+wheel handler right before it calls `setScale` so the zoom
1500
+ * pivots on the cursor ("zoom toward the pointer") in BOTH axes; consumed and
1501
+ * cleared by `setScale`. `null` for every non-gesture source (the public
1502
+ * `setScale`, the +/- steppers, `fitWidth`/`fitPage`, the resize re-fit), which
1503
+ * keep the historical viewport-TOP re-anchor so their behaviour is unchanged. */
1504
+ private _pendingZoomAnchor;
550
1505
  /** Observes the container so a width change re-fits the base scale. Disconnected
551
1506
  * in `destroy()`. */
552
1507
  private _resizeObserver;
@@ -615,6 +1570,12 @@ export declare class DocxScrollViewer {
615
1570
  * `_positionSlot` (the flush-left floor), and by `_syncSpacer` (the spacer
616
1571
  * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
617
1572
  private _padH;
1573
+ /** Index of the page whose slot spans content-offset `y` (largest `i` with
1574
+ * `offsets[i] <= y`), for the pointer-anchored zoom re-anchor. Mirrors the
1575
+ * `topIndex` search `computeVisibleRange` runs for the scrollTop, but for an
1576
+ * ARBITRARY content-y (the pointer, not the viewport top). Clamped into
1577
+ * `[0, n-1]`; a `y` below the first page (inside the leading pad) yields 0. */
1578
+ private _pageIndexAtOffset;
618
1579
  private _range;
619
1580
  private _syncSpacer;
620
1581
  /** Horizontal scroll extent: the widest page (docx pages can differ in width)
@@ -664,10 +1625,24 @@ export declare class DocxScrollViewer {
664
1625
  * can pass for an old-epoch resolution). We gate them on the captured epoch.
665
1626
  */
666
1627
  private _renderSlot;
667
- /** Warn once when an `enableTextSelection` overlay was requested but the render
668
- * mode is `worker` (so the overlay stays empty). Same wording as
669
- * `DocxViewer._render` one warning per viewer, not per slot. */
670
- private _maybeWarnNoTextSelection;
1628
+ /**
1629
+ * IX1/IX-nav the click handler passed to the text-layer overlay. When the
1630
+ * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
1631
+ * is suppressed). Otherwise the built-in default is: an external link opens in
1632
+ * a new tab through core `openExternalHyperlink` (URL sanitised against the
1633
+ * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
1634
+ * resolves its bookmark name to its destination page via
1635
+ * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and scrolls there
1636
+ * with {@link scrollToPage}. An anchor naming no known bookmark is a safe no-op
1637
+ * rather than a scroll to a guessed page.
1638
+ */
1639
+ private _hyperlinkHandler;
1640
+ /** A width-measurer primed with a run's `font` — used ONLY to clamp a §17.3.2.10
1641
+ * 縦中横 selection span to its drawn one-em cell (#836). Mirrors DocxViewer's
1642
+ * `_measureForFont`. Returns a length-based fallback when canvas metrics are
1643
+ * unavailable so the caller still gets a callable (the overlay then sees scale
1644
+ * 1 and leaves the span un-clamped). */
1645
+ private _measureForFont;
671
1646
  /** Route an async render failure to `onError`, or `console.error` when none is
672
1647
  * set (so failures are never fully silent), and never after teardown. */
673
1648
  private _reportRenderError;
@@ -697,7 +1672,10 @@ export declare class DocxScrollViewer {
697
1672
  * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
698
1673
  * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
699
1674
  * then re-anchor VERTICALLY so the page currently under the viewport top stays
700
- * fixed. A no-op when nothing is loaded or when the clamped scale is unchanged.
1675
+ * fixed. A no-op when the clamped scale is unchanged. Called BEFORE the doc is
1676
+ * loaded / the base fit is established, the clamped factor is LATCHED (IX9 F1,
1677
+ * family-unified with the single-canvas viewers) and applied by `relayout()`
1678
+ * once the layout establishes — `onScaleChange` fires then.
701
1679
  *
702
1680
  * FLICKER-FREE (design §7): this does NOT re-render the visible pages inline.
703
1681
  * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
@@ -718,6 +1696,38 @@ export declare class DocxScrollViewer {
718
1696
  * can no longer return below the floor to the original base fit through this API.
719
1697
  */
720
1698
  setScale(scale: number): void;
1699
+ /** IX9 {@link ZoomableViewer} — the current zoom factor, where `1` = 100% (a
1700
+ * page at its natural pt→px width). This is the viewer's absolute `_scale`
1701
+ * (`widthPt × PT_TO_PX × _scale` is the drawn width), so it reads `1` at true
1702
+ * 100% and, after the initial fit-to-width, the base fit factor. Before the
1703
+ * fit is established it reports a latched pre-load `setScale` (IX9 F1) if one
1704
+ * is pending — matching what a single-canvas viewer would show — else `1`. */
1705
+ getScale(): number;
1706
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
1707
+ * ladder above the current factor (clamped to `zoomMax` by {@link setScale}). */
1708
+ zoomIn(): void;
1709
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
1710
+ zoomOut(): void;
1711
+ /**
1712
+ * IX9 {@link ZoomableViewer} — fit a page's WIDTH to the container (the classic
1713
+ * continuous-scroll "fit width"). Sets the scale to the width-fit base for the
1714
+ * current container, then re-anchors + re-renders via {@link setScale}. Defers
1715
+ * (no-op) while the container is unlaid-out. Note the `zoomMin`/`zoomMax` clamp
1716
+ * still applies, so a fit below `zoomMin` pins to `zoomMin`.
1717
+ */
1718
+ fitWidth(): void;
1719
+ /**
1720
+ * IX9 {@link ZoomableViewer} — fit a WHOLE page (width and height) inside the
1721
+ * container so one page is visible without scrolling; takes the tighter of the
1722
+ * width/height fit. Uses the FIRST page's size (the continuous viewer's fit
1723
+ * reference, matching the base-fit convention). Defers while unlaid-out.
1724
+ */
1725
+ fitPage(): void;
1726
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: the width-fit factor is the
1727
+ * established base (`_baseScale`); the page-fit additionally bounds by the
1728
+ * container height against the first page's height. Applies via {@link setScale}
1729
+ * so the flicker-free re-anchor / settle path and `onScaleChange` all run. */
1730
+ private _fit;
721
1731
  /**
722
1732
  * CSS preview of the visible window at the current `_scale` (design §7
723
1733
  * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
@@ -744,12 +1754,11 @@ export declare class DocxScrollViewer {
744
1754
  private _scheduleSettle;
745
1755
  /** Full-resolution settle re-render of the visible window (design §7 mechanisms
746
1756
  * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
747
- * swap (main) / same-canvas transfer (worker). Main mode also rebuilds the text
748
- * overlay and clears its preview transform; in worker mode the overlay is
749
- * permanently empty (text selection is main-mode-only), so the transform is
750
- * inert there and is reset on recycle. Dispatched at the CURRENT epoch; the
751
- * existing epoch gate discards it if a later `setScale` supersedes it
752
- * mid-render. */
1757
+ * swap (main) / same-canvas transfer (worker). Both modes rebuild the text
1758
+ * overlay from the fresh render's run geometry (IX6 — worker mode collects the
1759
+ * runs off-thread via `_renderSlotBitmap`) and clear the preview transform.
1760
+ * Dispatched at the CURRENT epoch; the existing epoch gate discards it if a
1761
+ * later `setScale` supersedes it mid-render. */
753
1762
  private _settleRender;
754
1763
  /**
755
1764
  * Settle-render one slot at the current scale (design §7 mechanism 3).
@@ -825,6 +1834,8 @@ export declare class DocxScrollViewer {
825
1834
  /* Excluded from this release type: baseScaleForTest */
826
1835
  /* Excluded from this release type: renderEpochForTest */
827
1836
  /* Excluded from this release type: resizeForTest */
1837
+ /* Excluded from this release type: contentAtViewportYForTest */
1838
+ /* Excluded from this release type: viewportYOfForTest */
828
1839
  /**
829
1840
  * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
830
1841
  * engine) destroy the engine. An injected engine is left intact — the caller
@@ -871,9 +1882,10 @@ export declare interface DocxScrollViewerOptions extends Omit<RenderPageOptions,
871
1882
  paddingRight?: number;
872
1883
  /** Pages kept mounted beyond the viewport on each side. Default 1. */
873
1884
  overscan?: number;
874
- /** Per-page transparent text-selection overlay. MAIN render mode only:
875
- * in worker mode `onTextRun` cannot cross the worker boundary, so the overlay
876
- * stays empty and the viewer logs one warning (design §11). */
1885
+ /** Per-page transparent text-selection overlay. IX6 works in BOTH render
1886
+ * modes: in worker mode the per-run geometry is collected off-thread and
1887
+ * shipped back beside the page bitmap, so the overlay is populated identically
1888
+ * to main mode (no more empty overlay / one-time warning). */
877
1889
  enableTextSelection?: boolean;
878
1890
  /** Minimum zoom scale (px-per-pt multiplier floor). Default 0.1. */
879
1891
  zoomMin?: number;
@@ -916,6 +1928,17 @@ export declare interface DocxScrollViewerOptions extends Omit<RenderPageOptions,
916
1928
  * `computeVisibleRange` (the first page intersecting the viewport top,
917
1929
  * EXCLUDING overscan). */
918
1930
  onVisiblePageChange?: (topIndex: number, total: number) => void;
1931
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100% = a page
1932
+ * at its natural pt→px size): from {@link DocxScrollViewer.setScale},
1933
+ * `zoomIn`/`zoomOut`, `fitWidth`/`fitPage`, a Ctrl/⌘+wheel gesture, or a
1934
+ * container-resize re-fit. Named `onScaleChange` to match the single-canvas
1935
+ * viewers so all five share one notification shape. */
1936
+ onScaleChange?: (scale: number) => void;
1937
+ /** IX1 (design decision — NOT user-confirmed, integrator may veto). Called when
1938
+ * a hyperlink run is clicked. When omitted, the default is: external → open in a
1939
+ * new tab via core `openExternalHyperlink` (sanitised, noopener,noreferrer);
1940
+ * internal → jump to the page whose text contains the bookmark (best-effort). */
1941
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
919
1942
  /** Error callback. When set, `load()` invokes it and resolves (otherwise the
920
1943
  * error is rethrown — shared viewer error contract). It ALSO fires for async
921
1944
  * per-slot render failures (both main `renderPage` and worker
@@ -930,6 +1953,15 @@ export declare interface DocxTextRun {
930
1953
  bold: boolean;
931
1954
  italic: boolean;
932
1955
  underline: boolean;
1956
+ /** ECMA-376 §17.3.2.40 `<w:u w:val>` — the raw ST_Underline (§17.18.99) style
1957
+ * value (`double` / `thick` / `dotted` / `wave` / `dashLong` / …). Absent for
1958
+ * the plain single rule (or no underline). The renderer normalizes this
1959
+ * WordprocessingML vocabulary to the shared DrawingML ST_TextUnderlineType
1960
+ * (§20.1.10.82) that `core.drawUnderline` dispatches on. */
1961
+ underlineStyle?: string;
1962
+ /** ECMA-376 §17.3.2.40 `<w:u w:color>` — underline-only colour (hex 6, or the
1963
+ * literal `auto`). Absent ⇒ the underline follows the glyph colour. */
1964
+ underlineColor?: string;
933
1965
  strikethrough: boolean;
934
1966
  fontSize: number;
935
1967
  color: string | null;
@@ -957,10 +1989,23 @@ export declare interface DocxTextRun {
957
1989
  vertAlign: 'super' | 'sub' | null;
958
1990
  /** Target URL for hyperlinks (resolved from relationships.xml) */
959
1991
  hyperlink: string | null;
1992
+ /** ECMA-376 §17.16.23 `<w:hyperlink w:anchor>` — internal bookmark name this
1993
+ * link jumps to (a `<w:bookmarkStart w:name>` in the same document). Set for an
1994
+ * internal cross-reference / TOC entry. When a link carries both `r:id` and
1995
+ * `w:anchor`, {@link DocxTextRun.hyperlink} (external) wins and this still
1996
+ * records the anchor. Absent when the link has no anchor. */
1997
+ hyperlinkAnchor?: string | null;
960
1998
  allCaps?: boolean;
961
1999
  smallCaps?: boolean;
962
2000
  doubleStrikethrough?: boolean;
963
2001
  highlight?: string | null;
2002
+ /** ECMA-376 §17.3.2.12 `<w:em w:val>` — emphasis (boten / 圏点) mark drawn on
2003
+ * every non-space character of the run (§17.18.24 ST_Em). `'dot'` = filled
2004
+ * dot above, `'comma'` = sesame/comma above, `'circle'` = hollow circle
2005
+ * above, `'underDot'` = filled dot below (horizontal writing). Absent (or the
2006
+ * authored `val="none"`) ⇒ no mark. The renderer stamps the mark per glyph
2007
+ * after the text and does NOT change the glyph advance. */
2008
+ emphasisMark?: EmphasisMark;
964
2009
  /** ECMA-376 §17.3.3.25 ruby annotation (furigana). Renders above the
965
2010
  * base text in a smaller font; line height is expanded to fit it. */
966
2011
  ruby?: RubyAnnotation;
@@ -992,6 +2037,42 @@ export declare interface DocxTextRun {
992
2037
  /** ECMA-376 §17.3.2.20 `<w:lang w:bidi>` — complex-script (RTL) language tag,
993
2038
  * lower-cased (e.g. "ar-sa", "ae-ar"). Drives Word's AN digit ordering. */
994
2039
  langBidi?: string;
2040
+ /** ECMA-376 §17.3.2.35 `<w:spacing w:val>` — character-spacing adjustment in
2041
+ * POINTS (signed): the extra pitch added after each character before the next
2042
+ * is rendered. The renderer feeds it to `ctx.letterSpacing` on BOTH the
2043
+ * measure and paint passes so line breaking / pagination stay consistent.
2044
+ * Absent ⇒ no extra pitch. */
2045
+ charSpacing?: number;
2046
+ /** ECMA-376 §17.3.2.43 `<w:w w:val>` — horizontal text scale as a FRACTION of
2047
+ * normal character width (0.67 = 67%, 2.0 = 200%). Stretches each glyph's
2048
+ * width, not the gap between glyphs. Absent ⇒ 100%. */
2049
+ charScale?: number;
2050
+ /** ECMA-376 §17.3.2.24 `<w:position w:val>` — baseline raise (positive) /
2051
+ * lower (negative) in POINTS, without changing the font size or line box.
2052
+ * Absent ⇒ no shift. */
2053
+ position?: number;
2054
+ /** ECMA-376 §17.3.2.19 `<w:kern w:val>` — font-kerning threshold in POINTS
2055
+ * (the smallest font size that is kerned). Presence enables kerning subject
2056
+ * to the threshold; absent ⇒ kerning off (the hierarchy default). `0` = kern
2057
+ * at all sizes. */
2058
+ kerning?: number;
2059
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:vert>` — horizontal-in-vertical
2060
+ * (縦中横 / tate-chū-yoko). `true` means that in a VERTICAL (tbRl) page this
2061
+ * run's characters are laid out horizontally side by side within ONE cell of
2062
+ * the vertical line (rotated 90° relative to the vertical flow). Absent ⇒
2063
+ * normal vertical stacking. Inert in a horizontal page. */
2064
+ eastAsianVert?: boolean;
2065
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:vertCompress>` — compress the
2066
+ * 縦中横 run to fit the existing line height without growing the line. Ignored
2067
+ * unless {@link eastAsianVert} is set. Absent ⇒ not compressed. */
2068
+ eastAsianVertCompress?: boolean;
2069
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:combine>` — two-lines-in-one.
2070
+ * PARSED for completeness; not yet rendered (no fixture). */
2071
+ eastAsianCombine?: boolean;
2072
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:combineBrackets>` (§17.18.8) —
2073
+ * bracket style around two-lines-in-one text. PARSED for completeness; the
2074
+ * two-lines-in-one draw is a follow-up. */
2075
+ eastAsianCombineBrackets?: string;
995
2076
  /** ECMA-376 §17.11.6/.7/.16/.17 — set when this run is a footnote/endnote
996
2077
  * reference marker (`<w:footnoteReference>` in the body, `<w:footnoteRef>` at
997
2078
  * the start of the note's content, and the endnote equivalents). `text` holds
@@ -1015,11 +2096,40 @@ export declare interface DocxTextRunInfo {
1015
2096
  fontSize: number;
1016
2097
  /** CSS `font` shorthand used for canvas drawing (e.g. `"bold 16px Arial"`). */
1017
2098
  font: string;
1018
- }
1019
-
1020
- export declare class DocxViewer {
2099
+ /** ECMA-376 §17.6.20 (tbRl) — when the page is vertical the canvas is the
2100
+ * physical landscape page rotated +90° at paint, so this run's `x`/`y` are the
2101
+ * PHYSICAL top-left the overlay span must sit at, and `transform` is the CSS
2102
+ * rotation (`"rotate(90deg)"`, applied about the span's top-left) that lays the
2103
+ * horizontal DOM span along the drawn (rotated) glyph run. Absent for
2104
+ * horizontal pages (the span is placed at `x`/`y` untransformed). */
2105
+ transform?: string;
2106
+ /** IX1 — the resolved hyperlink target of this run (ECMA-376 §17.16.22
2107
+ * external URL / §17.16.23 internal `w:anchor` bookmark), or absent for a
2108
+ * non-link run. The text-layer overlay turns a run carrying this into a
2109
+ * clickable region; the drawn glyphs are unaffected. */
2110
+ hyperlink?: HyperlinkTarget;
2111
+ /** ECMA-376 §17.3.2.10 eastAsianLayout `w:vert` (縦中横 / horizontal-in-vertical):
2112
+ * `true` when this run was drawn as tate-chu-yoko — its glyphs laid out
2113
+ * horizontally, side by side, COMPRESSED into ONE em cell of the vertical
2114
+ * column (see {@link drawTateChuYokoRun}). `w` is the drawn cell extent (one
2115
+ * em), NOT the natural text width, so the find / selection overlays must clamp
2116
+ * their horizontal extent to `w` rather than re-measuring the run's natural
2117
+ * glyphs (issue #836). Absent for every ordinary run. */
2118
+ eastAsianVert?: boolean;
2119
+ }
2120
+
2121
+ export declare class DocxViewer implements ZoomableViewer {
1021
2122
  private _doc;
1022
2123
  private _currentPage;
2124
+ /**
2125
+ * IX9 explicit zoom factor (`1` = 100% = the page at its natural pt→px width),
2126
+ * or `null` when the caller has never invoked a zoom method. `null` preserves
2127
+ * the pre-IX9 render path EXACTLY: the page renders at `opts.width` (or its
2128
+ * natural width when that is unset), so default rendering is byte-identical. The
2129
+ * first `setScale`/`zoomIn`/`zoomOut`/`fitWidth`/`fitPage` call latches a number
2130
+ * here, after which `_renderPage` derives the canvas width from it instead.
2131
+ */
2132
+ private _scale;
1023
2133
  private _canvas;
1024
2134
  private _wrapper;
1025
2135
  /** The canvas's DOM position BEFORE the constructor reparented it into
@@ -1032,20 +2142,56 @@ export declare class DocxViewer {
1032
2142
  * (empty string if it was unset), restored on {@link destroy}. */
1033
2143
  private _originalDisplay;
1034
2144
  private _textLayer;
2145
+ /** IX2 — the find-highlight overlay layer. Always created (independent of
2146
+ * `enableTextSelection`): highlights ride the same positioned-DOM overlay
2147
+ * mechanism as the selection layer but are visible boxes, not transparent
2148
+ * spans. Sits above the text layer so a highlight shows over a link's hit
2149
+ * region without stealing its clicks (`pointer-events:none`). */
2150
+ private _highlightLayer;
2151
+ /** IX2 — find state (per-page runs, matches, active cursor). */
2152
+ private _find;
2153
+ /** A 2d context used only to measure text for highlight geometry (its own
2154
+ * 1×1 offscreen canvas, so measuring never touches the visible canvas). */
2155
+ private _measureCtx;
1035
2156
  private _opts;
1036
2157
  private readonly _mode;
1037
2158
  /** The canvas's bitmaprenderer context, used only in worker mode (a canvas
1038
2159
  * holds one context type for its lifetime; the main-mode 2d render path is
1039
2160
  * never used on the same canvas). */
1040
2161
  private _bitmapCtx;
1041
- private _warnedNoTextSelection;
2162
+ /** Set by {@link destroy} (first line). Guards {@link _reportRenderError} so a
2163
+ * render rejection that lands AFTER teardown is swallowed rather than surfaced
2164
+ * to an `onError` / `console.error` on a dead viewer — parity with the scroll
2165
+ * viewers' `_destroyed` flag. */
2166
+ private _destroyed;
2167
+ /**
2168
+ * Concurrent-load latch (generation token). Every {@link load} increments this
2169
+ * and captures the value; after its engine finishes loading it re-checks the
2170
+ * live value and BAILS (destroying its own just-loaded engine) if a newer
2171
+ * `load()` has since started. Without it, two overlapping `load(A)`/`load(B)`
2172
+ * calls race the WASM parse / worker init, and whichever RESOLVES last wins the
2173
+ * swap — even the stale `load(A)` resolving after `load(B)`; the loser's freshly
2174
+ * created engine (never installed, or installed then overwritten) then leaks its
2175
+ * worker + pinned WASM allocation. The latch composes with SC20: the check runs
2176
+ * AFTER the new engine loads but BEFORE the field assignment and
2177
+ * `previous?.destroy()`, so a superseded load never touches `this._doc` nor
2178
+ * frees the current (newer) engine. {@link destroy} also bumps it so a load in
2179
+ * flight at teardown is treated as superseded and its engine cleaned up.
2180
+ */
2181
+ private _loadGen;
1042
2182
  constructor(canvas: HTMLCanvasElement, opts?: DocxViewerOptions);
1043
2183
  /**
1044
2184
  * Load a DOCX from URL or ArrayBuffer and render the first page.
1045
2185
  *
1046
- * Error contract (shared by all three viewers): on failure, if an `onError`
1047
- * callback was provided it is invoked and `load` resolves normally; if not,
1048
- * the error is rethrown so it is never silently swallowed.
2186
+ * Error contract (shared by all three viewers):
2187
+ * - Parse/load failure (the underlying `DocxDocument.load()` call itself
2188
+ * rejects): if an `onError` callback was provided it is invoked and `load`
2189
+ * resolves normally; if not, the error is rethrown so it is never silently
2190
+ * swallowed.
2191
+ * - Render failure (the first page fails to draw AFTER a successful
2192
+ * parse/load): routed to the shared `_reportRenderError` contract (`onError`
2193
+ * if provided, else `console.error` — never silent) and `load` still
2194
+ * RESOLVES, matching every subsequent navigation call.
1049
2195
  */
1050
2196
  load(source: string | ArrayBuffer): Promise<void>;
1051
2197
  get pageCount(): number;
@@ -1055,6 +2201,91 @@ export declare class DocxViewer {
1055
2201
  goToPage(index: number): Promise<void>;
1056
2202
  nextPage(): Promise<void>;
1057
2203
  prevPage(): Promise<void>;
2204
+ /** Natural (100%) CSS-px width of the current page — `widthPt × PT_TO_PX`.
2205
+ * This is the scale-1 reference every zoom factor multiplies. 0 when nothing
2206
+ * is loaded. */
2207
+ private _naturalWidthPx;
2208
+ /**
2209
+ * The width (CSS px) `_renderPage` renders the current page at, honouring the
2210
+ * zoom state. `_scale === null` (no zoom method ever called) ⇒ the pre-IX9
2211
+ * value `opts.width` verbatim (byte-identical default: `undefined` lets the
2212
+ * renderer use the page's natural width). Once a factor latched ⇒
2213
+ * `naturalWidth × scale` (rounded), so the on-screen page is exactly `scale ×`
2214
+ * its natural size regardless of the original `opts.width`.
2215
+ */
2216
+ private _renderWidth;
2217
+ /** IX9 {@link ZoomableViewer} — the current zoom factor (`1` = 100%). Before
2218
+ * any zoom method is called this is the EFFECTIVE scale implied by the current
2219
+ * render width: `opts.width / naturalWidth`, or `1` when `opts.width` is unset
2220
+ * (the page renders at its natural size) or nothing is loaded. */
2221
+ getScale(): number;
2222
+ private _zoomMin;
2223
+ private _zoomMax;
2224
+ /**
2225
+ * IX9 {@link ZoomableViewer} — set the absolute zoom factor (`1` = 100% = the
2226
+ * page at its natural pt→px width), clamped to `[zoomMin, zoomMax]`, and
2227
+ * re-render the current page at the new size. Fires `onScaleChange` when the
2228
+ * clamped factor actually changes. Resolves once the re-render settles. A no-op
2229
+ * (but still latches the scale) when nothing is loaded.
2230
+ */
2231
+ setScale(scale: number): Promise<void>;
2232
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
2233
+ * ladder (clamped to `zoomMax`). */
2234
+ zoomIn(): Promise<void>;
2235
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
2236
+ zoomOut(): Promise<void>;
2237
+ /**
2238
+ * IX9 {@link ZoomableViewer} — fit the current page's WIDTH to the host
2239
+ * container (the element the canvas lives in, or `opts.container` if supplied),
2240
+ * then re-render. Defers (no-op) when nothing is loaded or the container is
2241
+ * unlaid-out. Routes through {@link setScale}, so the factor is clamped and
2242
+ * `onScaleChange` fires.
2243
+ */
2244
+ fitWidth(): Promise<void>;
2245
+ /**
2246
+ * IX9 {@link ZoomableViewer} — fit the WHOLE current page (width and height)
2247
+ * inside the container so it is visible without scrolling; takes the tighter of
2248
+ * the width/height fit. Defers when unloaded / unlaid-out.
2249
+ */
2250
+ fitPage(): Promise<void>;
2251
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: measure the natural page
2252
+ * size + the container box, ask core's pure `fitScale`, apply via setScale. */
2253
+ private _fit;
2254
+ /** The element a fit measures against: the explicit `opts.container`, else the
2255
+ * host the wrapper was inserted into (`_wrapper.parentElement`). `null` when
2256
+ * the canvas was mounted detached (no host to fit to). */
2257
+ private _fitContainer;
2258
+ /**
2259
+ * IX2 — find every occurrence of `query` in the document and highlight them
2260
+ * all (a soft box per match, drawn on the highlight overlay over the drawn
2261
+ * glyphs). Returns every match in document order, each tagged with its
2262
+ * `{ page }` (0-based). Case-insensitive by default (browser find-in-page);
2263
+ * pass `{ caseSensitive: true }` to match case exactly.
2264
+ *
2265
+ * Scans all pages, so a large document renders each page once (offscreen) to
2266
+ * read its text (the visible page reuses its on-screen render). IX6 — works in
2267
+ * BOTH `mode: 'main'` and `mode: 'worker'`: in worker mode each page's run
2268
+ * geometry is collected off-thread and shipped back, so find returns the same
2269
+ * matches on the same code path. An empty query clears the find and returns `[]`.
2270
+ */
2271
+ findText(query: string, opts?: FindMatchesOptions): Promise<FindMatch<DocxMatchLocation>[]>;
2272
+ /**
2273
+ * IX2 — move to the next match (wrap-around from last to first), navigating to
2274
+ * its page if needed, and draw it in the distinct active-match colour. Returns
2275
+ * the now-active match, or `null` when there are no matches. Call
2276
+ * {@link findText} first.
2277
+ */
2278
+ findNext(): Promise<FindMatch<DocxMatchLocation> | null>;
2279
+ /** IX2 — move to the previous match (wrap-around from first to last). */
2280
+ findPrev(): Promise<FindMatch<DocxMatchLocation> | null>;
2281
+ /** IX2 — clear all highlights and reset the find state. */
2282
+ clearFind(): void;
2283
+ /** Navigate to the active match's page (if not already there) and redraw the
2284
+ * highlights so the active box shows in the emphasis colour. */
2285
+ private _activateMatch;
2286
+ /** Rebuild the highlight overlay for the current page from cached runs
2287
+ * (no page re-render). */
2288
+ private _redrawHighlights;
1058
2289
  /**
1059
2290
  * Terminate the parser worker and release resources.
1060
2291
  *
@@ -1066,7 +2297,34 @@ export declare class DocxViewer {
1066
2297
  */
1067
2298
  destroy(): void;
1068
2299
  private _render;
2300
+ /** Route a render failure to `onError`, or `console.error` when none is given
2301
+ * (never fully silent), and never after teardown. Mirrors the scroll viewers'
2302
+ * `_reportRenderError`. */
2303
+ private _reportRenderError;
2304
+ private _renderPage;
2305
+ /** Draw the find-highlight boxes for the current page from its runs. Clears
2306
+ * the overlay when there is no active find. */
2307
+ private _buildHighlightLayer;
2308
+ /** A width-measurer primed with `font`, backed by a private 1×1 canvas so it
2309
+ * never disturbs the visible canvas's context state. */
2310
+ private _measureForFont;
2311
+ /** Render a page to a throwaway offscreen canvas purely to collect its runs
2312
+ * (text + geometry) for search, without touching the visible canvas. Used by
2313
+ * the find controller for pages other than the one on screen. */
2314
+ private _collectPageRuns;
1069
2315
  private _buildTextLayer;
2316
+ /**
2317
+ * IX1/IX-nav — the click handler passed to the text-layer overlay. When the
2318
+ * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
2319
+ * is suppressed). Otherwise the built-in default is: an external link opens in
2320
+ * a new tab through core `openExternalHyperlink` (URL sanitised against the
2321
+ * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
2322
+ * resolves its bookmark name to a page via
2323
+ * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and jumps there
2324
+ * with {@link goToPage}. An anchor naming no known bookmark is a safe no-op
2325
+ * rather than a jump to a guessed page.
2326
+ */
2327
+ private _hyperlinkHandler;
1070
2328
  }
1071
2329
 
1072
2330
  export declare interface DocxViewerOptions extends RenderPageOptions, LoadOptions {
@@ -1078,10 +2336,53 @@ export declare interface DocxViewerOptions extends RenderPageOptions, LoadOption
1078
2336
  enableTextSelection?: boolean;
1079
2337
  /** Called when a page finishes rendering. */
1080
2338
  onPageChange?: (index: number, total: number) => void;
2339
+ /** IX9 zoom contract ({@link ZoomableViewer}) — the clamp range for
2340
+ * {@link DocxViewer.setScale} / `zoomIn` / `zoomOut` / `fitWidth` / `fitPage`,
2341
+ * as user-facing zoom factors (`1` = 100% = the page at its natural pt→px
2342
+ * size). Defaults 0.1–4 (10%–400%), matching the other viewers. */
2343
+ zoomMin?: number;
2344
+ zoomMax?: number;
2345
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100%): from
2346
+ * {@link DocxViewer.setScale}, `zoomIn`/`zoomOut`, or `fitWidth`/`fitPage`.
2347
+ * Named `onScaleChange` to match the pptx/xlsx viewers so all five share one
2348
+ * notification shape. */
2349
+ onScaleChange?: (scale: number) => void;
2350
+ /** IX1 (design decision — NOT user-confirmed, integrator may veto). Called when
2351
+ * a hyperlink run is clicked. When omitted, the default is: external → open in a
2352
+ * new tab via core `openExternalHyperlink` (sanitised, noopener,noreferrer);
2353
+ * internal → jump to the page whose text contains the bookmark (best-effort). */
2354
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
1081
2355
  /** Called on parse or render errors. */
1082
2356
  onError?: (err: Error) => void;
1083
2357
  }
1084
2358
 
2359
+ /** A duotone effect resolved to its two endpoint colours. Both are 6-char
2360
+ * uppercase hex WITHOUT a leading `#` (the form the Rust parsers emit). `clr1`
2361
+ * is the dark endpoint (luminance 0), `clr2` the light endpoint (luminance 1),
2362
+ * matching the child order of `<a:duotone>` in §20.1.8.23. Any per-colour
2363
+ * transforms (lumMod/lumOff/tint/satMod/…) are already baked into these hexes
2364
+ * by the parser's colour-resolution machinery. */
2365
+ declare interface Duotone {
2366
+ /** First `EG_ColorChoice` child — the dark endpoint. 6-char hex, no `#`. */
2367
+ clr1: string;
2368
+ /** Second `EG_ColorChoice` child — the light endpoint. 6-char hex, no `#`. */
2369
+ clr2: string;
2370
+ }
2371
+
2372
+ /** ECMA-376 §17.8.3.3-.6 — one embedded font-style slot from
2373
+ * `word/fontTable.xml`, resolved to its obfuscated part path + fontKey. */
2374
+ export declare interface EmbeddedFontRef {
2375
+ fontName: string;
2376
+ style: 'regular' | 'bold' | 'italic' | 'boldItalic';
2377
+ partPath: string;
2378
+ fontKey: string;
2379
+ }
2380
+
2381
+ /** ECMA-376 §17.18.24 ST_Em — the emphasis-mark styles a run may carry via
2382
+ * `<w:em w:val>` (§17.3.2.12). `'none'` is filtered out by the parser, so the
2383
+ * model only ever carries one of these four positive marks (or `undefined`). */
2384
+ declare type EmphasisMark = 'dot' | 'comma' | 'circle' | 'underDot';
2385
+
1085
2386
  export declare interface FieldRun {
1086
2387
  /** "page" | "numPages" | "other" */
1087
2388
  fieldType: string;
@@ -1100,6 +2401,51 @@ export declare interface FieldRun {
1100
2401
  smallCaps?: boolean;
1101
2402
  doubleStrikethrough?: boolean;
1102
2403
  highlight?: string | null;
2404
+ /** ECMA-376 §17.3.2.12 `<w:em w:val>` — emphasis (boten / 圏点) mark, mirrors
2405
+ * {@link DocxTextRun.emphasisMark} (§17.18.24 ST_Em). Absent (or the
2406
+ * authored `val="none"`) ⇒ no mark. */
2407
+ emphasisMark?: EmphasisMark;
2408
+ }
2409
+
2410
+ /**
2411
+ * IX2 public find-result shape, shared by all three viewers.
2412
+ *
2413
+ * `findText` returns an ordered list of {@link FindMatch}. Every match carries
2414
+ * its ordinal position (`matchIndex`, 0-based, document order — the same index
2415
+ * `findNext` / `findPrev` cycle through), the matched `text`, and a
2416
+ * format-specific `location`. The location is where the three formats
2417
+ * legitimately differ — a docx match lives on a page, a pptx match on a slide,
2418
+ * an xlsx match in a sheet cell — so `FindMatch` is generic over it rather than
2419
+ * forcing an artificial common shape. Each viewer instantiates it with its own
2420
+ * location type:
2421
+ *
2422
+ * - `DocxViewer.findText` → `FindMatch<DocxMatchLocation>` ({ page })
2423
+ * - `PptxViewer.findText` → `FindMatch<PptxMatchLocation>` ({ slide })
2424
+ * - `XlsxViewer.findText` → `FindMatch<XlsxMatchLocation>` ({ sheet, ref, … })
2425
+ *
2426
+ * The generic default is `unknown` so `FindMatch` can be referenced without a
2427
+ * type argument (e.g. in generic UI code) while each viewer's return type stays
2428
+ * precise.
2429
+ */
2430
+ export declare interface FindMatch<Loc = unknown> {
2431
+ /** 0-based ordinal among all matches, in document order. This is the index
2432
+ * `findNext`/`findPrev` make active, so a caller can correlate the array it
2433
+ * got from `findText` with the active-match reported by navigation. */
2434
+ matchIndex: number;
2435
+ /** The text that matched (the query as it appears in the document — its
2436
+ * original case, not the folded form used for case-insensitive matching). */
2437
+ text: string;
2438
+ /** Where the match is, in the format's own coordinates. */
2439
+ location: Loc;
2440
+ }
2441
+
2442
+ /** Options for {@link findMatches}. */
2443
+ export declare interface FindMatchesOptions {
2444
+ /**
2445
+ * Match case exactly. Default `false` (case-insensitive, like a browser's
2446
+ * find-in-page). IX2 default — an integrator can pass `true`.
2447
+ */
2448
+ caseSensitive?: boolean;
1103
2449
  }
1104
2450
 
1105
2451
  /**
@@ -1158,6 +2504,49 @@ export declare interface HeadersFooters {
1158
2504
  even: HeaderFooter | null;
1159
2505
  }
1160
2506
 
2507
+ /**
2508
+ * Shared hyperlink model + URL sanitisation for docx / pptx / xlsx (IX1).
2509
+ *
2510
+ * All three formats carry the same two ECMA-376 concepts:
2511
+ * - an **external** hyperlink — an absolute URL resolved from a relationship
2512
+ * part target (`document.xml.rels` for docx §17.16.22, the slide rels for
2513
+ * pptx §21.1.2.3.5, the worksheet rels for xlsx §18.3.1.47), with
2514
+ * `TargetMode="External"`.
2515
+ * - an **internal** hyperlink — a jump within the document itself:
2516
+ * docx `w:anchor` -> a `<w:bookmarkStart w:name>` (§17.16.23), pptx
2517
+ * `action="ppaction://hlinksldjump"` -> a slide, xlsx `location` -> a defined
2518
+ * name or a `Sheet!A1` cell reference.
2519
+ *
2520
+ * The parsers (Rust, one per format) do the format-specific rels lookup and hand
2521
+ * each run / shape / cell a {@link HyperlinkTarget}. Everything downstream — the
2522
+ * text-layer overlay, the viewer default click behaviour, and any integrator
2523
+ * callback — is format-agnostic and consumes this one shape. Keeping the type +
2524
+ * the pure `sanitizeHyperlinkUrl` predicate here (not duplicated per package)
2525
+ * follows the cross-package unification principle: a scheme-allowlist bug fixed
2526
+ * once is fixed everywhere.
2527
+ */
2528
+ /**
2529
+ * A resolved hyperlink attached to a run, shape, or cell.
2530
+ *
2531
+ * - `external` — `url` is the raw target as authored in the file. It is NOT
2532
+ * guaranteed safe; run it through {@link sanitizeHyperlinkUrl} before
2533
+ * navigating. It is kept verbatim here so an integrator can apply its own
2534
+ * policy (e.g. allow `file:` on a trusted intranet viewer).
2535
+ * - `internal` — `ref` is the in-document destination, verbatim from the file:
2536
+ * docx: the bookmark name (`w:anchor`).
2537
+ * pptx: the internal action (e.g. `ppaction://hlinksldjump`), with the
2538
+ * resolved 0-based `slideIndex` when the rels target names a slide.
2539
+ * xlsx: the `location` string (a defined name or `Sheet1!A1`).
2540
+ */
2541
+ export declare type HyperlinkTarget = {
2542
+ kind: 'external';
2543
+ url: string;
2544
+ } | {
2545
+ kind: 'internal';
2546
+ ref: string;
2547
+ slideIndex?: number;
2548
+ };
2549
+
1161
2550
  export declare interface ImageRun {
1162
2551
  /**
1163
2552
  * Embedded zip path of the raster blip (e.g. `word/media/image1.png`) — the
@@ -1215,6 +2604,19 @@ export declare interface ImageRun {
1215
2604
  * transparency. Implements a:clrChange (make-background-transparent).
1216
2605
  */
1217
2606
  colorReplaceFrom?: string;
2607
+ /**
2608
+ * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
2609
+ * colours (through the document theme). Absent ⇒ no duotone. When present the
2610
+ * renderer decodes the raster once, remaps it along the `clr1`→`clr2`
2611
+ * luminance ramp, and caches the recoloured bitmap under a colour-suffixed key.
2612
+ */
2613
+ duotone?: Duotone;
2614
+ /**
2615
+ * ECMA-376 §20.1.8.6 `<a:alphaModFix@amt>` opacity as 0..1. Absent ⇒ fully
2616
+ * opaque. When present the renderer multiplies the picture's `globalAlpha` by
2617
+ * this fraction.
2618
+ */
2619
+ alpha?: number;
1218
2620
  /**
1219
2621
  * Wrap mode for anchor images:
1220
2622
  * "square" | "topAndBottom" | "none" | "tight" | "through"
@@ -1264,6 +2666,18 @@ export declare interface ImageRun {
1264
2666
  anchorYRelativeFrom?: string | null;
1265
2667
  }
1266
2668
 
2669
+ declare interface LegendManualLayout {
2670
+ /** `"edge"` = `x`/`y` are fractions from top-left of chart space;
2671
+ * `"factor"` = fractions offset from the default position. */
2672
+ xMode: string;
2673
+ yMode: string;
2674
+ /** Fractions of chart space width/height. */
2675
+ x: number;
2676
+ y: number;
2677
+ w: number;
2678
+ h: number;
2679
+ }
2680
+
1267
2681
  /** DrawingML line-end (arrow head). ECMA-376 §20.1.8.3 CT_LineEndProperties.
1268
2682
  * Maps 1:1 to core's `ArrowEnd`. */
1269
2683
  export declare interface LineEnd {
@@ -1275,6 +2689,24 @@ export declare interface LineEnd {
1275
2689
  len: string;
1276
2690
  }
1277
2691
 
2692
+ /** ECMA-376 §17.6.8 `<w:lnNumType>` — line numbering for a section. Mirrors the
2693
+ * Rust `LineNumbering`. A number is drawn in the left margin of each body line
2694
+ * whose count is a multiple of `countBy`. Absent on {@link SectionProps}
2695
+ * (`lineNumbering` undefined) ⇒ line numbering off. */
2696
+ export declare interface LineNumbering {
2697
+ /** `@w:countBy` — only lines whose number is a multiple of this display a
2698
+ * number. Required (absent ⇒ the whole struct is absent per §17.6.8). */
2699
+ countBy: number;
2700
+ /** `@w:start` — the starting number after each restart. Default 1. */
2701
+ start: number;
2702
+ /** `@w:distance` in pt (twips ÷ 20) — gap between the text margin and the
2703
+ * number glyphs. Absent ⇒ implementation-defined (renderer uses a default). */
2704
+ distance?: number;
2705
+ /** `@w:restart` (§17.18.47): "newPage" (default) | "newSection" |
2706
+ * "continuous" — when the counter resets to `start`. */
2707
+ restart: string;
2708
+ }
2709
+
1278
2710
  export declare interface LineSpacing {
1279
2711
  value: number;
1280
2712
  rule: 'auto' | 'exact' | 'atLeast';
@@ -1326,6 +2758,35 @@ declare interface LoadOptions_2 {
1326
2758
  * via `@font-face` in your application CSS.
1327
2759
  */
1328
2760
  useGoogleFonts?: boolean;
2761
+ /**
2762
+ * Password for an encrypted OOXML file ([MS-OFFCRYPTO] Agile Encryption).
2763
+ *
2764
+ * Password-protected Office documents are CFB (OLE2) containers, not ZIPs.
2765
+ * When this is set and the input is Agile-encrypted, `load()` decrypts it on
2766
+ * the main thread (via WebCrypto) and parses the recovered plaintext ZIP.
2767
+ *
2768
+ * Errors (thrown as {@link import('../errors/ooxml-error').OoxmlError}):
2769
+ * - no `password` on an encrypted file → code `'encrypted'`
2770
+ * - wrong `password` → code `'invalid-password'`
2771
+ * - a non-Agile scheme (Standard / Extensible / legacy) → code
2772
+ * `'unsupported-encryption'`
2773
+ *
2774
+ * Note: Agile Encryption uses a high password-hash spin count (commonly
2775
+ * 100,000), so decryption of a protected file adds roughly a second of
2776
+ * WebCrypto work before parsing begins.
2777
+ *
2778
+ * Security notes:
2779
+ * - This value is held as an ordinary JS `string` in memory for the
2780
+ * duration of key derivation. The library does not zero it, and does
2781
+ * not wrap it in a `SecureString`-equivalent — it becomes eligible for
2782
+ * garbage collection like any other string once nothing references it,
2783
+ * but no explicit wipe is performed. It is never logged or included in
2784
+ * thrown errors.
2785
+ * - Decryption recovers the plaintext but does not verify the file's HMAC
2786
+ * data-integrity tag ([MS-OFFCRYPTO] §2.3.4.14), so ciphertext tampering
2787
+ * is not detected — see "Security & Privacy" in the README.
2788
+ */
2789
+ password?: string;
1329
2790
  /**
1330
2791
  * Override the URL the parser worker fetches the WebAssembly module from.
1331
2792
  *
@@ -1370,6 +2831,23 @@ declare interface LoadOptions_2 {
1370
2831
  math?: MathRenderer;
1371
2832
  }
1372
2833
 
2834
+ /**
2835
+ * The slice of one run a match covers: the run's index in the original `runs[]`
2836
+ * and the `[start, end)` character range within that run's own `text`. A match
2837
+ * that straddles N runs yields N of these (the first sliced from its start
2838
+ * offset to the run end, the last from 0 to its end offset, any middle run
2839
+ * whole). The viewer measures each slice against that run's font to get a pixel
2840
+ * rectangle.
2841
+ */
2842
+ declare interface MatchRunSlice {
2843
+ /** Index into the original `runs[]` handed to {@link buildTextIndex}. */
2844
+ runIndex: number;
2845
+ /** Start offset within `runs[runIndex].text` (inclusive). */
2846
+ start: number;
2847
+ /** End offset within `runs[runIndex].text` (exclusive). */
2848
+ end: number;
2849
+ }
2850
+
1373
2851
  /** Accent (`m:acc`), e.g. hat, bar, vector arrow over the base. */
1374
2852
  declare interface MathAccent {
1375
2853
  kind: 'accent';
@@ -1392,6 +2870,31 @@ declare interface MathBar {
1392
2870
  base: MathNode[];
1393
2871
  }
1394
2872
 
2873
+ /** Border-box object (`m:borderBox`, §22.1.2.11): a border/strikes around the
2874
+ * base. Absent flags ⇒ a full rectangular box. */
2875
+ declare interface MathBorderBox {
2876
+ kind: 'borderBox';
2877
+ /** §22.1.2 hide* — when true the corresponding edge is NOT drawn. */
2878
+ hideTop?: boolean;
2879
+ hideBot?: boolean;
2880
+ hideLeft?: boolean;
2881
+ hideRight?: boolean;
2882
+ /** §22.1.2 strike* — strikeBLTR = bottom-left→top-right, strikeTLBR =
2883
+ * top-left→bottom-right diagonal. */
2884
+ strikeH?: boolean;
2885
+ strikeV?: boolean;
2886
+ strikeBltr?: boolean;
2887
+ strikeTlbr?: boolean;
2888
+ base: MathNode[];
2889
+ }
2890
+
2891
+ /** Box object (`m:box`, §22.1.2.13): a logical grouping (operator emulator /
2892
+ * line-break control). Draws NO border — a transparent group around `base`. */
2893
+ declare interface MathBox {
2894
+ kind: 'box';
2895
+ base: MathNode[];
2896
+ }
2897
+
1395
2898
  declare interface MathDelimiter {
1396
2899
  kind: 'delimiter';
1397
2900
  /** opening char (default '('). */
@@ -1449,7 +2952,23 @@ declare interface MathNary {
1449
2952
  body: MathNode[];
1450
2953
  }
1451
2954
 
1452
- declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup;
2955
+ declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup | MathPhant | MathSPre | MathBox | MathBorderBox;
2956
+
2957
+ /** Phantom object (`m:phant`, §22.1.2.81): contributes the spacing of `base`
2958
+ * while optionally hiding it and/or zeroing individual dimensions. */
2959
+ declare interface MathPhant {
2960
+ kind: 'phant';
2961
+ /** §22.1.2.96 `m:show` — `false` hides the base (invisible but occupies space,
2962
+ * i.e. `<mphantom>`); `true` (default) shows it and the phant only tweaks
2963
+ * spacing. */
2964
+ show: boolean;
2965
+ /** §22.1.2 zeroWid / zeroAsc / zeroDesc — suppress width / ascent / descent so
2966
+ * the base takes no space along that axis. Omitted ⇒ false. */
2967
+ zeroWid?: boolean;
2968
+ zeroAsc?: boolean;
2969
+ zeroDesc?: boolean;
2970
+ base: MathNode[];
2971
+ }
1453
2972
 
1454
2973
  declare interface MathRadical {
1455
2974
  kind: 'radical';
@@ -1492,6 +3011,15 @@ declare interface MathScript {
1492
3011
  sub?: MathNode[];
1493
3012
  }
1494
3013
 
3014
+ /** Pre-sub-superscript object (`m:sPre`, §22.1.2.99): sub + sup to the LEFT of
3015
+ * the base (e.g. ²₁A). */
3016
+ declare interface MathSPre {
3017
+ kind: 'sPre';
3018
+ sub: MathNode[];
3019
+ sup: MathNode[];
3020
+ base: MathNode[];
3021
+ }
3022
+
1495
3023
  declare type MathStyle = 'roman' | 'italic' | 'bold' | 'boldItalic';
1496
3024
 
1497
3025
  declare interface MathSvg {
@@ -1556,6 +3084,114 @@ export declare interface NumberingInfo {
1556
3084
  picBulletHeightPt?: number;
1557
3085
  }
1558
3086
 
3087
+ /**
3088
+ * Typed error thrown by the docx / pptx / xlsx `load()` factories for failures
3089
+ * that carry a stable, programmatic {@link OoxmlErrorCode} (e.g. a
3090
+ * password-protected or legacy-binary file detected from its container magic).
3091
+ *
3092
+ * Note on workers: `instanceof OoxmlError` does not survive a structured-clone
3093
+ * across the worker boundary. Detection that needs a typed error is therefore
3094
+ * done on the main thread (before the worker is involved) so a genuine
3095
+ * `OoxmlError` instance is thrown to the caller. Errors that must cross the
3096
+ * worker boundary should carry the `code` string and be reconstructed on the
3097
+ * main side.
3098
+ */
3099
+ export declare class OoxmlError extends Error {
3100
+ readonly code: OoxmlErrorCode;
3101
+ constructor(code: OoxmlErrorCode, message: string);
3102
+ }
3103
+
3104
+ /**
3105
+ * Machine-readable code for a typed load-time failure.
3106
+ *
3107
+ * The container-level failures the `load()` factories detect on the main thread
3108
+ * before handing bytes to the parser worker (see `sniffCfb` / `decryptOoxml`).
3109
+ * This is the seed of the broader typed-error surface tracked as PD4 (OoxmlError
3110
+ * typed errors). Add codes here rather than throwing bare `Error(string)`, so
3111
+ * callers can `switch` on `err.code` instead of matching message text.
3112
+ *
3113
+ * - `'encrypted'` — password-protected, but no `password` was
3114
+ * supplied (pass `LoadOptions.password` to decrypt).
3115
+ * - `'invalid-password'` — a `password` was supplied but did not match.
3116
+ * - `'unsupported-encryption'`— encrypted with a scheme other than Agile
3117
+ * (Standard / Extensible / a legacy binary encryptor), which this library
3118
+ * cannot decrypt (PD8 implements Agile only).
3119
+ * - `'legacy-binary-format'` — a raw .doc / .xls / .ppt (not OOXML).
3120
+ * - `'not-ooxml'` — a CFB of an unrecognised kind, or otherwise
3121
+ * not an OOXML ZIP.
3122
+ */
3123
+ export declare type OoxmlErrorCode = 'encrypted' | 'invalid-password' | 'unsupported-encryption' | 'legacy-binary-format' | 'not-ooxml';
3124
+
3125
+ /**
3126
+ * The default action a viewer takes for an **external** hyperlink click when
3127
+ * the integrator supplies no `onHyperlinkClick` handler: sanitise the URL and,
3128
+ * if allowed, open it in a new tab with `noopener,noreferrer` so the opened page
3129
+ * gets no `window.opener` handle back into this document. A blocked scheme is a
3130
+ * silent no-op (returns `false`) — the click does nothing rather than navigate
3131
+ * somewhere dangerous.
3132
+ *
3133
+ * Internal targets are intentionally NOT handled here: the in-document jump
3134
+ * (page / slide / cell) is format-specific and lives in each viewer.
3135
+ *
3136
+ * Split out (not inlined in three viewers) so the "open in new tab, drop opener,
3137
+ * refuse unsafe schemes" policy is defined once. `win` is injected for tests;
3138
+ * defaults to the ambient `window`.
3139
+ *
3140
+ * @returns `true` if navigation was initiated, `false` if the URL was blocked.
3141
+ */
3142
+ export declare function openExternalHyperlink(url: string, allowed?: readonly string[], win?: Pick<Window, 'open'> | undefined): boolean;
3143
+
3144
+ /** ECMA-376 §17.18.4 CT_Border for one edge of `<w:pgBorders>`. Mirrors the Rust
3145
+ * `PageBorderEdge`. Same shape as a paragraph border edge. */
3146
+ export declare interface PageBorderEdge {
3147
+ /** `@w:val` — ST_Border line style ("single" | "double" | "dashed" | …). */
3148
+ style: string;
3149
+ /** `@w:color` hex 6, or absent for "auto" (renderer defaults to black). */
3150
+ color?: string;
3151
+ /** `@w:sz` in pt (eighths of a point ÷ 8). */
3152
+ width: number;
3153
+ /** `@w:space` in pt — a POINT measure (§17.18.68, 0–31) for page borders, NOT
3154
+ * twips — the inset from the `offsetFrom` reference. */
3155
+ space: number;
3156
+ }
3157
+
3158
+ /** ECMA-376 §17.6.10 `<w:pgBorders>` — page borders drawn around each page of a
3159
+ * section. Mirrors the Rust `PageBorders`. Each edge is a CT_Border (§17.18.4);
3160
+ * the container carries the placement globals. Absent on {@link SectionProps}
3161
+ * (`pageBorders` undefined) ⇒ no page border (the common case). Art borders
3162
+ * (§17.18.2 decorative-image styles) are unsupported — the renderer draws only
3163
+ * the standard line styles (single/double/dashed/dotted/thick/…). */
3164
+ export declare interface PageBorders {
3165
+ /** `@w:offsetFrom` (§17.18.63): "page" ⇒ each edge's `space` is from the PAGE
3166
+ * edge; "text" (the default) ⇒ from the text margin. */
3167
+ offsetFrom: string;
3168
+ /** `@w:display` (§17.18.62): "allPages" (default) | "firstPage" |
3169
+ * "notFirstPage" — which physical pages of the section show the border. */
3170
+ display: string;
3171
+ /** `@w:zOrder` (§17.18.64): "front" (default; over text) | "back" (under). */
3172
+ zOrder: string;
3173
+ top?: PageBorderEdge;
3174
+ bottom?: PageBorderEdge;
3175
+ left?: PageBorderEdge;
3176
+ right?: PageBorderEdge;
3177
+ }
3178
+
3179
+ /** ECMA-376 §17.6.12 `<w:pgNumType>` — a section's page-numbering settings.
3180
+ * Mirrors the Rust `PageNumType`. Only the two attributes that change the
3181
+ * DISPLAYED page number are carried:
3182
+ * - `start` — the number shown on the FIRST page of the section (§17.6.12);
3183
+ * absent ⇒ numbering continues from the previous section's highest number.
3184
+ * Kept as a possibly-zero / possibly-negative integer (Word writes `start="0"`).
3185
+ * - `fmt` — the ST_NumberFormat (§17.18.59) for the section's page numbers
3186
+ * (decimal / upperRoman / lowerLetter / …); absent ⇒ decimal.
3187
+ * `chapStyle`/`chapSep` (chapter-prefixed numbering) are out of scope for this
3188
+ * pass and never surfaced. Field names match the Rust `PageNumType` serialization
3189
+ * (`start`, `fmt`). */
3190
+ export declare interface PageNumType {
3191
+ start?: number;
3192
+ fmt?: string;
3193
+ }
3194
+
1559
3195
  export declare interface ParaBorderEdge {
1560
3196
  style: string;
1561
3197
  color: string | null;
@@ -1599,6 +3235,22 @@ export declare type PathCmd = {
1599
3235
  cmd: 'close';
1600
3236
  };
1601
3237
 
3238
+ /** ECMA-376 §17.3.3.23 `<w:ptab>` — an absolute-position tab. Advances to a
3239
+ * position derived from {@link PTabRun.alignment} and {@link PTabRun.relativeTo},
3240
+ * independent of the paragraph's custom tab stops / default-tab interval. */
3241
+ export declare interface PTabRun {
3242
+ /** ST_PTabAlignment (§17.18.71): where on the line the tab lands, and how the
3243
+ * following text aligns to it. */
3244
+ alignment: 'left' | 'center' | 'right';
3245
+ /** ST_PTabRelativeTo (§17.18.73): the base the position is measured from —
3246
+ * the text margins or the paragraph indents. */
3247
+ relativeTo: 'margin' | 'indent';
3248
+ /** ST_PTabLeader (§17.18.72): the character repeated to fill the tab gap. */
3249
+ leader: 'none' | 'dot' | 'hyphen' | 'underscore' | 'middleDot';
3250
+ /** Resolved run font size (pt) — matches the surrounding text's leader/gap. */
3251
+ fontSize: number;
3252
+ }
3253
+
1602
3254
  export declare interface RenderPageOptions {
1603
3255
  /** Canvas CSS width in px; height is auto-computed from page aspect ratio.
1604
3256
  * Applies per CALL — pages of different physical widths (per-section pgSz,
@@ -1608,7 +3260,10 @@ export declare interface RenderPageOptions {
1608
3260
  width?: number;
1609
3261
  dpr?: number;
1610
3262
  defaultTextColor?: string;
1611
- /** Called for each rendered text segment. Used to build a transparent text selection overlay. */
3263
+ /** Called for each rendered text segment. Used to build a transparent text
3264
+ * selection overlay. On a vertical (§17.6.20 tbRl) page `x`/`y` are the
3265
+ * PHYSICAL top-left and `transform` is the CSS rotation the overlay span
3266
+ * applies about its top-left; absent for horizontal pages. */
1612
3267
  onTextRun?: (run: {
1613
3268
  text: string;
1614
3269
  x: number;
@@ -1617,12 +3272,27 @@ export declare interface RenderPageOptions {
1617
3272
  h: number;
1618
3273
  fontSize: number;
1619
3274
  font: string;
3275
+ transform?: string;
1620
3276
  }) => void;
1621
3277
  /** Default `true`. When false, ECMA-376 §17.13.5 track-changes runs render
1622
3278
  * in their normal style (no author colour, no underline / strikethrough)
1623
3279
  * — equivalent to Word's "Final / No Markup" view. */
1624
3280
  showTrackChanges?: boolean;
1625
- }
3281
+ /** ECMA-376 §17.16.5.16 DATE / §17.16.5.72 TIME — the "current" instant a
3282
+ * DATE/TIME field formats through its `\@` date picture (§17.16.4.1). A `Date`
3283
+ * or epoch-ms number. Default = the real current time at render. Set a fixed
3284
+ * value for deterministic / reproducible DATE/TIME field output. */
3285
+ currentDate?: Date | number;
3286
+ }
3287
+
3288
+ /** IX6 — options for {@link DocxDocument.renderPageToBitmap}: the serializable
3289
+ * render knobs plus an OPTIONAL `onTextRun`. The callback stays main-thread (it
3290
+ * never crosses the wire); in worker mode the proxy invokes it with the runs
3291
+ * the worker shipped back beside the bitmap, so a caller gets the selection /
3292
+ * find geometry on the same path in both modes. */
3293
+ export declare type RenderPageToBitmapOptions = WireRenderPageOptions & {
3294
+ onTextRun?: (run: DocxTextRunInfo) => void;
3295
+ };
1626
3296
 
1627
3297
  export declare interface RubyAnnotation {
1628
3298
  text: string;
@@ -1639,6 +3309,50 @@ export declare interface RunRevision {
1639
3309
  date?: string;
1640
3310
  }
1641
3311
 
3312
+ /**
3313
+ * A secondary value axis (combo charts). Mirrors the primary value-axis
3314
+ * properties but lives in its own object so the flat primary-axis fields stay
3315
+ * untouched. Parsed from the right-hand `<c:valAx>` (`axPos="r"`,
3316
+ * `<c:crosses val="max">`).
3317
+ */
3318
+ declare interface SecondaryValueAxis {
3319
+ /** `<c:scaling><c:min val>`. null = derive from the series data. */
3320
+ min: number | null;
3321
+ /** `<c:scaling><c:max val>`. null = derive from the series data. */
3322
+ max: number | null;
3323
+ /** `<c:title>` plain text. null = no title. */
3324
+ title: string | null;
3325
+ /** `<c:delete val="1"/>` — hide labels/ticks entirely. */
3326
+ hidden: boolean;
3327
+ /** `<c:numFmt formatCode>` for tick labels. */
3328
+ formatCode?: string | null;
3329
+ /** `<c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
3330
+ fontColor?: string | null;
3331
+ /** `<c:txPr>` tick-label font size (hpt). */
3332
+ fontSizeHpt?: number | null;
3333
+ /** `<c:spPr><a:ln><a:solidFill>` axis-line color (hex without '#'). */
3334
+ lineColor?: string | null;
3335
+ /** `<c:spPr><a:ln w>` axis-line width in EMU. */
3336
+ lineWidthEmu?: number | null;
3337
+ /** `<c:spPr><a:ln><a:noFill>` — hide just the axis rule. */
3338
+ lineHidden: boolean;
3339
+ /** `<c:majorTickMark>` — "cross" (default) | "out" | "in" | "none". */
3340
+ majorTickMark: string;
3341
+ /**
3342
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between
3343
+ * major ticks/gridlines on THIS secondary axis, overriding the Excel-style
3344
+ * auto "nice" step. null/undefined ⇒ auto step (byte-stable). Symmetric with
3345
+ * {@link ChartModel.valAxisMajorUnit} on the primary axis.
3346
+ */
3347
+ majorUnit?: number | null;
3348
+ /** `<c:title>` run-prop font size (hpt). */
3349
+ titleFontSizeHpt?: number | null;
3350
+ /** `<c:title>` run-prop bold flag. */
3351
+ titleFontBold?: boolean | null;
3352
+ /** `<c:title>` run-prop color (hex without '#'). */
3353
+ titleFontColor?: string | null;
3354
+ }
3355
+
1642
3356
  /** ECMA-376 §17.6.13 `<w:pgSz>` + §17.6.11 `<w:pgMar>` — a section's page
1643
3357
  * geometry: page size + margins + header/footer distances (pt). Mirrors the Rust
1644
3358
  * `SectionGeom`. Carried on a {@link BodyElement} `sectionBreak` arm (`geom`) so a
@@ -1683,6 +3397,17 @@ export declare interface SectionProps {
1683
3397
  * spec default). Non-final sections carry their start type on their own
1684
3398
  * SectionBreak marker. */
1685
3399
  sectionStart?: string | null;
3400
+ /** ECMA-376 §17.6.20 `<w:textDirection w:val>` — the section's flow direction,
3401
+ * using the TRANSITIONAL ST_TextDirection enum Word writes (Part 4 §14.11.7:
3402
+ * `lrTb`|`tbRl`|`btLr`|`lrTbV`|`tbLrV`|`tbRlV`), NOT the Part 1 §17.18.93
3403
+ * Strict set. Absent / `null` ⇒ "lrTb" (horizontal, left→right / top→bottom,
3404
+ * the default). `"tbRl"` = vertical Japanese (glyphs stack top→bottom, lines
3405
+ * advance right→left); the renderer (see `isVerticalSection`) lays the page out
3406
+ * horizontally and rotates it +90° at paint for the vertical values
3407
+ * (`tbRl`/`tbRlV`/`tbLrV`), keeping CJK glyphs upright and Latin sideways. Only
3408
+ * a non-default value is emitted by the parser, so horizontal documents keep
3409
+ * byte-identical rendering. */
3410
+ textDirection?: string | null;
1686
3411
  /** ECMA-376 §17.6.5 w:docGrid/@w:type — "default" | "lines" | "linesAndChars" | "snapToChars". */
1687
3412
  docGridType?: string | null;
1688
3413
  /** ECMA-376 §17.6.5 w:docGrid/@w:linePitch in pt. When docGridType is "lines" or
@@ -1701,6 +3426,23 @@ export declare interface SectionProps {
1701
3426
  * body text flows top-to-bottom through `count` columns (newspaper fill);
1702
3427
  * see {@link computeColumns}. */
1703
3428
  columns?: ColumnsSpec | null;
3429
+ /** ECMA-376 §17.6.12 `<w:pgNumType>` — the body (final) section's page-numbering
3430
+ * settings (start / fmt). `null`/absent ⇒ numbering continues; decimal. The
3431
+ * renderer resolves the displayed page number per physical page from this plus
3432
+ * the per-section `SectionBreak.pageNumType` markers. */
3433
+ pageNumType?: PageNumType | null;
3434
+ /** ECMA-376 §17.6.10 `<w:pgBorders>` — page borders for this section.
3435
+ * `null`/absent ⇒ no page border (the common case). */
3436
+ pageBorders?: PageBorders | null;
3437
+ /** ECMA-376 §17.6.8 `<w:lnNumType>` — line numbering for this section.
3438
+ * `null`/absent ⇒ line numbering off. */
3439
+ lineNumbering?: LineNumbering | null;
3440
+ /** ECMA-376 §17.6.23 `<w:vAlign w:val>` — body vertical alignment between the
3441
+ * top/bottom margins ("top" | "center" | "both" | "bottom"). `null`/absent ⇒
3442
+ * "top" (body flows from the top margin unchanged). "both" (vertical
3443
+ * justification) is parsed but rendered as "top" until distribution is
3444
+ * implemented (see renderer note). */
3445
+ vAlign?: string | null;
1704
3446
  }
1705
3447
 
1706
3448
  declare type ShapeFill = {
@@ -1793,6 +3535,15 @@ export declare interface ShapeRun {
1793
3535
  wrapSide?: string | null;
1794
3536
  /** Text rendered INSIDE the shape's bounding box (`<wps:txbx><w:txbxContent>`). */
1795
3537
  textBlocks?: ShapeText[];
3538
+ /** ECMA-376 §20.1.4.1.17 `<wps:style><a:fontRef>` — the shape's DEFAULT text
3539
+ * color (hex, no `#`). A text-box run ({@link ShapeTextRun}) with no explicit
3540
+ * {@link ShapeTextRun.color} inherits this before falling back to the
3541
+ * document/theme default (black); an explicit run color still wins. This is
3542
+ * the color axis of the fontRef only — the `@idx` (major/minor/none) font-face
3543
+ * selection is out of scope (fonts resolve via rFonts/docDefaults). Mirrors
3544
+ * pptx's per-shape default text color from the placeholder fontRef. Absent ⇒
3545
+ * no shape default (the run color or black applies). */
3546
+ defaultTextColor?: string | null;
1796
3547
  /** "t" | "ctr" | "b" — vertical anchor for the shape's text body (`<wps:bodyPr @anchor>`). */
1797
3548
  textAnchor?: string | null;
1798
3549
  /** ECMA-376 §21.1.2.1.1 auto-fit mode from `<wps:bodyPr>`, normalized to the
@@ -1805,6 +3556,16 @@ export declare interface ShapeRun {
1805
3556
  textInsetT?: number;
1806
3557
  textInsetR?: number;
1807
3558
  textInsetB?: number;
3559
+ /** ECMA-376 Part 4 §19.1.2.23 `<v:textpath>` — WordArt text laid on the
3560
+ * shape path (a text watermark). When set the renderer draws this string,
3561
+ * scaled to fill the box (`fitshape`), rotated by {@link ShapeRun.rotation},
3562
+ * filled with {@link ShapeRun.fill} at {@link ShapeRun.fillOpacity} alpha —
3563
+ * INSTEAD of a fill/stroke panel + body text. */
3564
+ textPath?: TextPath | null;
3565
+ /** ECMA-376 Part 4 §19.1.2.5 `<v:fill opacity>` — fill alpha in `[0, 1]`
3566
+ * (default 1 = opaque). Used with {@link ShapeRun.textPath} to draw the
3567
+ * watermark semi-transparently. Absent ⇒ opaque. */
3568
+ fillOpacity?: number | null;
1808
3569
  }
1809
3570
 
1810
3571
  export declare interface ShapeText {
@@ -1940,7 +3701,107 @@ export declare interface TblpPr {
1940
3701
  tblpYSpec?: 'inline' | 'top' | 'center' | 'bottom' | 'inside' | 'outside' | string;
1941
3702
  }
1942
3703
 
3704
+ /** ECMA-376 Part 4 §19.1.2.23 `<v:textpath>` — a WordArt vector text path,
3705
+ * emitted by Word for text watermarks (the `PowerPlusWaterMarkObject` shape).
3706
+ * The text is stretched to fit the shape box (`fitshape`, the WordArt
3707
+ * `#_x0000_t136` shapetype default), so its drawn size derives from the shape
3708
+ * geometry rather than the nominal `font-size` in the textpath style. */
3709
+ export declare interface TextPath {
3710
+ /** The `string` attribute — the watermark text (e.g. "DRAFT"). */
3711
+ string: string;
3712
+ /** `font-family` from the textpath style (quotes stripped). */
3713
+ fontFamily?: string | null;
3714
+ bold?: boolean;
3715
+ italic?: boolean;
3716
+ }
3717
+
1943
3718
  /** Serializable subset of RenderPageOptions (callbacks cannot cross the wire). */
1944
3719
  export declare type WireRenderPageOptions = Omit<RenderPageOptions, 'onTextRun'>;
1945
3720
 
3721
+ /**
3722
+ * IX9 — the shared zoom API contract for every viewer (DocxViewer, PptxViewer,
3723
+ * DocxScrollViewer, PptxScrollViewer, XlsxViewer).
3724
+ *
3725
+ * This module owns ONLY the pure, DOM-free pieces of the contract: the type
3726
+ * ({@link ZoomableViewer}), the discrete zoom-step ladder ({@link nextZoomStep} /
3727
+ * {@link prevZoomStep}), the fit-to-content scale math ({@link fitScale}), and the
3728
+ * range clamp ({@link clampScale}). Each viewer implements the interface with its
3729
+ * own scale field and re-render path; this keeps ONE definition of "what a zoom
3730
+ * factor means" and "what the +/- steps are" across all five, so a host can drive
3731
+ * any viewer through the same six calls without special-casing the format.
3732
+ *
3733
+ * SCALE SEMANTICS (the contract): a scale of `1` means 100% — the content at its
3734
+ * natural size (a docx page at `widthPt × PT_TO_PX`, a pptx slide at
3735
+ * `slideWidth / EMU_PER_PX`, an xlsx grid at `cellScale` 1). `getScale()` and
3736
+ * `setScale(n)` speak this user-facing factor for EVERY viewer.
3737
+ *
3738
+ * KNOWN FAMILY DIFFERENCE — the INITIAL scale right after load (deliberate,
3739
+ * documented rather than papered over): the single-canvas viewers (DocxViewer /
3740
+ * PptxViewer) and XlsxViewer start at `1` (or the effective factor implied by an
3741
+ * explicit `width` option); the continuous-scroll viewers (DocxScrollViewer /
3742
+ * PptxScrollViewer) AUTO-FIT to the container on first layout, so their
3743
+ * `getScale()` right after load reports the fit-to-width BASE factor (≠ 1 unless
3744
+ * the container happens to match the natural width). The unit is identical — only
3745
+ * the starting point differs, because fit-to-width is the natural resting state
3746
+ * of a continuous document viewer.
3747
+ *
3748
+ * PRE-LOAD `setScale` (family-unified, IX9 F1): a `setScale` called before the
3749
+ * content is loaded / before the layout is established is LATCHED — never
3750
+ * silently dropped — and applied once the viewer establishes its scale (the
3751
+ * single-canvas viewers honour it on the first render; the scroll viewers apply
3752
+ * it right after the base fit establishes, firing `onScaleChange` at application
3753
+ * time). `getScale()` reports the latched factor while it is pending.
3754
+ *
3755
+ * API SHAPE (idiomatic default — the integrator MAY veto; see the IX9 PR): a
3756
+ * six-method surface plus one change notification (`onScaleChange`). Deliberately
3757
+ * NO new UI here — the contract is API only (design decision IX9 §4). Touch-pinch
3758
+ * (IX8) is out of scope.
3759
+ */
3760
+ /**
3761
+ * The zoom contract every viewer satisfies. All scales are the user-facing factor
3762
+ * where `1` = 100% (see the module note). `fitWidth`/`fitPage` are async because a
3763
+ * fit re-renders at the new scale; the getters/steppers resolve synchronously.
3764
+ */
3765
+ declare interface ZoomableViewer {
3766
+ /** The current zoom factor (`1` = 100%). Never throws — returns the default
3767
+ * (`1`) before anything is loaded, or the latched pending factor when a
3768
+ * pre-load `setScale` is waiting to be applied (see the module note). */
3769
+ getScale(): number;
3770
+ /** Set the absolute zoom factor (`1` = 100%), clamped to the viewer's
3771
+ * `[zoomMin, zoomMax]`. Re-renders at the new scale and fires `onScaleChange`
3772
+ * when the clamped value actually changes. Called BEFORE the content is
3773
+ * loaded / the layout is established, the (clamped) factor is LATCHED and
3774
+ * applied once the viewer establishes its scale — family-unified semantics
3775
+ * (IX9 F1): never silently dropped by any viewer. */
3776
+ setScale(scale: number): void | Promise<void>;
3777
+ /** Step up to the next larger rung of the shared zoom ladder (25 %→400 %),
3778
+ * clamped to `zoomMax`. Equivalent to `setScale(nextZoomStep(getScale()))`. */
3779
+ zoomIn(): void | Promise<void>;
3780
+ /** Step down to the next smaller ladder rung, clamped to `zoomMin`. */
3781
+ zoomOut(): void | Promise<void>;
3782
+ /** Fit the content's WIDTH to the container (the common "fit width" / "fit
3783
+ * page width" verb). Sets the scale so one page/slide/sheet-column-run spans
3784
+ * the available width, then re-renders. Resolves once the fit render settles.
3785
+ *
3786
+ * PERSISTENCE is viewer-implementation-dependent (deliberate, by family): the
3787
+ * single-canvas viewers (DocxViewer / PptxViewer) and XlsxViewer apply the fit
3788
+ * ONE-SHOT — they observe no container resizes, so a later resize does NOT
3789
+ * re-fit (call `fitWidth()` again after a layout change). The continuous-
3790
+ * scroll viewers (DocxScrollViewer / PptxScrollViewer) re-fit their width-fit
3791
+ * base on every container resize, so a `fitWidth()` there effectively
3792
+ * PERSISTS across resizes (the resize re-fit preserves the width-fit state). */
3793
+ fitWidth(): void | Promise<void>;
3794
+ /** Fit the WHOLE content (width AND height) inside the container, so an entire
3795
+ * page/slide is visible without scrolling. Sets the scale to the smaller of the
3796
+ * width- and height-fit factors, then re-renders.
3797
+ *
3798
+ * PERSISTENCE is viewer-implementation-dependent, and — unlike `fitWidth` —
3799
+ * a page fit does NOT persist across container resizes on ANY viewer: the
3800
+ * single-canvas viewers and XlsxViewer observe no resizes at all (one-shot),
3801
+ * and the continuous-scroll viewers' resize handler re-applies the WIDTH fit
3802
+ * (preserving the zoom multiplier), not the page fit. Re-invoke `fitPage()`
3803
+ * after a layout change to re-fit. */
3804
+ fitPage(): void | Promise<void>;
3805
+ }
3806
+
1946
3807
  export { }