@silurus/ooxml 0.70.2 → 0.72.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,19 @@ 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;
72
+ /** ECMA-376 §17.6.20 `<w:textDirection w:val>` — this ENDING section's
73
+ * flow direction (TRANSITIONAL ST_TextDirection, same enum and semantics
74
+ * as {@link SectionProps.textDirection}), so a vertical (tbRl/btLr)
75
+ * non-final section can coexist with a horizontal final section (issue
76
+ * #1000). Absent ⇒ horizontal ("lrTb" is collapsed by the parser).
77
+ * Carried separately from `geom` (like `pageNumType`) because a section
78
+ * may inherit its page geometry yet still set its own flow direction. */
79
+ textDirection?: string | null;
67
80
  };
68
81
 
69
82
  export declare interface BorderSpec {
@@ -72,6 +85,27 @@ export declare interface BorderSpec {
72
85
  style: string;
73
86
  }
74
87
 
88
+ /**
89
+ * Populate a highlight overlay layer with one box per matched run-slice.
90
+ *
91
+ * Every box is positioned as a PERCENTAGE of `cssWidth`/`cssHeight`, and the
92
+ * container's own size is left untouched (`width:100%;height:100%` from the
93
+ * caller), so the highlights track the canvas's ACTUAL rendered box even when a
94
+ * consumer scales the canvas down with external CSS — mirroring
95
+ * {@link buildDocxTextLayer}.
96
+ *
97
+ * @param layer the overlay div (cleared here; sized `100%` by the caller).
98
+ * @param runs the page's runs (same array the page was rendered/text-layered from).
99
+ * @param matches the page's matches (run-slices + active flag).
100
+ * @param cssWidth the page's intended CSS width (px, number) — the x-axis % denominator.
101
+ * @param cssHeight the page's intended CSS height (px, number) — the y-axis % denominator.
102
+ * @param measureForFont returns a width-measurer primed with a run's `font`
103
+ * (the viewer closes over a canvas 2d context). Kept as a
104
+ * factory so the font is set once per run, not per glyph.
105
+ * @param colors optional colour overrides.
106
+ */
107
+ export declare function buildDocxHighlightLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], matches: DocxHighlightMatch[], cssWidth: number, cssHeight: number, measureForFont: (font: string) => (s: string) => number, colors?: DocxHighlightColors): void;
108
+
75
109
  /**
76
110
  * Build the transparent text-selection overlay for a rendered docx page: one
77
111
  * absolutely-positioned, color-transparent `<span>` per {@link DocxTextRunInfo}
@@ -79,16 +113,45 @@ export declare interface BorderSpec {
79
113
  * lands on the drawn glyphs. Extracted verbatim from `DocxViewer._buildTextLayer`
80
114
  * so both the pager (DocxViewer) and the continuous-scroll viewer (DocxScrollViewer)
81
115
  * 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.
116
+ * overlay (design §10). IX6 usable in BOTH render modes: worker mode collects
117
+ * the same `DocxTextRunInfo[]` off-thread and ships it back beside the bitmap, so
118
+ * the overlay is built from identical geometry regardless of thread.
119
+ *
120
+ * Every span is positioned as a PERCENTAGE of `cssWidth`/`cssHeight` (the page's
121
+ * intended CSS-px box), never literal px, and the container's own width/height are
122
+ * left untouched (the caller sizes it `width:100%;height:100%`). This lets the
123
+ * overlay track the canvas's ACTUAL rendered box even when a consumer scales the
124
+ * canvas down with external CSS (`width:100%!important; height:auto`): the
125
+ * `display:inline-block` wrapper shrinks with the canvas, the `100%` container
126
+ * follows, and every `%`-placed span scales with it, so nothing overflows the
127
+ * wrapper into an ancestor's scroll area.
84
128
  *
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.
129
+ * @param layer the overlay div (sized `width:100%;height:100%` by the caller).
130
+ * @param runs per-run geometry from `renderPage({ onTextRun })`.
131
+ * @param cssWidth the page's intended CSS width (px, number) — the %
132
+ * denominator for the x axis.
133
+ * @param cssHeight the page's intended CSS height (px, number) — the %
134
+ * denominator for the y axis.
135
+ * @param onHyperlinkClick IX1 — invoked when a run carrying a resolved
136
+ * {@link HyperlinkTarget} is clicked. A hyperlink run's
137
+ * span keeps its transparent glyphs (the visible link
138
+ * colour/underline is already drawn on the canvas) but
139
+ * gains `cursor:pointer`, a `title` tooltip (the URL or
140
+ * bookmark ref) and this click handler. A plain
141
+ * `<span>` — not an `<a href>` — is used deliberately so
142
+ * the browser's own navigation can never bypass the
143
+ * caller's URL sanitisation. When omitted, link runs are
144
+ * rendered exactly like plain runs (no click affordance).
145
+ * @param measureForFont optional width-measurer factory (primed with a run's
146
+ * `font`), used ONLY to clamp a §17.3.2.10 縦中横
147
+ * (eastAsianVert) span to its drawn one-em cell (#836):
148
+ * the span composes a `scaleX(run.w / naturalWidth)` so
149
+ * its selection extent matches the compressed glyphs
150
+ * instead of the run's natural ~2× width. When omitted,
151
+ * a 縦中横 span keeps the bare rotate (no regression for
152
+ * callers that do not thread a measurer).
90
153
  */
91
- export declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], canvasCssWidth: string, canvasCssHeight: string): void;
154
+ export declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], cssWidth: number, cssHeight: number, onHyperlinkClick?: (target: HyperlinkTarget) => void, measureForFont?: (font: string) => (s: string) => number): void;
92
155
 
93
156
  export declare interface CellBorders {
94
157
  top: BorderSpec | null;
@@ -112,6 +175,802 @@ export declare type CellElement = {
112
175
  type: 'table';
113
176
  } & DocTable;
114
177
 
178
+ declare interface ChartDataLabelOverride {
179
+ idx: number;
180
+ /** Empty string = label deleted (skip drawing). */
181
+ text: string;
182
+ /** "l"|"r"|"t"|"b"|"ctr"|"outEnd"|"bestFit". undefined = inherit. */
183
+ position?: string;
184
+ fontColor?: string;
185
+ fontSizeHpt?: number;
186
+ /** `<a:defRPr b="1">` inside the per-idx rich text. */
187
+ fontBold?: boolean;
188
+ /** Per-point callout box (`<c:dLbl><c:spPr>`, ECMA-376 §21.2.2.47/§21.2.2.197):
189
+ * overrides the series-default box for this one slice. */
190
+ labelBox?: ChartLabelBox;
191
+ /**
192
+ * Per-point label-content flags (`<c:dLbl>` §21.2.2.47 carries the same
193
+ * show-flag group as the series `<c:dLbls>` §21.2.2.49: §21.2.2.189
194
+ * `<c:showVal>`, §21.2.2.177 `<c:showCatName>`, §21.2.2.180 `<c:showSerName>`,
195
+ * §21.2.2.187 `<c:showPercent>`). When present they OVERRIDE the series-level
196
+ * defaults for that one point (e.g. sample-14 slide-7's pie sets
197
+ * `showCatName=0 showPercent=1` per slice while the series default is
198
+ * `showCatName=1`, so each label is percent only). undefined = inherit the
199
+ * series default for that flag.
200
+ */
201
+ showVal?: boolean;
202
+ showCatName?: boolean;
203
+ showSerName?: boolean;
204
+ showPercent?: boolean;
205
+ /**
206
+ * `<c:dLbl><c:delete val="1"/>` (ECMA-376 §21.2.2.43) — the point's label is
207
+ * removed. Distinguishes a genuine delete from a `<c:dLbl>` that only carries
208
+ * style / flag overrides with no `<c:tx>` (both otherwise present as
209
+ * `text === ''`). true = skip the label; undefined/absent = not deleted.
210
+ */
211
+ deleted?: boolean;
212
+ }
213
+
214
+ declare interface ChartDataPointOverride {
215
+ idx: number;
216
+ /** Resolved fill hex (no `#`). */
217
+ color?: string;
218
+ markerSymbol?: string;
219
+ markerSize?: number;
220
+ markerFill?: string;
221
+ markerLine?: string;
222
+ /**
223
+ * `<c:dPt><c:explosion val>` (ECMA-376 §21.2.2.61) — the amount this
224
+ * pie/doughnut slice is moved out from the center. The schema type is
225
+ * `CT_UnsignedInt` (unbounded `xsd:unsignedInt`); the spec text only says
226
+ * "the amount the data point shall be moved from the center of the pie"
227
+ * and does not itself define units or a 0–100 range. We treat it as a
228
+ * de-facto percentage of the outer radius (0–100 typical), matching
229
+ * Office's UI (the Point Explosion slider caps at 100%) rather than a
230
+ * spec-mandated bound. undefined/absent = 0 (no explosion, flush with the
231
+ * ring). Only consulted by the pie/doughnut renderer.
232
+ */
233
+ explosion?: number;
234
+ }
235
+
236
+ declare interface ChartErrBars {
237
+ /** "x" | "y". */
238
+ dir: string;
239
+ /** "plus" | "minus" | "both". */
240
+ barType: string;
241
+ plus: (number | null)[];
242
+ minus: (number | null)[];
243
+ noEndCap: boolean;
244
+ /** Resolved hex (no `#`). */
245
+ color?: string;
246
+ lineWidthEmu?: number;
247
+ /** "solid"|"dash"|"dot"|"dashDot"|... */
248
+ dash?: string;
249
+ }
250
+
251
+ /**
252
+ * One box-and-whisker series (chartEx `boxWhisker`, MS 2014 chartex ext). Each
253
+ * `<cx:series>` references its own raw sample points via `<cx:dataId>`; the
254
+ * parser groups them by category and threads the `<cx:layoutPr>` flags. The
255
+ * renderer derives the statistics.
256
+ */
257
+ declare interface ChartexBoxSeries {
258
+ /** Series display name (`<cx:tx><cx:v>`). */
259
+ name: string;
260
+ /** Fill (hex, no '#') — theme accent cycled by series index. null = fall
261
+ * back to the renderer palette. */
262
+ color?: string | null;
263
+ /** Raw sample values grouped by category (outer = category index parallel to
264
+ * {@link ChartexBoxWhisker.categories}, inner = the points in that group). */
265
+ valuesByCategory: number[][];
266
+ /** `<cx:visibility meanMarker>` — draw the mean `×`. */
267
+ meanMarker: boolean;
268
+ /** `<cx:visibility meanLine>` — draw a mean connector line across categories. */
269
+ meanLine: boolean;
270
+ /** `<cx:visibility outliers>` — draw outlier points. */
271
+ showOutliers: boolean;
272
+ /** `<cx:visibility nonoutliers>` — draw the interior (non-outlier) sample
273
+ * points as jittered dots on top of the box. Flag parsed; interior-dot
274
+ * rendering is pending a fixture that enables it (every sample-24 series
275
+ * ships `nonoutliers="0"`, so there is nothing to verify against yet). */
276
+ showNonoutliers: boolean;
277
+ /** `<cx:statistics quartileMethod>` — "exclusive" (Excel default) | "inclusive". */
278
+ quartileMethod: string;
279
+ }
280
+
281
+ /** A chartEx box-and-whisker chart: unique categories + one series per column. */
282
+ declare interface ChartexBoxWhisker {
283
+ /** Unique category labels in first-seen order. */
284
+ categories: string[];
285
+ /** One entry per `<cx:series>`. */
286
+ series: ChartexBoxSeries[];
287
+ }
288
+
289
+ /** A chartEx sunburst: the flat rows the renderer folds into a ring tree. */
290
+ declare interface ChartexSunburst {
291
+ rows: ChartexSunburstRow[];
292
+ }
293
+
294
+ /**
295
+ * One row of a chartEx `sunburst`: the branch→…→leaf label chain (empty
296
+ * trailing segments trimmed) and its size value.
297
+ */
298
+ declare interface ChartexSunburstRow {
299
+ /** Label chain root→leaf. */
300
+ path: string[];
301
+ /** `<cx:numDim type="size">` value attaching to the deepest node in `path`. */
302
+ size: number;
303
+ }
304
+
305
+ /** Callout-box style for a pie/doughnut data label — the white (or themed)
306
+ * rounded rectangle with a thin border Word draws around a `bestFit` label
307
+ * placed outside its slice. From the label's `<c:spPr>` (§21.2.2.197). All
308
+ * fields optional: absent → transparent / unbordered. Mirror of Rust
309
+ * `ChartLabelBox`. */
310
+ declare interface ChartLabelBox {
311
+ /** `<a:solidFill>` resolved hex (no `#`). Box background. */
312
+ fill?: string;
313
+ /** `<a:ln><a:solidFill>` resolved hex (no `#`). Border stroke. */
314
+ borderColor?: string;
315
+ /** `<a:ln w>` border width in EMU (12700 EMU = 1 pt). */
316
+ borderWidthEmu?: number;
317
+ }
318
+
319
+ /**
320
+ * `<c:manualLayout>` block. Fractions are of the chart-space rect.
321
+ * `xMode`/`yMode`: "edge" = absolute fraction from top-left, "factor" =
322
+ * fraction offset from default position.
323
+ */
324
+ declare interface ChartManualLayout {
325
+ xMode: string;
326
+ yMode: string;
327
+ layoutTarget?: string;
328
+ x: number;
329
+ y: number;
330
+ w?: number;
331
+ h?: number;
332
+ }
333
+
334
+ declare interface ChartModel {
335
+ chartType: ChartType;
336
+ title: string | null;
337
+ categories: string[];
338
+ series: ChartSeries[];
339
+ /**
340
+ * §21.2.2.227 `<c:varyColors val="1"/>` on a SINGLE-series bar/column chart:
341
+ * color each data point (bar) from the theme/palette sequence and list one
342
+ * legend entry per point, matching Office. Set by the shared parser ONLY for
343
+ * that non-pie, single-series case (pie/doughnut already vary by point via
344
+ * `chartType` + `dataPointColors`); absent/false otherwise.
345
+ */
346
+ varyColors?: boolean | null;
347
+ /** Show data labels on bars / points / slices. */
348
+ showDataLabels: boolean;
349
+ /** Explicit Y-axis minimum (OOXML `<c:valAx><c:min>`). */
350
+ valMin: number | null;
351
+ /** Explicit Y-axis maximum (OOXML `<c:valAx><c:max>`). */
352
+ valMax: number | null;
353
+ catAxisTitle: string | null;
354
+ valAxisTitle: string | null;
355
+ /** `<c:catAx><c:delete val="1"/>`. */
356
+ catAxisHidden: boolean;
357
+ /** `<c:valAx><c:delete val="1"/>`. */
358
+ valAxisHidden: boolean;
359
+ /** `<c:catAx><c:spPr><a:ln><a:noFill>` — hide just the axis LINE; labels
360
+ * and tick marks still render. Distinct from `catAxisHidden` (which
361
+ * removes everything via `<c:delete val="1"/>`). */
362
+ catAxisLineHidden: boolean;
363
+ /** `<c:valAx><c:spPr><a:ln><a:noFill>` — hide just the axis LINE; labels
364
+ * and tick marks still render. */
365
+ valAxisLineHidden: boolean;
366
+ /** Hex without '#'. From `<c:plotArea><c:spPr><a:solidFill>`. */
367
+ plotAreaBg: string | null;
368
+ /** Outer chartSpace background (hex without '#'). null when noFill/absent. */
369
+ chartBg: string | null;
370
+ /** True when `<c:legend>` is declared in the chart XML. False = no legend. */
371
+ showLegend: boolean;
372
+ /** `<c:legend><c:legendPos val>` — "r"|"l"|"t"|"b"|"tr". null = default (r). */
373
+ legendPos: 'r' | 'l' | 't' | 'b' | 'tr' | null;
374
+ /** `<c:catAx><c:crossBetween val="..."/>`. "between" inserts 0.5-step padding
375
+ * on each end of the category axis; "midCat" anchors endpoints to the axes. */
376
+ catAxisCrossBetween: 'between' | 'midCat' | string;
377
+ /** `<c:valAx><c:majorTickMark>`. ECMA-376 default is "cross". */
378
+ valAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
379
+ /** `<c:catAx><c:majorTickMark>`. */
380
+ catAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
381
+ /** `<c:valAx | catAx><c:minorTickMark>`. ECMA-376 default is "none". */
382
+ valAxisMinorTickMark?: 'cross' | 'out' | 'in' | 'none' | string | null;
383
+ catAxisMinorTickMark?: 'cross' | 'out' | 'in' | 'none' | string | null;
384
+ /** Title font size in OOXML hundredths of a point (1600 = 16pt). null = default. */
385
+ titleFontSizeHpt: number | null;
386
+ /** Title font color as a hex string without '#' (e.g. "1B4332"). null = default. */
387
+ titleFontColor: string | null;
388
+ /** Title font family from `<a:latin typeface>` (ECMA-376 §20.1.4.2.24). null = default. */
389
+ titleFontFace: string | null;
390
+ /** `<c:catAx><c:txPr>` font size (hpt). null = fall back to proportional default. */
391
+ catAxisFontSizeHpt: number | null;
392
+ /** `<c:valAx><c:txPr>` font size (hpt). null = fall back to proportional default. */
393
+ valAxisFontSizeHpt: number | null;
394
+ /** `<c:catAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#').
395
+ * null = renderer default. Lets templates color category labels gray. */
396
+ catAxisFontColor?: string | null;
397
+ /** `<c:valAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
398
+ valAxisFontColor?: string | null;
399
+ /** `<c:dLbls><c:txPr>` font size (hpt) for data-point value labels. */
400
+ dataLabelFontSizeHpt: number | null;
401
+ /** Waterfall subtotal category indices. */
402
+ subtotalIndices: number[];
403
+ /** `<c:legend><c:manualLayout>` absolute placement fractions of the chart
404
+ * space (ECMA-376 §21.2.2.31). Overrides the default side-based legend
405
+ * rectangle while still letting `legendPos` decide which side of the plot
406
+ * gets the reserved band. null = use default layout. */
407
+ legendManualLayout?: LegendManualLayout | null;
408
+ /**
409
+ * `<c:valAx><c:numFmt@formatCode>` — format code applied to value-axis tick
410
+ * labels (ECMA-376 §21.2.2.21). null = plain numeric formatting.
411
+ */
412
+ valAxisFormatCode?: string | null;
413
+ /**
414
+ * `<c:barChart><c:gapWidth>` — space between category groups as a
415
+ * percentage of bar width (ECMA-376 §21.2.2.13). Default per spec is 150.
416
+ * null = renderer default.
417
+ */
418
+ barGapWidth?: number | null;
419
+ /**
420
+ * `<c:barChart><c:overlap>` — signed percentage overlap between bars in the
421
+ * same category cluster (ECMA-376 §21.2.2.25). Negative = gap, positive =
422
+ * overlap, 0 = flush. Range [-100, 100]. null = renderer default (0).
423
+ */
424
+ barOverlap?: number | null;
425
+ /**
426
+ * `<c:dLbls><c:dLblPos>` — data label position (ECMA-376 §21.2.2.16).
427
+ * "ctr"|"inBase"|"inEnd"|"outEnd"|"l"|"r"|"t"|"b"|"bestFit" etc.
428
+ */
429
+ dataLabelPosition?: string | null;
430
+ /** Hex (no `#`) for data label text, resolved from `<c:dLbls><c:txPr>`. */
431
+ dataLabelFontColor?: string | null;
432
+ /**
433
+ * `<c:dLbls><c:numFmt@formatCode>` — chart-level override for data label
434
+ * number format (ECMA-376 §21.2.2.35). When absent, `valFormatCode` on each
435
+ * series is used.
436
+ */
437
+ dataLabelFormatCode?: string | null;
438
+ /** `<c:title>...defRPr@b>` chart title bold flag. */
439
+ titleFontBold?: boolean | null;
440
+ /** `<c:catAx><c:txPr>...defRPr@b>` X-axis tick label bold flag. */
441
+ catAxisFontBold?: boolean | null;
442
+ /** `<c:valAx><c:txPr>...defRPr@b>` Y-axis tick label bold flag. */
443
+ valAxisFontBold?: boolean | null;
444
+ /** `<c:catAx><c:title>` run-prop font size (hpt). Distinct from
445
+ * `catAxisFontSizeHpt` (tick labels). null = renderer default. */
446
+ catAxisTitleFontSizeHpt?: number | null;
447
+ /** `<c:catAx><c:title>` run-prop bold flag. null = not bold. */
448
+ catAxisTitleFontBold?: boolean | null;
449
+ /** `<c:catAx><c:title>` run-prop color (hex without '#'). null = default. */
450
+ catAxisTitleFontColor?: string | null;
451
+ /** `<c:valAx><c:title>` run-prop font size (hpt). null = renderer default. */
452
+ valAxisTitleFontSizeHpt?: number | null;
453
+ /** `<c:valAx><c:title>` run-prop bold flag. null = not bold. */
454
+ valAxisTitleFontBold?: boolean | null;
455
+ /** `<c:valAx><c:title>` run-prop color (hex without '#'). null = default. */
456
+ valAxisTitleFontColor?: string | null;
457
+ /** `<c:catAx><c:txPr>…<a:latin typeface>` tick-label font. */
458
+ catAxisFontFace?: string | null;
459
+ /** `<c:valAx><c:txPr>…<a:latin typeface>` tick-label font. */
460
+ valAxisFontFace?: string | null;
461
+ /** `<c:catAx><c:title>…<a:latin typeface>` axis-title font. */
462
+ catAxisTitleFontFace?: string | null;
463
+ /** `<c:valAx><c:title>…<a:latin typeface>` axis-title font. */
464
+ valAxisTitleFontFace?: string | null;
465
+ /** `<c:dLbls><c:txPr>…<a:latin typeface>` data-label font. */
466
+ dataLabelFontFace?: string | null;
467
+ /** `<c:legend><c:txPr>…<a:latin typeface>` legend font. */
468
+ legendFontFace?: string | null;
469
+ /** `<c:legend><c:txPr>…<a:solidFill>` legend text color (hex without '#'). */
470
+ legendFontColor?: string | null;
471
+ /** `<c:legend><c:txPr>` legend font size (OOXML hundredths of a point). */
472
+ legendFontSizeHpt?: number | null;
473
+ /** `<c:legend><c:txPr>…defRPr@b` legend bold flag. */
474
+ legendFontBold?: boolean | null;
475
+ /**
476
+ * Theme font-scheme faces (`<a:fontScheme>`, ECMA-376 §20.1.4.2). Latin
477
+ * heading (majorFont) and body (minorFont) typefaces, used as the fallback
478
+ * for any chart text element whose own `<c:txPr>` supplies no `<a:latin>`.
479
+ * null when the theme is not threaded to the chart (then the renderer's
480
+ * built-in sans-serif remains, byte-stable). Axis titles / chart title use
481
+ * the major (heading) face; tick labels / data labels / legend use the
482
+ * minor (body) face — matching Office's default chart text styling.
483
+ */
484
+ themeMajorFontLatin?: string | null;
485
+ themeMinorFontLatin?: string | null;
486
+ /** Explicit chart border color (hex without '#') from
487
+ * `<c:chartSpace><c:spPr><a:ln><a:solidFill><a:srgbClr>`. Only set when the
488
+ * XML explicitly declares a paintable line; null otherwise (no default
489
+ * border is drawn). */
490
+ chartBorderColor?: string | null;
491
+ /** `<c:chartSpace><c:spPr><a:ln@w>` border width in EMU. null = 1px hairline
492
+ * when a color is present. */
493
+ chartBorderWidthEmu?: number | null;
494
+ /**
495
+ * `<c:catAx><c:crosses val>` (`autoZero` | `min` | `max`). Drives the Y
496
+ * coordinate where the X axis is drawn. Default `autoZero` puts the X
497
+ * axis at y=0 — that's how Excel "Project Timeline" templates split
498
+ * milestones (positive Y) above and tasks (negative Y) below the axis.
499
+ */
500
+ catAxisCrosses?: string | null;
501
+ /** `<c:catAx><c:crossesAt val>` — explicit numeric override for the
502
+ * crossing point. Takes precedence over `catAxisCrosses`. */
503
+ catAxisCrossesAt?: number | null;
504
+ valAxisCrosses?: string | null;
505
+ valAxisCrossesAt?: number | null;
506
+ /** Axis line color (hex without `#`) and width in EMU from
507
+ * `<c:catAx|valAx><c:spPr><a:ln>`. */
508
+ catAxisLineColor?: string | null;
509
+ catAxisLineWidthEmu?: number | null;
510
+ valAxisLineColor?: string | null;
511
+ valAxisLineWidthEmu?: number | null;
512
+ /**
513
+ * `<c:catAx><c:numFmt@formatCode>` (or scatter X-axis valAx). When set,
514
+ * the renderer formats X-axis tick labels with this code (e.g. dates).
515
+ */
516
+ catAxisFormatCode?: string | null;
517
+ /**
518
+ * `<c:catAx><c:scaling><c:min/max>` — explicit X-axis range. Used by
519
+ * scatter / bubble charts whose X axis is numeric. null = derive from
520
+ * data extents.
521
+ */
522
+ catAxisMin?: number | null;
523
+ catAxisMax?: number | null;
524
+ /**
525
+ * `<c:title><c:layout><c:manualLayout>` (ECMA-376 §21.2.2.27) absolute
526
+ * placement for the chart title.
527
+ */
528
+ titleManualLayout?: ChartManualLayout | null;
529
+ /**
530
+ * `<c:plotArea><c:layout><c:manualLayout>` absolute placement for the
531
+ * plot area. `layoutTarget="inner"` (default) describes the inner plot
532
+ * rect (no axes / labels); `outer` describes the outer rect (axes
533
+ * included).
534
+ */
535
+ plotAreaManualLayout?: ChartManualLayout | null;
536
+ /**
537
+ * `<c:scatterChart><c:scatterStyle val>` (ECMA-376 §21.2.2.42). Drives
538
+ * whether scatter charts connect points with lines and whether those
539
+ * lines are smoothed. Values: "marker" (markers only — Excel default
540
+ * "Scatter"), "line" / "lineMarker" (straight segments), "smooth" /
541
+ * "smoothMarker" (cubic Bézier through points), "lineNoMarker",
542
+ * "smoothNoMarker". null = renderer default ("marker"). Only consulted
543
+ * for `chartType === "scatter"`; bubble ignores it.
544
+ */
545
+ scatterStyle?: string | null;
546
+ /**
547
+ * `<c:radarChart><c:radarStyle val>` (ECMA-376 §21.2.3.10). Controls
548
+ * whether radar series render as line + markers ("standard" / "marker")
549
+ * or as a closed polygon with area fill ("filled"). null = default
550
+ * ("standard" — line, no fill). Only consulted for `chartType === "radar"`.
551
+ */
552
+ radarStyle?: string | null;
553
+ /**
554
+ * Secondary value axis for combo charts (bar + line). When present, series
555
+ * with `useSecondaryAxis` are plotted against this axis's independent scale
556
+ * and the axis is drawn on the right edge of the plot. null/absent = single
557
+ * value axis (the common case). See {@link SecondaryValueAxis}.
558
+ */
559
+ secondaryValAxis?: SecondaryValueAxis | null;
560
+ /**
561
+ * `<c:date1904>` (ECMA-376 §21.2.2.38). When true the chart's serial
562
+ * date-times resolve against the 1904 date system (base 1904-01-01) instead
563
+ * of the default 1900 system. Threaded to the date formatters for date-axis
564
+ * category labels and value-axis tick labels. Omitted/false ⇒ 1900 system.
565
+ * Note: per §21.2.2.38 the element's `val` defaults to true when present but
566
+ * the attribute is omitted, so `<c:date1904/>` alone means date1904=true.
567
+ */
568
+ date1904?: boolean;
569
+ /**
570
+ * `<c:doughnutChart><c:holeSize val>` (ECMA-376 §21.2.2.60,
571
+ * `ST_HoleSizePercent` §21.2.3.55) — the doughnut hole diameter as a
572
+ * percentage 1–90 of the outer diameter. Ignored for pie (which has no
573
+ * hole). null/undefined = use the renderer's doughnut default when the
574
+ * element is absent. Note the ECMA `CT_HoleSize` schema default is 10%, but
575
+ * a real doughnut file always writes an explicit `<c:holeSize>` (Excel /
576
+ * PowerPoint emit 50–75%); the renderer falls back to 50% only for the
577
+ * pathological absent case.
578
+ */
579
+ holeSize?: number | null;
580
+ /**
581
+ * `<c:pieChart | doughnutChart><c:firstSliceAng val>` (ECMA-376 §21.2.2.52,
582
+ * `ST_FirstSliceAng` §21.2.3.15) — the angle in degrees (0–360, clockwise
583
+ * from the 12 o'clock position) at which the first slice begins.
584
+ * null/undefined = 0 (start at 12 o'clock), which matches the renderer's
585
+ * historical fixed −90° (canvas up) start.
586
+ */
587
+ firstSliceAngle?: number | null;
588
+ /**
589
+ * `<c:chartSpace><c:chart><c:dispBlanksAs val>` (ECMA-376 §21.2.2.42,
590
+ * `ST_DispBlanksAs` §21.2.3.10) — how blank (null) cells are plotted on
591
+ * line/area charts:
592
+ * - "gap" → leave a gap (break the line). The renderer's historical
593
+ * behavior and the model default when the element is absent.
594
+ * - "zero" → plot the blank as the value 0 (the point drops to the axis).
595
+ * - "span" → skip the blank but connect its neighbours with a straight
596
+ * line (bridge the gap).
597
+ * Note the XSD `@val` default is "zero" (applies when `<c:dispBlanksAs/>` is
598
+ * present but the attribute is omitted); when the ELEMENT is absent entirely
599
+ * Office falls back to "gap", which is what we model as the default. Only
600
+ * consulted for the line and area families. null/undefined = "gap".
601
+ */
602
+ dispBlanksAs?: string | null;
603
+ /**
604
+ * `<c:valAx><c:majorGridlines>` presence (ECMA-376 §21.2.2.100). `false` when
605
+ * the value axis exists but omits the element (Office suppresses value
606
+ * gridlines). null/undefined ⇒ the renderer's historical always-on value
607
+ * gridlines (byte-stable). `true` is redundant with the default but honored.
608
+ */
609
+ valAxisMajorGridlines?: boolean | null;
610
+ /**
611
+ * `<c:catAx><c:majorGridlines>` presence (§21.2.2.100). `true` turns on
612
+ * category-axis gridlines (Office omits them by default). null/undefined/false
613
+ * ⇒ no category gridlines (the historical default, byte-stable).
614
+ */
615
+ catAxisMajorGridlines?: boolean | null;
616
+ /**
617
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
618
+ * color (hex without `#`) — ECMA-376 §21.2.2.100. When set, the value-axis
619
+ * major gridlines are stroked in this color instead of the renderer's faint
620
+ * `#e0e0e0` default (e.g. sample-1 slide 5's `accent3` gridlines). null/absent
621
+ * ⇒ the historical default (byte-stable).
622
+ */
623
+ valAxisGridlineColor?: string | null;
624
+ /**
625
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. When
626
+ * set, the value-axis gridline stroke width is derived from this (floored so a
627
+ * hairline stays visible). null/absent ⇒ the renderer's 0.5 px default.
628
+ */
629
+ valAxisGridlineWidthEmu?: number | null;
630
+ /**
631
+ * `<c:catAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
632
+ * color (hex without `#`). Only meaningful when {@link catAxisMajorGridlines}
633
+ * is on. null/absent ⇒ the faint default.
634
+ */
635
+ catAxisGridlineColor?: string | null;
636
+ /** `<c:catAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. */
637
+ catAxisGridlineWidthEmu?: number | null;
638
+ /** `<c:valAx><c:minorGridlines>` presence (§21.2.2.109). Only drawn when a
639
+ * minor step is resolvable (see {@link valAxisMinorUnit}). */
640
+ valAxisMinorGridlines?: boolean | null;
641
+ /**
642
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between major
643
+ * gridlines/ticks, overriding the Excel-style auto "nice" step. null/undefined
644
+ * ⇒ auto step (byte-stable).
645
+ */
646
+ valAxisMajorUnit?: number | null;
647
+ /** `<c:valAx><c:minorUnit val>` (§21.2.2.112) — explicit minor step. Drives
648
+ * minor gridlines/ticks when present. null ⇒ no minor divisions. */
649
+ valAxisMinorUnit?: number | null;
650
+ /**
651
+ * `<c:valAx><c:scaling><c:logBase val>` (§21.2.2.98, `ST_LogBase` §21.2.3.25)
652
+ * — logarithmic value-axis base (>= 2). When set, values map to pixels in log
653
+ * space and gridlines fall on powers of the base. null/undefined ⇒ linear
654
+ * (byte-stable).
655
+ */
656
+ valAxisLogBase?: number | null;
657
+ /**
658
+ * `<c:valAx><c:scaling><c:orientation val>` (§21.2.2.130, `ST_Orientation`
659
+ * §21.2.3.30) — "minMax" (normal) | "maxMin" (reversed, so the value axis runs
660
+ * top→bottom max→min). null/undefined/"minMax" ⇒ normal (byte-stable).
661
+ */
662
+ valAxisOrientation?: 'minMax' | 'maxMin' | string | null;
663
+ /** `<c:catAx><c:scaling><c:orientation val>` — "maxMin" reverses the category
664
+ * axis left↔right. null/"minMax" ⇒ normal. */
665
+ catAxisOrientation?: 'minMax' | 'maxMin' | string | null;
666
+ /**
667
+ * `<c:catAx><c:tickLblPos val>` (§21.2.2.207, `ST_TickLblPos` §21.2.3.47) —
668
+ * "nextTo" (default) | "low" | "high" | "none". "none" hides the category tick
669
+ * labels. null/undefined ⇒ nextTo (byte-stable).
670
+ */
671
+ catAxisTickLabelPos?: string | null;
672
+ /** `<c:valAx><c:tickLblPos val>` (§21.2.2.207). "none" hides value tick labels. */
673
+ valAxisTickLabelPos?: string | null;
674
+ /**
675
+ * `<c:catAx><c:txPr><a:bodyPr rot>` (DrawingML `ST_Angle`, 60000ths of a
676
+ * degree) — category tick-label rotation. e.g. -2700000 = -45°. null/undefined
677
+ * /0 ⇒ horizontal labels (byte-stable).
678
+ */
679
+ catAxisLabelRotation?: number | null;
680
+ /**
681
+ * `<c:stockChart><c:hiLowLines>` presence (ECMA-376 §21.2.2.60). When true
682
+ * the stock renderer draws a vertical line spanning each category's low↔high
683
+ * value. Only set for `chartType === "stock"`; null/undefined on every other
684
+ * chart type (byte-stable).
685
+ */
686
+ stockHiLowLines?: boolean | null;
687
+ /**
688
+ * `<c:hiLowLines><c:spPr><a:ln><a:solidFill>` resolved color (hex, no `#`).
689
+ * null = the renderer's default gray hi-lo line.
690
+ */
691
+ stockHiLowLineColor?: string | null;
692
+ /**
693
+ * `<c:stockChart><c:upDownBars>` presence (ECMA-376 §21.2.2.227). Parsed so a
694
+ * stock file carrying open-close up/down bars is recognized; the renderer does
695
+ * NOT yet draw them (tracked follow-up). null/undefined when absent.
696
+ */
697
+ stockUpDownBars?: boolean | null;
698
+ /**
699
+ * Structured box-and-whisker data (`chartType === 'boxWhisker'`). Present
700
+ * ONLY for boxWhisker charts; null/absent otherwise so the flat
701
+ * `categories`/`series` model the other chartEx renderers consume is
702
+ * untouched. The renderer computes quartiles / mean / whiskers / outliers.
703
+ */
704
+ chartexBox?: ChartexBoxWhisker | null;
705
+ /**
706
+ * Structured sunburst hierarchy (`chartType === 'sunburst'`). Present ONLY
707
+ * for sunburst charts; null/absent otherwise.
708
+ */
709
+ chartexSunburst?: ChartexSunburst | null;
710
+ /**
711
+ * Theme accent palette (`accent1..6`, hex without '#') for chartEx charts
712
+ * that color by branch/series index (boxWhisker series, sunburst branches).
713
+ * null/absent when the resolver supplies no default palette (pptx); the
714
+ * renderer then falls back to its own `CHART_PALETTE`.
715
+ */
716
+ chartexAccents?: string[] | null;
717
+ }
718
+
719
+ /** ECMA-376 §21.2 — a DrawingML chart embedded in the run flow via
720
+ * `<w:drawing><wp:inline|wp:anchor>…<a:graphicData uri=".../chart"><c:chart r:id>`.
721
+ * Mirrors the Rust `ChartRun`. `chart` is the shared {@link ChartModel} the
722
+ * core `renderChart` consumes (identical to what pptx/xlsx pass), so a docx
723
+ * chart draws at the same quality through the same code path. `widthPt`/
724
+ * `heightPt` are the `<wp:extent>` natural size. An inline chart flows as an
725
+ * inline box of that size; an anchored chart (§20.4.2.3) is painted via
726
+ * `registerAnchorFloats` when it wraps text, or by `renderAnchorImages` for
727
+ * wrapNone/no-wrap anchors — all paths use `renderChart`. */
728
+ export declare interface ChartRun {
729
+ chart: ChartModel;
730
+ widthPt: number;
731
+ heightPt: number;
732
+ /** true = `<wp:anchor>` (absolute page position, drawn by the anchor path);
733
+ * false = `<wp:inline>` (flows with text). */
734
+ anchor: boolean;
735
+ anchorXPt?: number;
736
+ anchorYPt?: number;
737
+ anchorXFromMargin?: boolean;
738
+ anchorYFromPara?: boolean;
739
+ /**
740
+ * Wrap mode for anchored charts (ECMA-376 §20.4.2.16/.17):
741
+ * "square" | "topAndBottom" | "none" | "tight" | "through"
742
+ * Inline charts and undetermined cases leave this undefined. The renderer
743
+ * treats "tight" and "through" as "square", matching anchored images.
744
+ */
745
+ wrapMode?: string;
746
+ /** Padding top (pt). Anchor-only (ECMA-376 §20.4.2.16/.17). */
747
+ distTop?: number;
748
+ /** Padding bottom (pt). Anchor-only (ECMA-376 §20.4.2.16/.17). */
749
+ distBottom?: number;
750
+ /** Padding left (pt). Anchor-only (ECMA-376 §20.4.2.16/.17). */
751
+ distLeft?: number;
752
+ /** Padding right (pt). Anchor-only (ECMA-376 §20.4.2.16/.17). */
753
+ distRight?: number;
754
+ /** wrapText attribute: "bothSides" | "left" | "right" | "largest". */
755
+ wrapSide?: string;
756
+ /**
757
+ * ECMA-376 §20.4.2.3 `wp:anchor/@allowOverlap`. The parser omits this
758
+ * field when true, so renderers must read it as `allowOverlap ?? true`.
759
+ */
760
+ allowOverlap?: boolean;
761
+ /** ECMA-376 §20.4.3.1 wp:align horizontal: "left" | "center" | "right" |
762
+ * "inside" | "outside". */
763
+ anchorXAlign?: string | null;
764
+ /** Vertical equivalent of anchorXAlign: "top" | "center" | "bottom". */
765
+ anchorYAlign?: string | null;
766
+ /**
767
+ * ECMA-376 §20.4.3.2 `<wp:positionH/@relativeFrom>` / §20.4.3.5
768
+ * `<wp:positionV/@relativeFrom>` — the raw anchor placement containers.
769
+ */
770
+ anchorXRelativeFrom?: string | null;
771
+ anchorYRelativeFrom?: string | null;
772
+ }
773
+
774
+ declare interface ChartSeries {
775
+ name: string;
776
+ /** Hex without '#'. null = fall back to palette. */
777
+ color: string | null;
778
+ /** Numeric values; null = missing data point. */
779
+ values: (number | null)[];
780
+ /**
781
+ * Per-data-point colors (pie / doughnut). Hex without '#'. null inside the
782
+ * array = use palette for that slice. Omit entirely for non-pie series.
783
+ */
784
+ dataPointColors?: (string | null)[] | null;
785
+ /**
786
+ * Per-data-point data-label text colors. Used by chartEx (`<cx:dataLabel idx>`)
787
+ * to override label colour per bar — sample-2's waterfall paints negative
788
+ * △ values in red while positive values stay black. Null inside the array =
789
+ * fall back to the chart-level `dataLabelFontColor`.
790
+ */
791
+ dataLabelColors?: (string | null)[] | null;
792
+ /**
793
+ * Series-level data-label text colour (`<c:ser><c:dLbls><c:txPr>…solidFill`,
794
+ * ECMA-376 §21.2.2.216). Hex without '#'. Stacked-bar charts colour each
795
+ * segment's label independently (e.g. white on the dark segment, black on
796
+ * the light one), which a single chart-level `dataLabelFontColor` can't
797
+ * express. Takes precedence over `dataLabelFontColor`; null = no override.
798
+ */
799
+ labelColor?: string | null;
800
+ /**
801
+ * Mixed chart: per-series chart type override. Currently only "line" (XLSX
802
+ * and PPTX combo charts) is honoured; other values are treated as the
803
+ * chart's primary type.
804
+ */
805
+ seriesType?: string | null;
806
+ /**
807
+ * Combo chart: this series is plotted against the SECONDARY value axis
808
+ * (`ChartModel.secondaryValAxis`) — the `<c:valAx>` with `axPos="r"` /
809
+ * `<c:crosses val="max">`. When false/absent the series uses the primary
810
+ * (left) value-axis scale. PowerPoint's "Revenue vs. gross margin" combo
811
+ * (sample-14 slide-8) puts the margin line on a 0–100% secondary axis.
812
+ */
813
+ useSecondaryAxis?: boolean | null;
814
+ /**
815
+ * Scatter-only X values (as strings). When null the series uses
816
+ * `ChartModel.categories` as X.
817
+ */
818
+ categories?: string[] | null;
819
+ /**
820
+ * Resolved marker visibility for line/scatter series. ECMA-376 §21.2.2.32
821
+ * `<c:marker><c:symbol>` defaults to "none" for line charts unless the
822
+ * chart-level `<c:marker val="1"/>` or a per-series symbol opts in. When
823
+ * undefined/null the renderer uses its own default (visible) so callers
824
+ * that don't parse markers (e.g. pptx today) keep their existing behavior.
825
+ */
826
+ showMarker?: boolean | null;
827
+ /**
828
+ * Excel number-format code for this series' values (ECMA-376 §21.2.2.37,
829
+ * `<c:val>/<c:numRef>/<c:formatCode>`). Used to format data labels when the
830
+ * chart-level `<c:dLbls><c:numFmt>` is not set. null = no series-level code.
831
+ */
832
+ valFormatCode?: string | null;
833
+ /**
834
+ * `<c:marker><c:symbol val>` (ECMA-376 §21.2.2.32) — point marker shape.
835
+ * One of "circle"|"square"|"diamond"|"triangle"|"x"|"plus"|"star"|
836
+ * "dot"|"dash"|"picture"|"none". null = renderer default (circle when
837
+ * showMarker is true).
838
+ */
839
+ markerSymbol?: string | null;
840
+ /**
841
+ * `<c:marker><c:size val>` (ECMA-376 §21.2.2.34) — marker side length in
842
+ * points. null = renderer default (~5 pt).
843
+ */
844
+ markerSize?: number | null;
845
+ /** `<c:marker><c:spPr><a:solidFill>` resolved hex (no `#`). */
846
+ markerFill?: string | null;
847
+ /** `<c:marker><c:spPr><a:ln><a:solidFill>` resolved hex (no `#`). */
848
+ markerLine?: string | null;
849
+ /**
850
+ * Per-data-point overrides (ECMA-376 §21.2.2.39 `<c:dPt>`). Keyed by point
851
+ * index. Any unset field falls back to the series-level value.
852
+ */
853
+ dataPointOverrides?: ChartDataPointOverride[] | null;
854
+ /**
855
+ * Per-data-point custom labels (ECMA-376 §21.2.2.45 `<c:dLbl idx>`).
856
+ * `text` is the resolved plain string — `<a:fld type="CELLRANGE">`
857
+ * placeholders are already substituted at parse time. An empty string
858
+ * means the point's label was deleted with `<c:delete val="1"/>` and
859
+ * the renderer should skip it.
860
+ */
861
+ dataLabelOverrides?: ChartDataLabelOverride[] | null;
862
+ /**
863
+ * Series-level `<c:dLbls>` block (showVal / showSerName / position).
864
+ * Applied to every point lacking its own `<c:dLbl>` override.
865
+ */
866
+ seriesDataLabels?: ChartSeriesDataLabels | null;
867
+ /**
868
+ * `<c:errBars>` per-series error bars (ECMA-376 §21.2.2.20). Up to two
869
+ * (one per direction). Plus / minus deltas are absolute per-point values
870
+ * regardless of `errValType`.
871
+ */
872
+ errBars?: ChartErrBars[] | null;
873
+ /**
874
+ * `<c:bubbleSize>` per-point sizes for bubble charts (ECMA-376 §21.2.2.4).
875
+ * Drives marker radius — renderer treats the values as areas (radius
876
+ * scales by sqrt) so visual area is proportional to value, matching
877
+ * Excel. null / empty array = uniform marker size. Ignored for non-bubble
878
+ * series.
879
+ */
880
+ bubbleSizes?: (number | null)[] | null;
881
+ /**
882
+ * `<c:ser><c:smooth val>` (ECMA-376 §21.2.2.194) — line/area series flag
883
+ * requesting a smoothed (spline) curve through the points instead of straight
884
+ * segments. Only consulted for the line and area families (scatter carries its
885
+ * smoothing in `ChartModel.scatterStyle`). null/undefined/false = straight
886
+ * polyline (the default; byte-stable for series that never set it).
887
+ */
888
+ smooth?: boolean | null;
889
+ /**
890
+ * `<c:ser><c:trendline>` per-series trendlines (ECMA-376 §21.2.2.211,
891
+ * `CT_Trendline`). A series can carry several (e.g. a linear fit + a moving
892
+ * average). null/undefined/empty = no trendline (the default; byte-stable for
893
+ * series that never declare one).
894
+ */
895
+ trendLines?: ChartTrendline[] | null;
896
+ /**
897
+ * `<c:ser><c:spPr><a:ln><a:noFill/>` (ECMA-376 §21.2.2.198 CT_ShapeProperties
898
+ * → DrawingML §20.1.2.2.24 CT_LineProperties). true when the series connecting
899
+ * line is explicitly turned OFF. For a scatter/line series this OVERRIDES the
900
+ * chart-group `<c:scatterStyle>` (§21.2.2.42) / line default — Excel and
901
+ * PowerPoint draw markers only (no connecting line) even when the group style
902
+ * is `lineMarker`. null/undefined = no explicit line-off, so the group default
903
+ * governs (byte-stable for series that carry a paintable line).
904
+ */
905
+ lineHidden?: boolean | null;
906
+ }
907
+
908
+ declare interface ChartSeriesDataLabels {
909
+ showVal: boolean;
910
+ showCatName: boolean;
911
+ showSerName: boolean;
912
+ showPercent: boolean;
913
+ position?: string;
914
+ fontColor?: string;
915
+ formatCode?: string;
916
+ /** Series-level bold default for data labels. */
917
+ fontBold?: boolean;
918
+ /** Series-level font size for data labels (OOXML hundredths of a point). */
919
+ fontSizeHpt?: number;
920
+ /** Series-default callout box (`<c:dLbls><c:spPr>`, ECMA-376 §21.2.2.49/
921
+ * §21.2.2.197). When present the pie/doughnut renderer draws Word's boxed
922
+ * callout layout (box + optional leader line) instead of plain text. */
923
+ labelBox?: ChartLabelBox;
924
+ /** `<c:dLbls><c:showLeaderLines val>` (§21.2.2.183) — draw leader lines from
925
+ * a pulled-away label back to its slice. Default false. */
926
+ showLeaderLines?: boolean;
927
+ /** `<c:leaderLines><c:spPr><a:ln><a:solidFill>` (§21.2.2.92) resolved hex
928
+ * (no `#`). undefined → renderer uses a neutral grey. */
929
+ leaderLineColor?: string;
930
+ /** `<c:leaderLines><c:spPr><a:ln w>` leader-line width in EMU. */
931
+ leaderLineWidthEmu?: number;
932
+ }
933
+
934
+ /**
935
+ * `<c:ser><c:trendline>` (ECMA-376 §21.2.2.211). A regression/smoothing curve
936
+ * fitted to the series' data points.
937
+ */
938
+ declare interface ChartTrendline {
939
+ /**
940
+ * `<c:trendlineType val>` (§21.2.2.213, `ST_TrendlineType` §21.2.3.50):
941
+ * "linear" | "exp" | "log" | "power" | "poly" | "movingAvg". The renderer
942
+ * currently draws "linear" (least squares) and "movingAvg"; other types parse
943
+ * but are not yet plotted (tracked as a follow-up).
944
+ */
945
+ trendlineType: string;
946
+ /** `<c:order val>` — polynomial order (`poly`, default 2). */
947
+ order?: number | null;
948
+ /** `<c:period val>` — moving-average window (`movingAvg`, default 2). */
949
+ period?: number | null;
950
+ /** `<c:forward val>` — units to extend the line past the last point. */
951
+ forward?: number | null;
952
+ /** `<c:backward val>` — units to extend the line before the first point. */
953
+ backward?: number | null;
954
+ /** `<c:intercept val>` — forced y-intercept (linear/exp). null = free fit. */
955
+ intercept?: number | null;
956
+ /** `<c:dispRSqr val="1">` — show the R² value (label; not yet rendered). */
957
+ dispRSqr?: boolean | null;
958
+ /** `<c:dispEq val="1">` — show the fit equation (label; not yet rendered). */
959
+ dispEq?: boolean | null;
960
+ /** `<c:spPr><a:ln><a:solidFill>` trendline color (hex without '#'). null =
961
+ * inherit the series color. */
962
+ lineColor?: string | null;
963
+ /** `<c:spPr><a:ln w>` trendline width in EMU. */
964
+ lineWidthEmu?: number | null;
965
+ }
966
+
967
+ /**
968
+ * Canonical chart type vocabulary. Embeds direction (`H` = horizontal) and
969
+ * grouping (`Pct` = percent-stacked) so renderers do not need to inspect
970
+ * separate `barDir`/`grouping` fields.
971
+ */
972
+ 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;
973
+
115
974
  /** ECMA-376 §17.6.3 `<w:col>` — one column's width and trailing space (pt). */
116
975
  export declare interface ColSpec {
117
976
  widthPt: number;
@@ -159,7 +1018,7 @@ export declare interface DocParagraph {
159
1018
  * both, distribute. Other values (kashida variants, numTab, thaiDistribute)
160
1019
  * are treated as start-aligned.
161
1020
  */
162
- alignment: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | 'both' | 'distribute' | string;
1021
+ alignment: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | 'both' | 'distribute' | 'lowKashida' | 'mediumKashida' | 'highKashida' | 'thaiDistribute' | string;
163
1022
  indentLeft: number;
164
1023
  indentRight: number;
165
1024
  indentFirst: number;
@@ -169,16 +1028,33 @@ export declare interface DocParagraph {
169
1028
  numbering: NumberingInfo | null;
170
1029
  tabStops: TabStop[];
171
1030
  runs: DocRun[];
1031
+ /**
1032
+ * ECMA-376 §17.13.6.2 `<w:bookmarkStart w:name>` — names of the bookmarks that
1033
+ * start within (or at the head of) this paragraph, in document order. A
1034
+ * `<w:hyperlink w:anchor="X">` internal link (§17.16.23) targets the paragraph
1035
+ * whose `bookmarks` contains `"X"`; {@link buildBookmarkPageMap} turns these
1036
+ * into a `bookmarkName → pageIndex` map after pagination. Absent (`undefined`)
1037
+ * for the common paragraph that anchors nothing.
1038
+ */
1039
+ bookmarks?: string[];
172
1040
  /** Paragraph background hex color (w:shd fill) */
173
1041
  shading?: string | null;
174
1042
  /** Force a page break before this paragraph (w:pageBreakBefore) */
175
1043
  pageBreakBefore?: boolean;
176
- /** Suppress spacing between adjacent same-style paragraphs (w:contextualSpacing) */
1044
+ /** ECMA-376 §17.3.1.9 `<w:contextualSpacing>` — between adjacent SAME-style
1045
+ * paragraphs, a toggling paragraph drops its OWN contribution to the
1046
+ * collapsed inter-paragraph gap (per-side, Word-adjudicated — issue #1015;
1047
+ * see the renderer's `contextualSpacingAdjust`). */
177
1048
  contextualSpacing?: boolean;
178
1049
  /** Keep paragraph on same page as the next paragraph (w:keepNext) */
179
1050
  keepNext?: boolean;
180
1051
  /** Keep all lines of this paragraph on the same page (w:keepLines) */
181
1052
  keepLines?: boolean;
1053
+ /** ECMA-376 §17.3.1.29 + §17.3.2.41 — the paragraph MARK's resolved `w:vanish`
1054
+ * (hidden text). An inkless paragraph whose mark is vanished collapses to zero
1055
+ * height in the normal/print view (hidden-text off), the same way the parser
1056
+ * strips hidden runs; the paginator drops it whole. Absent = mark is visible. */
1057
+ markVanish?: boolean;
182
1058
  /** Widow/orphan control (w:widowControl). ECMA-376 default is true. */
183
1059
  widowControl?: boolean;
184
1060
  /** Paragraph borders (w:pBdr) */
@@ -190,6 +1066,17 @@ export declare interface DocParagraph {
190
1066
  /** Default font family resolved from the style chain. Used to size empty
191
1067
  * paragraphs (no runs) with the intended font's line metrics. */
192
1068
  defaultFontFamily?: string | null;
1069
+ /** Default East Asian font family resolved from the style chain. Empty /
1070
+ * anchor-only paragraph marks in East Asian documents use this axis for line
1071
+ * metrics instead of the ASCII fallback. */
1072
+ defaultFontFamilyEastAsia?: string | null;
1073
+ /** ECMA-376 §17.3.1.29 — the paragraph MARK run's resolved `w:color` (direct
1074
+ * pPr/rPr → pStyle chain → docDefaults; hex 6 without `#`, lowercased; an
1075
+ * explicit `auto` surfaces as absent, §17.3.2.6). Word formats a numbering
1076
+ * marker with the level rPr (§17.9.24) layered over the mark's run
1077
+ * properties, so the renderer uses this as the marker-color fallback when
1078
+ * {@link NumberingInfo.color} is absent. */
1079
+ paragraphMarkColor?: string | null;
193
1080
  /**
194
1081
  * ECMA-376 §17.3.1.6 `<w:bidi>` — right-to-left paragraph. `true` = RTL,
195
1082
  * `false` = explicitly LTR, absent = unspecified (inherit). The renderer uses
@@ -230,6 +1117,8 @@ export declare type DocRun = {
230
1117
  } & DocxTextRun | {
231
1118
  type: 'image';
232
1119
  } & ImageRun | {
1120
+ type: 'chart';
1121
+ } & ChartRun | {
233
1122
  type: 'break';
234
1123
  breakType: 'line' | 'page' | 'column';
235
1124
  } | {
@@ -242,7 +1131,9 @@ export declare type DocRun = {
242
1131
  display: boolean;
243
1132
  fontSize: number;
244
1133
  jc?: string;
245
- };
1134
+ } | {
1135
+ type: 'ptab';
1136
+ } & PTabRun;
246
1137
 
247
1138
  export declare interface DocSettings {
248
1139
  /** §17.15.1.58 `w:kinsoku` — East-Asian line-breaking toggle. `undefined`
@@ -263,6 +1154,17 @@ export declare interface DocSettings {
263
1154
  * tab stops generated after all custom stops. `undefined` ⇒ the renderer
264
1155
  * uses the spec default of 720 twips (36pt). */
265
1156
  defaultTabStop?: number;
1157
+ /** §17.15.1.18 `w:characterSpacingControl@w:val` — East Asian punctuation /
1158
+ * character-spacing control. */
1159
+ characterSpacingControl?: string;
1160
+ /** §17.15.3.1 `w:compat/w:useFELayout` — Far East layout compatibility. */
1161
+ useFeLayout?: boolean;
1162
+ /** §17.15.3.1 `w:compat/w:balanceSingleByteDoubleByteWidth` — balance
1163
+ * single-byte and double-byte widths for East Asian layout. */
1164
+ balanceSingleByteDoubleByteWidth?: boolean;
1165
+ /** §17.15.3.1 `w:compat/w:adjustLineHeightInTable` — apply the section
1166
+ * document-grid line pitch to text in table cells. */
1167
+ adjustLineHeightInTable?: boolean;
266
1168
  }
267
1169
 
268
1170
  export declare interface DocTable {
@@ -275,6 +1177,14 @@ export declare interface DocTable {
275
1177
  cellMarginRight: number;
276
1178
  /** table horizontal alignment on the page: 'left' | 'center' | 'right'. */
277
1179
  jc: string;
1180
+ /** ECMA-376 §17.4.50 `<w:tblInd>` — indentation added before the table's
1181
+ * LEADING edge (left in an LTR table, right in an RTL/`bidiVisual` table), in
1182
+ * pt. SIGNED: a negative value pulls the table outward past the leading margin
1183
+ * toward the page edge (Word writes this for a header banner that must reach
1184
+ * the physical page edge). `type="dxa"` only; `pct`/`auto` are dropped by the
1185
+ * parser per §17.4.50. Absent ⇒ no direct indent. The renderer applies it only
1186
+ * when the resolved `jc` is left/leading (§17.4.50). */
1187
+ tblInd?: number;
278
1188
  /** ECMA-376 §17.4.52 `<w:tblLayout w:type>` — 'fixed' | 'autofit'. Absent
279
1189
  * (undefined) ⇒ spec default 'autofit'. Both paths size columns from the
280
1190
  * tblGrid (§17.4.48) scaled to fit: 'fixed' uses the grid verbatim; 'autofit'
@@ -336,16 +1246,36 @@ export declare interface DocTableRow {
336
1246
  * lower bound; "exact" = fixed clip. */
337
1247
  rowHeightRule: 'auto' | 'atLeast' | 'exact' | string;
338
1248
  isHeader: boolean;
1249
+ /** ECMA-376 §17.4.6 `<w:cantSplit>` — when true, the row must not be split
1250
+ * across page boundaries. Omitted/false rows may split at page boundaries. */
1251
+ cantSplit?: boolean;
339
1252
  }
340
1253
 
341
1254
  export declare class DocxDocument {
342
1255
  private _document;
343
1256
  private _meta;
344
1257
  private _pages;
1258
+ /** Lazily-built `bookmarkName → 0-based page index` map for internal hyperlink
1259
+ * anchors (IX-nav). Built on first {@link getBookmarkPage} from the paginated
1260
+ * pages (main) or the worker meta's `bookmarkPages` (worker). Nulled by
1261
+ * {@link destroy} so a reused reference never serves a stale document. */
1262
+ private _bookmarkPages;
345
1263
  private _mode;
346
1264
  private _worker;
347
1265
  private _bridge;
348
1266
  private _imageCache;
1267
+ /** Embedded `FontFace` objects this document registered into `document.fonts`
1268
+ * (main mode only — in worker mode the worker owns them and terminates with
1269
+ * its own FontFaceSet). Released in {@link destroy} so they do not leak into
1270
+ * the shared FontFaceSet for the lifetime of the SPA (deduped + refcounted in
1271
+ * core, so a font shared with another open document survives until both go). */
1272
+ private _embeddedFontFaces;
1273
+ /** Google-Fonts `FontFace` objects this document preloaded into `document.fonts`
1274
+ * (main mode only — in worker mode the worker owns them and terminates with its
1275
+ * own FontFaceSet). Released in {@link destroy} so they do not leak into the
1276
+ * shared FontFaceSet for the lifetime of the SPA (deduped + refcounted in core,
1277
+ * so a web font shared with another open document survives until both go). */
1278
+ private _googleFontFaces;
349
1279
  /** One stable closure per instance: core's path-keyed SVG cache namespaces on
350
1280
  * this identity, so two open documents never swap a shared zip path (e.g.
351
1281
  * word/media/image1.svg). Reusing one reference also lets the SVG cache hit
@@ -364,6 +1294,32 @@ export declare class DocxDocument {
364
1294
  * are decoded lazily rather than inlined as base64 at parse time.
365
1295
  */
366
1296
  getImage(imagePath: string, mimeType: string): Promise<Blob>;
1297
+ /**
1298
+ * Extract raw bytes for an embedded font part by zip path (e.g.
1299
+ * `word/fonts/font1.odttf`). Routes through the SAME persistent-worker
1300
+ * `extractImage` message as {@link getImage} — `DocxArchive.extract_image`
1301
+ * reads ANY zip entry, not just media — returning the raw (still obfuscated)
1302
+ * `.odttf` bytes rather than a Blob. Consumed by {@link loadEmbeddedFonts},
1303
+ * which de-obfuscates (ECMA-376 §17.8.1) and registers each as a FontFace.
1304
+ */
1305
+ getFontBytes(partPath: string): Promise<Uint8Array>;
1306
+ /**
1307
+ * Project the document to GitHub-flavoured markdown: headings (from
1308
+ * `<w:outlineLvl>`), bullet / numbered lists, tables (with vMerge
1309
+ * continuation), and rich-text formatting (bold / italic / strikethrough /
1310
+ * hyperlink), with footnotes / endnotes / comments collated at the end.
1311
+ * Positioning, section properties, fonts, and drawing shapes are discarded —
1312
+ * the projection is meant for AI ingestion and full-text search, not layout.
1313
+ *
1314
+ * Runs entirely in the worker off the archive opened at {@link load} (no
1315
+ * re-copy of the file, no re-parse of the model on the main thread), so it
1316
+ * works in BOTH `mode: 'main'` and `mode: 'worker'`.
1317
+ *
1318
+ * @example
1319
+ * const doc = await DocxDocument.load(buffer);
1320
+ * const md = await doc.toMarkdown();
1321
+ */
1322
+ toMarkdown(): Promise<string>;
367
1323
  get pageCount(): number;
368
1324
  /** The render mode this engine was loaded with ('main' | 'worker'). A fact for
369
1325
  * integrators and the scroll viewer: an injected engine's mode decides whether
@@ -399,6 +1355,23 @@ export declare class DocxDocument {
399
1355
  */
400
1356
  get endnotes(): DocNote[];
401
1357
  private _getPages;
1358
+ /** Lazily build (and cache) the `bookmarkName → page index` map from either
1359
+ * the worker meta (worker mode) or the paginated pages (main mode). */
1360
+ private _getBookmarkPages;
1361
+ /**
1362
+ * ECMA-376 §17.13.6.2 / §17.16.23 — resolve a bookmark name (a
1363
+ * `<w:hyperlink w:anchor>` internal-link target) to the 0-based index of the
1364
+ * page its `<w:bookmarkStart w:name>` destination falls on, or `undefined`
1365
+ * when the document has no bookmark of that name. When a bookmark's paragraph
1366
+ * spans a page break, the page where it *begins* is returned.
1367
+ *
1368
+ * This is the map an internal-hyperlink click resolves against: a viewer's
1369
+ * `onHyperlinkClick` default (or an integrator) turns the anchor into a page
1370
+ * and calls {@link DocxViewer.goToPage} (or scrolls the scroll viewer to it).
1371
+ * Works in BOTH `main` and `worker` mode (the map rides along in the worker
1372
+ * meta, built from the same paginated pages as `pageSizes`).
1373
+ */
1374
+ getBookmarkPage(bookmarkName: string): number | undefined;
402
1375
  /**
403
1376
  * ECMA-376 §17.6.13 / §17.6.11 — the page size (pt) of page `pageIndex`, per
404
1377
  * section (a mixed portrait/landscape document returns different sizes per page).
@@ -422,8 +1395,23 @@ export declare class DocxDocument {
422
1395
  * The returned ImageBitmap is owned by the caller: pass it to
423
1396
  * `transferFromImageBitmap` (which consumes it) or call `bitmap.close()`
424
1397
  * when done, or its backing memory is held until GC.
1398
+ *
1399
+ * IX6 — an optional `onTextRun` in `opts` receives the page's text-run
1400
+ * geometry (the same stream `renderPage` emits in main mode), so a caller can
1401
+ * build the selection / find overlay from a worker-rendered page on the SAME
1402
+ * code path as main mode. In worker mode the runs ride back beside the bitmap
1403
+ * (one round-trip, no second render).
425
1404
  */
426
- renderPageToBitmap(pageIndex: number, opts?: WireRenderPageOptions): Promise<ImageBitmap>;
1405
+ renderPageToBitmap(pageIndex: number, opts?: RenderPageToBitmapOptions): Promise<ImageBitmap>;
1406
+ /**
1407
+ * IX6 — collect a page's text-run geometry (`DocxTextRunInfo[]`) without
1408
+ * painting a visible canvas. Works in BOTH modes: worker mode renders the page
1409
+ * off-thread and ships only the runs (no bitmap transfer); main mode renders
1410
+ * to a throwaway offscreen canvas. Used by the find controller to scan every
1411
+ * page for matches. The geometry is identical to a `renderPage` of the same
1412
+ * page at the same width/dpr.
1413
+ */
1414
+ collectPageRuns(pageIndex: number, opts?: WireRenderPageOptions): Promise<DocxTextRunInfo[]>;
427
1415
  }
428
1416
 
429
1417
  export declare interface DocxDocumentModel {
@@ -444,6 +1432,21 @@ export declare interface DocxDocumentModel {
444
1432
  * entry is absent or classified as "auto".
445
1433
  */
446
1434
  fontFamilyClasses?: Record<string, string>;
1435
+ /**
1436
+ * ECMA-376 §17.8.3.29 — per-font pitch from `word/fontTable.xml`
1437
+ * (`<w:pitch>`, ST_Pitch §17.18.66): font name → "fixed" | "variable" |
1438
+ * "default". Present only for fonts that declare `<w:pitch>`. The renderer
1439
+ * pairs this with {@link fontFamilyClasses}: a `family="modern"` face is
1440
+ * treated as monospace ONLY when its pitch is "fixed"; "variable" /
1441
+ * "default" / absent fall through to name-pattern / CJK-sans classification
1442
+ * (§17.8.3.10 `family` classifies the design, not the pitch — issue #855).
1443
+ */
1444
+ fontFamilyPitches?: Record<string, string>;
1445
+ /** ECMA-376 §17.8.3.3-.6 — embedded fonts from `word/fontTable.xml`, resolved
1446
+ * to their `.odttf` part paths + fontKey. The viewer de-obfuscates (§17.8.1)
1447
+ * and registers each as a FontFace before pagination so text measures/draws
1448
+ * with the authored typeface. */
1449
+ embeddedFonts?: EmbeddedFontRef[];
447
1450
  /** ECMA-376 §17.13.5 — flat list of `<w:ins>` / `<w:del>` events in the
448
1451
  * body. Each entry carries author / date / text. The renderer marks
449
1452
  * runs inline via {@link DocxTextRun.revision}; this array is primarily for
@@ -463,6 +1466,31 @@ export declare interface DocxDocumentModel {
463
1466
  * (kinsoku) configuration. Absent when settings.xml has no relevant
464
1467
  * elements (the renderer then uses spec defaults: kinsoku ON). */
465
1468
  settings?: DocSettings;
1469
+ /** RB7 partial degradation: set when `word/document.xml` (the body part) could
1470
+ * not be read or parsed. The document still "opens" — `body` is empty and this
1471
+ * part-tagged error (e.g. `"word/document.xml: <detail>"`) is carried — so the
1472
+ * viewer shows a visible placeholder page instead of throwing. Absent
1473
+ * (`undefined`) for every healthy document. */
1474
+ parseError?: string;
1475
+ }
1476
+
1477
+ export declare interface DocxHighlightColors {
1478
+ /** Fill for non-active matches. */
1479
+ match?: string;
1480
+ /** Fill for the active match. */
1481
+ active?: string;
1482
+ }
1483
+
1484
+ /** One page's highlight input: the run-slices a match covers, and whether that
1485
+ * match is the active one (emphasis colour). */
1486
+ export declare interface DocxHighlightMatch {
1487
+ slices: MatchRunSlice[];
1488
+ active: boolean;
1489
+ }
1490
+
1491
+ /** Where a docx match lives: its 0-based page index. */
1492
+ export declare interface DocxMatchLocation {
1493
+ page: number;
466
1494
  }
467
1495
 
468
1496
  /** ECMA-376 §17.3.2.4 `<w:bdr>` — a run-level border drawn as a box around the
@@ -478,7 +1506,7 @@ export declare interface DocxRunBorder {
478
1506
  space: number;
479
1507
  }
480
1508
 
481
- export declare class DocxScrollViewer {
1509
+ export declare class DocxScrollViewer implements ZoomableViewer {
482
1510
  private _doc;
483
1511
  private readonly _injected;
484
1512
  private readonly _opts;
@@ -499,6 +1527,18 @@ export declare class DocxScrollViewer {
499
1527
  * than a `_scale === 1` sentinel because a fit scale of exactly 1 is a valid
500
1528
  * established state (a 1× fit would otherwise be re-fit forever). */
501
1529
  private _scaleEstablished;
1530
+ /**
1531
+ * IX9 F1 — a `setScale` factor requested BEFORE the base fit is established
1532
+ * (pre-load, or a zero-width container), already clamped to
1533
+ * `[zoomMin, zoomMax]`, or `null` when none is pending. The single-canvas
1534
+ * viewers latch a pre-load `setScale` and honour it on the first render; the
1535
+ * scroll viewers used to silently DROP it — the family-unified semantics are
1536
+ * "latch and apply once the layout establishes". `relayout()` applies (and
1537
+ * clears) this right after establishing the base, firing `onScaleChange` at
1538
+ * application time; `getScale()` reports it while pending so the caller sees
1539
+ * the same value a single-canvas viewer would show.
1540
+ */
1541
+ private _pendingScale;
502
1542
  /** Live slots keyed by page index. */
503
1543
  private readonly _slots;
504
1544
  /** Recyclable detached slots (canvas + textLayer reused across pages). */
@@ -512,6 +1552,27 @@ export declare class DocxScrollViewer {
512
1552
  * reporting an error so a rejection that lands after teardown is swallowed
513
1553
  * rather than surfaced to a `onError` on a dead viewer. */
514
1554
  private _destroyed;
1555
+ /** Throwaway 2D context reused to measure text for the §17.3.2.10 縦中横 overlay
1556
+ * clamp (#836). Lazily created; `null` when canvas metrics are unavailable
1557
+ * (headless), in which case the overlay degrades to the un-clamped span. */
1558
+ private _measureCtx;
1559
+ /**
1560
+ * Concurrent-load latch (generation token). Every self-loading `load()`
1561
+ * increments this and captures the value; after its engine finishes loading it
1562
+ * re-checks the live value and BAILS (destroying its own just-loaded engine) if
1563
+ * a newer `load()` has since started. Without it, two overlapping
1564
+ * `load(A)`/`load(B)` calls race the WASM parse / worker init, and whichever
1565
+ * RESOLVES last wins the swap — even the stale `load(A)` resolving after
1566
+ * `load(B)`; the loser's freshly created engine (never installed, or installed
1567
+ * then overwritten) then leaks its worker + pinned WASM allocation. The latch
1568
+ * composes with SC20: the check runs AFTER the new engine loads but BEFORE the
1569
+ * field assignment, `previous?.destroy()`, and the recycle/relayout post-load
1570
+ * work, so a superseded load never touches `this._doc` nor frees the current
1571
+ * (newer) engine. Only the self-loading path uses it — the injected path throws
1572
+ * up-front and never reaches here. `destroy()` also bumps it so a load in flight
1573
+ * at teardown is treated as superseded and its engine cleaned up.
1574
+ */
1575
+ private _loadGen;
515
1576
  /** Worker mode: page indices whose bitmap render is currently dispatched to the
516
1577
  * engine. Coalesces a scroll storm — we never dispatch a second render for a
517
1578
  * page whose first is still in flight — and lets us drop pages that scrolled
@@ -542,11 +1603,14 @@ export declare class DocxScrollViewer {
542
1603
  * is host-agnostic. */
543
1604
  private _settleTimer;
544
1605
  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;
1606
+ /** Gesture-only pointer anchor for the NEXT `setScale`, in scrollHost-viewport
1607
+ * px (`{ x, y }` from the wheel event, relative to the scroll host's top-left).
1608
+ * Set by the Ctrl/⌘+wheel handler right before it calls `setScale` so the zoom
1609
+ * pivots on the cursor ("zoom toward the pointer") in BOTH axes; consumed and
1610
+ * cleared by `setScale`. `null` for every non-gesture source (the public
1611
+ * `setScale`, the +/- steppers, `fitWidth`/`fitPage`, the resize re-fit), which
1612
+ * keep the historical viewport-TOP re-anchor so their behaviour is unchanged. */
1613
+ private _pendingZoomAnchor;
550
1614
  /** Observes the container so a width change re-fits the base scale. Disconnected
551
1615
  * in `destroy()`. */
552
1616
  private _resizeObserver;
@@ -615,6 +1679,12 @@ export declare class DocxScrollViewer {
615
1679
  * `_positionSlot` (the flush-left floor), and by `_syncSpacer` (the spacer
616
1680
  * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
617
1681
  private _padH;
1682
+ /** Index of the page whose slot spans content-offset `y` (largest `i` with
1683
+ * `offsets[i] <= y`), for the pointer-anchored zoom re-anchor. Mirrors the
1684
+ * `topIndex` search `computeVisibleRange` runs for the scrollTop, but for an
1685
+ * ARBITRARY content-y (the pointer, not the viewport top). Clamped into
1686
+ * `[0, n-1]`; a `y` below the first page (inside the leading pad) yields 0. */
1687
+ private _pageIndexAtOffset;
618
1688
  private _range;
619
1689
  private _syncSpacer;
620
1690
  /** Horizontal scroll extent: the widest page (docx pages can differ in width)
@@ -664,10 +1734,34 @@ export declare class DocxScrollViewer {
664
1734
  * can pass for an old-epoch resolution). We gate them on the captured epoch.
665
1735
  */
666
1736
  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;
1737
+ /**
1738
+ * IX1/IX-nav the click handler passed to the text-layer overlay. When the
1739
+ * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
1740
+ * is suppressed). Otherwise the built-in default is: an external link opens in
1741
+ * a new tab through core `openExternalHyperlink` (URL sanitised against the
1742
+ * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
1743
+ * resolves its bookmark name to its destination page via
1744
+ * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and scrolls there
1745
+ * with {@link scrollToPage}. An anchor naming no known bookmark is a safe no-op
1746
+ * rather than a scroll to a guessed page.
1747
+ *
1748
+ * IX1 — returns `undefined` when `enableHyperlinks` is `false`, the single gate
1749
+ * that disables hyperlink interactivity: {@link buildDocxTextLayer} treats a
1750
+ * missing handler as "render link runs like plain runs", so no hit region,
1751
+ * cursor, tooltip, listener, or navigation is wired (a custom
1752
+ * `onHyperlinkClick` is suppressed too).
1753
+ */
1754
+ private _hyperlinkHandler;
1755
+ /** A width-measurer primed with a run's `font` — used ONLY to clamp a §17.3.2.10
1756
+ * 縦中横 selection span to its drawn one-em cell (#836). Mirrors DocxViewer's
1757
+ * `_measureForFont`. Returns a length-based fallback when canvas metrics are
1758
+ * unavailable so the caller still gets a callable (the overlay then sees scale
1759
+ * 1 and leaves the span un-clamped). */
1760
+ private _measureForFont;
1761
+ /** A canvas's intended CSS box in px (the % denominators the overlay builders
1762
+ * expect). Reads the inline `style.width`/`height` set by the render path,
1763
+ * falling back to the backing-store size when unset; tolerates the `px` suffix. */
1764
+ private _canvasCssPx;
671
1765
  /** Route an async render failure to `onError`, or `console.error` when none is
672
1766
  * set (so failures are never fully silent), and never after teardown. */
673
1767
  private _reportRenderError;
@@ -697,7 +1791,10 @@ export declare class DocxScrollViewer {
697
1791
  * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
698
1792
  * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
699
1793
  * 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.
1794
+ * fixed. A no-op when the clamped scale is unchanged. Called BEFORE the doc is
1795
+ * loaded / the base fit is established, the clamped factor is LATCHED (IX9 F1,
1796
+ * family-unified with the single-canvas viewers) and applied by `relayout()`
1797
+ * once the layout establishes — `onScaleChange` fires then.
701
1798
  *
702
1799
  * FLICKER-FREE (design §7): this does NOT re-render the visible pages inline.
703
1800
  * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
@@ -718,6 +1815,38 @@ export declare class DocxScrollViewer {
718
1815
  * can no longer return below the floor to the original base fit through this API.
719
1816
  */
720
1817
  setScale(scale: number): void;
1818
+ /** IX9 {@link ZoomableViewer} — the current zoom factor, where `1` = 100% (a
1819
+ * page at its natural pt→px width). This is the viewer's absolute `_scale`
1820
+ * (`widthPt × PT_TO_PX × _scale` is the drawn width), so it reads `1` at true
1821
+ * 100% and, after the initial fit-to-width, the base fit factor. Before the
1822
+ * fit is established it reports a latched pre-load `setScale` (IX9 F1) if one
1823
+ * is pending — matching what a single-canvas viewer would show — else `1`. */
1824
+ getScale(): number;
1825
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
1826
+ * ladder above the current factor (clamped to `zoomMax` by {@link setScale}). */
1827
+ zoomIn(): void;
1828
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
1829
+ zoomOut(): void;
1830
+ /**
1831
+ * IX9 {@link ZoomableViewer} — fit a page's WIDTH to the container (the classic
1832
+ * continuous-scroll "fit width"). Sets the scale to the width-fit base for the
1833
+ * current container, then re-anchors + re-renders via {@link setScale}. Defers
1834
+ * (no-op) while the container is unlaid-out. Note the `zoomMin`/`zoomMax` clamp
1835
+ * still applies, so a fit below `zoomMin` pins to `zoomMin`.
1836
+ */
1837
+ fitWidth(): void;
1838
+ /**
1839
+ * IX9 {@link ZoomableViewer} — fit a WHOLE page (width and height) inside the
1840
+ * container so one page is visible without scrolling; takes the tighter of the
1841
+ * width/height fit. Uses the FIRST page's size (the continuous viewer's fit
1842
+ * reference, matching the base-fit convention). Defers while unlaid-out.
1843
+ */
1844
+ fitPage(): void;
1845
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: the width-fit factor is the
1846
+ * established base (`_baseScale`); the page-fit additionally bounds by the
1847
+ * container height against the first page's height. Applies via {@link setScale}
1848
+ * so the flicker-free re-anchor / settle path and `onScaleChange` all run. */
1849
+ private _fit;
721
1850
  /**
722
1851
  * CSS preview of the visible window at the current `_scale` (design §7
723
1852
  * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
@@ -744,12 +1873,11 @@ export declare class DocxScrollViewer {
744
1873
  private _scheduleSettle;
745
1874
  /** Full-resolution settle re-render of the visible window (design §7 mechanisms
746
1875
  * 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. */
1876
+ * swap (main) / same-canvas transfer (worker). Both modes rebuild the text
1877
+ * overlay from the fresh render's run geometry (IX6 — worker mode collects the
1878
+ * runs off-thread via `_renderSlotBitmap`) and clear the preview transform.
1879
+ * Dispatched at the CURRENT epoch; the existing epoch gate discards it if a
1880
+ * later `setScale` supersedes it mid-render. */
753
1881
  private _settleRender;
754
1882
  /**
755
1883
  * Settle-render one slot at the current scale (design §7 mechanism 3).
@@ -825,6 +1953,8 @@ export declare class DocxScrollViewer {
825
1953
  /* Excluded from this release type: baseScaleForTest */
826
1954
  /* Excluded from this release type: renderEpochForTest */
827
1955
  /* Excluded from this release type: resizeForTest */
1956
+ /* Excluded from this release type: contentAtViewportYForTest */
1957
+ /* Excluded from this release type: viewportYOfForTest */
828
1958
  /**
829
1959
  * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
830
1960
  * engine) destroy the engine. An injected engine is left intact — the caller
@@ -871,9 +2001,10 @@ export declare interface DocxScrollViewerOptions extends Omit<RenderPageOptions,
871
2001
  paddingRight?: number;
872
2002
  /** Pages kept mounted beyond the viewport on each side. Default 1. */
873
2003
  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). */
2004
+ /** Per-page transparent text-selection overlay. IX6 works in BOTH render
2005
+ * modes: in worker mode the per-run geometry is collected off-thread and
2006
+ * shipped back beside the page bitmap, so the overlay is populated identically
2007
+ * to main mode (no more empty overlay / one-time warning). */
877
2008
  enableTextSelection?: boolean;
878
2009
  /** Minimum zoom scale (px-per-pt multiplier floor). Default 0.1. */
879
2010
  zoomMin?: number;
@@ -916,6 +2047,24 @@ export declare interface DocxScrollViewerOptions extends Omit<RenderPageOptions,
916
2047
  * `computeVisibleRange` (the first page intersecting the viewport top,
917
2048
  * EXCLUDING overscan). */
918
2049
  onVisiblePageChange?: (topIndex: number, total: number) => void;
2050
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100% = a page
2051
+ * at its natural pt→px size): from {@link DocxScrollViewer.setScale},
2052
+ * `zoomIn`/`zoomOut`, `fitWidth`/`fitPage`, a Ctrl/⌘+wheel gesture, or a
2053
+ * container-resize re-fit. Named `onScaleChange` to match the single-canvas
2054
+ * viewers so all five share one notification shape. */
2055
+ onScaleChange?: (scale: number) => void;
2056
+ /** IX1 (design decision — NOT user-confirmed, integrator may veto). Called when
2057
+ * a hyperlink run is clicked. When omitted, the default is: external → open in a
2058
+ * new tab via core `openExternalHyperlink` (sanitised, noopener,noreferrer);
2059
+ * internal → jump to the page whose text contains the bookmark (best-effort). */
2060
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
2061
+ /** IX1 — master switch for hyperlink interactivity. Default `true`. When
2062
+ * `false`, the hyperlink machinery is not wired at all: no overlay hit region
2063
+ * is installed for link runs, so there is no pointer cursor, no title tooltip,
2064
+ * no default navigation (external new-tab / internal bookmark jump), and
2065
+ * `onHyperlinkClick` is never called. Links still render exactly as authored
2066
+ * but are inert, like plain text. */
2067
+ enableHyperlinks?: boolean;
919
2068
  /** Error callback. When set, `load()` invokes it and resolves (otherwise the
920
2069
  * error is rethrown — shared viewer error contract). It ALSO fires for async
921
2070
  * per-slot render failures (both main `renderPage` and worker
@@ -930,6 +2079,15 @@ export declare interface DocxTextRun {
930
2079
  bold: boolean;
931
2080
  italic: boolean;
932
2081
  underline: boolean;
2082
+ /** ECMA-376 §17.3.2.40 `<w:u w:val>` — the raw ST_Underline (§17.18.99) style
2083
+ * value (`double` / `thick` / `dotted` / `wave` / `dashLong` / …). Absent for
2084
+ * the plain single rule (or no underline). The renderer normalizes this
2085
+ * WordprocessingML vocabulary to the shared DrawingML ST_TextUnderlineType
2086
+ * (§20.1.10.82) that `core.drawUnderline` dispatches on. */
2087
+ underlineStyle?: string;
2088
+ /** ECMA-376 §17.3.2.40 `<w:u w:color>` — underline-only colour (hex 6, or the
2089
+ * literal `auto`). Absent ⇒ the underline follows the glyph colour. */
2090
+ underlineColor?: string;
933
2091
  strikethrough: boolean;
934
2092
  fontSize: number;
935
2093
  color: string | null;
@@ -957,10 +2115,23 @@ export declare interface DocxTextRun {
957
2115
  vertAlign: 'super' | 'sub' | null;
958
2116
  /** Target URL for hyperlinks (resolved from relationships.xml) */
959
2117
  hyperlink: string | null;
2118
+ /** ECMA-376 §17.16.23 `<w:hyperlink w:anchor>` — internal bookmark name this
2119
+ * link jumps to (a `<w:bookmarkStart w:name>` in the same document). Set for an
2120
+ * internal cross-reference / TOC entry. When a link carries both `r:id` and
2121
+ * `w:anchor`, {@link DocxTextRun.hyperlink} (external) wins and this still
2122
+ * records the anchor. Absent when the link has no anchor. */
2123
+ hyperlinkAnchor?: string | null;
960
2124
  allCaps?: boolean;
961
2125
  smallCaps?: boolean;
962
2126
  doubleStrikethrough?: boolean;
963
2127
  highlight?: string | null;
2128
+ /** ECMA-376 §17.3.2.12 `<w:em w:val>` — emphasis (boten / 圏点) mark drawn on
2129
+ * every non-space character of the run (§17.18.24 ST_Em). `'dot'` = filled
2130
+ * dot above, `'comma'` = sesame/comma above, `'circle'` = hollow circle
2131
+ * above, `'underDot'` = filled dot below (horizontal writing). Absent (or the
2132
+ * authored `val="none"`) ⇒ no mark. The renderer stamps the mark per glyph
2133
+ * after the text and does NOT change the glyph advance. */
2134
+ emphasisMark?: EmphasisMark;
964
2135
  /** ECMA-376 §17.3.3.25 ruby annotation (furigana). Renders above the
965
2136
  * base text in a smaller font; line height is expanded to fit it. */
966
2137
  ruby?: RubyAnnotation;
@@ -992,6 +2163,52 @@ export declare interface DocxTextRun {
992
2163
  /** ECMA-376 §17.3.2.20 `<w:lang w:bidi>` — complex-script (RTL) language tag,
993
2164
  * lower-cased (e.g. "ar-sa", "ae-ar"). Drives Word's AN digit ordering. */
994
2165
  langBidi?: string;
2166
+ /** ECMA-376 §17.3.2.34 `<w:snapToGrid>` — false opts this run out of the
2167
+ * section character grid; absent inherits participation. */
2168
+ snapToGrid?: boolean;
2169
+ /** ECMA-376 §17.3.2.35 `<w:spacing w:val>` — character-spacing adjustment in
2170
+ * POINTS (signed): the extra pitch added after each character before the next
2171
+ * is rendered. The renderer feeds it to `ctx.letterSpacing` on BOTH the
2172
+ * measure and paint passes so line breaking / pagination stay consistent.
2173
+ * Absent ⇒ no extra pitch. */
2174
+ charSpacing?: number;
2175
+ /** ECMA-376 §17.3.2.14 `<w:fitText>` — manual run-width target in TWIPS
2176
+ * (`w:val`, 1/20 pt) plus the optional `w:id` that links consecutive runs
2177
+ * into one region. The arbitrary-precision XSD integer id is serialized as a
2178
+ * string; numeric synthetic inputs remain supported for layout tests and
2179
+ * direct model construction. An id-less run is always standalone. */
2180
+ fitTextVal?: number;
2181
+ fitTextId?: string | number;
2182
+ /** ECMA-376 §17.3.2.43 `<w:w w:val>` — horizontal text scale as a FRACTION of
2183
+ * normal character width (0.67 = 67%, 2.0 = 200%). Stretches each glyph's
2184
+ * width, not the gap between glyphs. Absent ⇒ 100%. */
2185
+ charScale?: number;
2186
+ /** ECMA-376 §17.3.2.24 `<w:position w:val>` — baseline raise (positive) /
2187
+ * lower (negative) in POINTS, without changing the font size or line box.
2188
+ * Absent ⇒ no shift. */
2189
+ position?: number;
2190
+ /** ECMA-376 §17.3.2.19 `<w:kern w:val>` — font-kerning threshold in POINTS
2191
+ * (the smallest font size that is kerned). Presence enables kerning subject
2192
+ * to the threshold; absent ⇒ kerning off (the hierarchy default). `0` = kern
2193
+ * at all sizes. */
2194
+ kerning?: number;
2195
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:vert>` — horizontal-in-vertical
2196
+ * (縦中横 / tate-chū-yoko). `true` means that in a VERTICAL (tbRl) page this
2197
+ * run's characters are laid out horizontally side by side within ONE cell of
2198
+ * the vertical line (rotated 90° relative to the vertical flow). Absent ⇒
2199
+ * normal vertical stacking. Inert in a horizontal page. */
2200
+ eastAsianVert?: boolean;
2201
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:vertCompress>` — compress the
2202
+ * 縦中横 run to fit the existing line height without growing the line. Ignored
2203
+ * unless {@link eastAsianVert} is set. Absent ⇒ not compressed. */
2204
+ eastAsianVertCompress?: boolean;
2205
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:combine>` — two-lines-in-one.
2206
+ * PARSED for completeness; not yet rendered (no fixture). */
2207
+ eastAsianCombine?: boolean;
2208
+ /** ECMA-376 §17.3.2.10 `<w:eastAsianLayout w:combineBrackets>` (§17.18.8) —
2209
+ * bracket style around two-lines-in-one text. PARSED for completeness; the
2210
+ * two-lines-in-one draw is a follow-up. */
2211
+ eastAsianCombineBrackets?: string;
995
2212
  /** ECMA-376 §17.11.6/.7/.16/.17 — set when this run is a footnote/endnote
996
2213
  * reference marker (`<w:footnoteReference>` in the body, `<w:footnoteRef>` at
997
2214
  * the start of the note's content, and the endnote equivalents). `text` holds
@@ -1015,11 +2232,43 @@ export declare interface DocxTextRunInfo {
1015
2232
  fontSize: number;
1016
2233
  /** CSS `font` shorthand used for canvas drawing (e.g. `"bold 16px Arial"`). */
1017
2234
  font: string;
1018
- }
1019
-
1020
- export declare class DocxViewer {
2235
+ /** Uniform per-code-point pitch in CSS px used to draw a horizontal run.
2236
+ * Absent when the pitch is zero or the run uses vertical / 縦中横 paint. */
2237
+ letterSpacingPx?: number;
2238
+ /** ECMA-376 §17.6.20 (tbRl) — when the page is vertical the canvas is the
2239
+ * physical landscape page rotated +90° at paint, so this run's `x`/`y` are the
2240
+ * PHYSICAL top-left the overlay span must sit at, and `transform` is the CSS
2241
+ * rotation (`"rotate(90deg)"`, applied about the span's top-left) that lays the
2242
+ * horizontal DOM span along the drawn (rotated) glyph run. Absent for
2243
+ * horizontal pages (the span is placed at `x`/`y` untransformed). */
2244
+ transform?: string;
2245
+ /** IX1 — the resolved hyperlink target of this run (ECMA-376 §17.16.22
2246
+ * external URL / §17.16.23 internal `w:anchor` bookmark), or absent for a
2247
+ * non-link run. The text-layer overlay turns a run carrying this into a
2248
+ * clickable region; the drawn glyphs are unaffected. */
2249
+ hyperlink?: HyperlinkTarget;
2250
+ /** ECMA-376 §17.3.2.10 eastAsianLayout `w:vert` (縦中横 / horizontal-in-vertical):
2251
+ * `true` when this run was drawn as tate-chu-yoko — its glyphs laid out
2252
+ * horizontally, side by side, COMPRESSED into ONE em cell of the vertical
2253
+ * column (see {@link drawTateChuYokoRun}). `w` is the drawn cell extent (one
2254
+ * em), NOT the natural text width, so the find / selection overlays must clamp
2255
+ * their horizontal extent to `w` rather than re-measuring the run's natural
2256
+ * glyphs (issue #836). Absent for every ordinary run. */
2257
+ eastAsianVert?: boolean;
2258
+ }
2259
+
2260
+ export declare class DocxViewer implements ZoomableViewer {
1021
2261
  private _doc;
1022
2262
  private _currentPage;
2263
+ /**
2264
+ * IX9 explicit zoom factor (`1` = 100% = the page at its natural pt→px width),
2265
+ * or `null` when the caller has never invoked a zoom method. `null` preserves
2266
+ * the pre-IX9 render path EXACTLY: the page renders at `opts.width` (or its
2267
+ * natural width when that is unset), so default rendering is byte-identical. The
2268
+ * first `setScale`/`zoomIn`/`zoomOut`/`fitWidth`/`fitPage` call latches a number
2269
+ * here, after which `_renderPage` derives the canvas width from it instead.
2270
+ */
2271
+ private _scale;
1023
2272
  private _canvas;
1024
2273
  private _wrapper;
1025
2274
  /** The canvas's DOM position BEFORE the constructor reparented it into
@@ -1032,20 +2281,56 @@ export declare class DocxViewer {
1032
2281
  * (empty string if it was unset), restored on {@link destroy}. */
1033
2282
  private _originalDisplay;
1034
2283
  private _textLayer;
2284
+ /** IX2 — the find-highlight overlay layer. Always created (independent of
2285
+ * `enableTextSelection`): highlights ride the same positioned-DOM overlay
2286
+ * mechanism as the selection layer but are visible boxes, not transparent
2287
+ * spans. Sits above the text layer so a highlight shows over a link's hit
2288
+ * region without stealing its clicks (`pointer-events:none`). */
2289
+ private _highlightLayer;
2290
+ /** IX2 — find state (per-page runs, matches, active cursor). */
2291
+ private _find;
2292
+ /** A 2d context used only to measure text for highlight geometry (its own
2293
+ * 1×1 offscreen canvas, so measuring never touches the visible canvas). */
2294
+ private _measureCtx;
1035
2295
  private _opts;
1036
2296
  private readonly _mode;
1037
2297
  /** The canvas's bitmaprenderer context, used only in worker mode (a canvas
1038
2298
  * holds one context type for its lifetime; the main-mode 2d render path is
1039
2299
  * never used on the same canvas). */
1040
2300
  private _bitmapCtx;
1041
- private _warnedNoTextSelection;
2301
+ /** Set by {@link destroy} (first line). Guards {@link _reportRenderError} so a
2302
+ * render rejection that lands AFTER teardown is swallowed rather than surfaced
2303
+ * to an `onError` / `console.error` on a dead viewer — parity with the scroll
2304
+ * viewers' `_destroyed` flag. */
2305
+ private _destroyed;
2306
+ /**
2307
+ * Concurrent-load latch (generation token). Every {@link load} increments this
2308
+ * and captures the value; after its engine finishes loading it re-checks the
2309
+ * live value and BAILS (destroying its own just-loaded engine) if a newer
2310
+ * `load()` has since started. Without it, two overlapping `load(A)`/`load(B)`
2311
+ * calls race the WASM parse / worker init, and whichever RESOLVES last wins the
2312
+ * swap — even the stale `load(A)` resolving after `load(B)`; the loser's freshly
2313
+ * created engine (never installed, or installed then overwritten) then leaks its
2314
+ * worker + pinned WASM allocation. The latch composes with SC20: the check runs
2315
+ * AFTER the new engine loads but BEFORE the field assignment and
2316
+ * `previous?.destroy()`, so a superseded load never touches `this._doc` nor
2317
+ * frees the current (newer) engine. {@link destroy} also bumps it so a load in
2318
+ * flight at teardown is treated as superseded and its engine cleaned up.
2319
+ */
2320
+ private _loadGen;
1042
2321
  constructor(canvas: HTMLCanvasElement, opts?: DocxViewerOptions);
1043
2322
  /**
1044
2323
  * Load a DOCX from URL or ArrayBuffer and render the first page.
1045
2324
  *
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.
2325
+ * Error contract (shared by all three viewers):
2326
+ * - Parse/load failure (the underlying `DocxDocument.load()` call itself
2327
+ * rejects): if an `onError` callback was provided it is invoked and `load`
2328
+ * resolves normally; if not, the error is rethrown so it is never silently
2329
+ * swallowed.
2330
+ * - Render failure (the first page fails to draw AFTER a successful
2331
+ * parse/load): routed to the shared `_reportRenderError` contract (`onError`
2332
+ * if provided, else `console.error` — never silent) and `load` still
2333
+ * RESOLVES, matching every subsequent navigation call.
1049
2334
  */
1050
2335
  load(source: string | ArrayBuffer): Promise<void>;
1051
2336
  get pageCount(): number;
@@ -1055,6 +2340,91 @@ export declare class DocxViewer {
1055
2340
  goToPage(index: number): Promise<void>;
1056
2341
  nextPage(): Promise<void>;
1057
2342
  prevPage(): Promise<void>;
2343
+ /** Natural (100%) CSS-px width of the current page — `widthPt × PT_TO_PX`.
2344
+ * This is the scale-1 reference every zoom factor multiplies. 0 when nothing
2345
+ * is loaded. */
2346
+ private _naturalWidthPx;
2347
+ /**
2348
+ * The width (CSS px) `_renderPage` renders the current page at, honouring the
2349
+ * zoom state. `_scale === null` (no zoom method ever called) ⇒ the pre-IX9
2350
+ * value `opts.width` verbatim (byte-identical default: `undefined` lets the
2351
+ * renderer use the page's natural width). Once a factor latched ⇒
2352
+ * `naturalWidth × scale` (rounded), so the on-screen page is exactly `scale ×`
2353
+ * its natural size regardless of the original `opts.width`.
2354
+ */
2355
+ private _renderWidth;
2356
+ /** IX9 {@link ZoomableViewer} — the current zoom factor (`1` = 100%). Before
2357
+ * any zoom method is called this is the EFFECTIVE scale implied by the current
2358
+ * render width: `opts.width / naturalWidth`, or `1` when `opts.width` is unset
2359
+ * (the page renders at its natural size) or nothing is loaded. */
2360
+ getScale(): number;
2361
+ private _zoomMin;
2362
+ private _zoomMax;
2363
+ /**
2364
+ * IX9 {@link ZoomableViewer} — set the absolute zoom factor (`1` = 100% = the
2365
+ * page at its natural pt→px width), clamped to `[zoomMin, zoomMax]`, and
2366
+ * re-render the current page at the new size. Fires `onScaleChange` when the
2367
+ * clamped factor actually changes. Resolves once the re-render settles. A no-op
2368
+ * (but still latches the scale) when nothing is loaded.
2369
+ */
2370
+ setScale(scale: number): Promise<void>;
2371
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
2372
+ * ladder (clamped to `zoomMax`). */
2373
+ zoomIn(): Promise<void>;
2374
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
2375
+ zoomOut(): Promise<void>;
2376
+ /**
2377
+ * IX9 {@link ZoomableViewer} — fit the current page's WIDTH to the host
2378
+ * container (the element the canvas lives in, or `opts.container` if supplied),
2379
+ * then re-render. Defers (no-op) when nothing is loaded or the container is
2380
+ * unlaid-out. Routes through {@link setScale}, so the factor is clamped and
2381
+ * `onScaleChange` fires.
2382
+ */
2383
+ fitWidth(): Promise<void>;
2384
+ /**
2385
+ * IX9 {@link ZoomableViewer} — fit the WHOLE current page (width and height)
2386
+ * inside the container so it is visible without scrolling; takes the tighter of
2387
+ * the width/height fit. Defers when unloaded / unlaid-out.
2388
+ */
2389
+ fitPage(): Promise<void>;
2390
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: measure the natural page
2391
+ * size + the container box, ask core's pure `fitScale`, apply via setScale. */
2392
+ private _fit;
2393
+ /** The element a fit measures against: the explicit `opts.container`, else the
2394
+ * host the wrapper was inserted into (`_wrapper.parentElement`). `null` when
2395
+ * the canvas was mounted detached (no host to fit to). */
2396
+ private _fitContainer;
2397
+ /**
2398
+ * IX2 — find every occurrence of `query` in the document and highlight them
2399
+ * all (a soft box per match, drawn on the highlight overlay over the drawn
2400
+ * glyphs). Returns every match in document order, each tagged with its
2401
+ * `{ page }` (0-based). Case-insensitive by default (browser find-in-page);
2402
+ * pass `{ caseSensitive: true }` to match case exactly.
2403
+ *
2404
+ * Scans all pages, so a large document renders each page once (offscreen) to
2405
+ * read its text (the visible page reuses its on-screen render). IX6 — works in
2406
+ * BOTH `mode: 'main'` and `mode: 'worker'`: in worker mode each page's run
2407
+ * geometry is collected off-thread and shipped back, so find returns the same
2408
+ * matches on the same code path. An empty query clears the find and returns `[]`.
2409
+ */
2410
+ findText(query: string, opts?: FindMatchesOptions): Promise<FindMatch<DocxMatchLocation>[]>;
2411
+ /**
2412
+ * IX2 — move to the next match (wrap-around from last to first), navigating to
2413
+ * its page if needed, and draw it in the distinct active-match colour. Returns
2414
+ * the now-active match, or `null` when there are no matches. Call
2415
+ * {@link findText} first.
2416
+ */
2417
+ findNext(): Promise<FindMatch<DocxMatchLocation> | null>;
2418
+ /** IX2 — move to the previous match (wrap-around from first to last). */
2419
+ findPrev(): Promise<FindMatch<DocxMatchLocation> | null>;
2420
+ /** IX2 — clear all highlights and reset the find state. */
2421
+ clearFind(): void;
2422
+ /** Navigate to the active match's page (if not already there) and redraw the
2423
+ * highlights so the active box shows in the emphasis colour. */
2424
+ private _activateMatch;
2425
+ /** Rebuild the highlight overlay for the current page from cached runs
2426
+ * (no page re-render). */
2427
+ private _redrawHighlights;
1058
2428
  /**
1059
2429
  * Terminate the parser worker and release resources.
1060
2430
  *
@@ -1066,7 +2436,45 @@ export declare class DocxViewer {
1066
2436
  */
1067
2437
  destroy(): void;
1068
2438
  private _render;
2439
+ /** Route a render failure to `onError`, or `console.error` when none is given
2440
+ * (never fully silent), and never after teardown. Mirrors the scroll viewers'
2441
+ * `_reportRenderError`. */
2442
+ private _reportRenderError;
2443
+ private _renderPage;
2444
+ /** Draw the find-highlight boxes for the current page from its runs. Clears
2445
+ * the overlay when there is no active find. */
2446
+ private _buildHighlightLayer;
2447
+ /** The canvas's intended CSS box in px (the % denominators the overlay builders
2448
+ * expect). Reads the inline `style.width`/`height` set by the render path
2449
+ * (which mirror the render's logical size), falling back to the backing-store
2450
+ * dimensions when unset. Parsing tolerates the trailing `px`. */
2451
+ private _canvasCssPx;
2452
+ /** A width-measurer primed with `font`, backed by a private 1×1 canvas so it
2453
+ * never disturbs the visible canvas's context state. */
2454
+ private _measureForFont;
2455
+ /** Render a page to a throwaway offscreen canvas purely to collect its runs
2456
+ * (text + geometry) for search, without touching the visible canvas. Used by
2457
+ * the find controller for pages other than the one on screen. */
2458
+ private _collectPageRuns;
1069
2459
  private _buildTextLayer;
2460
+ /**
2461
+ * IX1/IX-nav — the click handler passed to the text-layer overlay. When the
2462
+ * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
2463
+ * is suppressed). Otherwise the built-in default is: an external link opens in
2464
+ * a new tab through core `openExternalHyperlink` (URL sanitised against the
2465
+ * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
2466
+ * resolves its bookmark name to a page via
2467
+ * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and jumps there
2468
+ * with {@link goToPage}. An anchor naming no known bookmark is a safe no-op
2469
+ * rather than a jump to a guessed page.
2470
+ *
2471
+ * IX1 — returns `undefined` when `enableHyperlinks` is `false`, the single gate
2472
+ * that disables hyperlink interactivity: {@link buildDocxTextLayer} treats a
2473
+ * missing handler as "render link runs like plain runs", so no hit region,
2474
+ * cursor, tooltip, listener, or navigation is wired (a custom
2475
+ * `onHyperlinkClick` is suppressed too).
2476
+ */
2477
+ private _hyperlinkHandler;
1070
2478
  }
1071
2479
 
1072
2480
  export declare interface DocxViewerOptions extends RenderPageOptions, LoadOptions {
@@ -1078,10 +2486,62 @@ export declare interface DocxViewerOptions extends RenderPageOptions, LoadOption
1078
2486
  enableTextSelection?: boolean;
1079
2487
  /** Called when a page finishes rendering. */
1080
2488
  onPageChange?: (index: number, total: number) => void;
2489
+ /** IX9 zoom contract ({@link ZoomableViewer}) — the clamp range for
2490
+ * {@link DocxViewer.setScale} / `zoomIn` / `zoomOut` / `fitWidth` / `fitPage`,
2491
+ * as user-facing zoom factors (`1` = 100% = the page at its natural pt→px
2492
+ * size). Defaults 0.1–4 (10%–400%), matching the other viewers. */
2493
+ zoomMin?: number;
2494
+ zoomMax?: number;
2495
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100%): from
2496
+ * {@link DocxViewer.setScale}, `zoomIn`/`zoomOut`, or `fitWidth`/`fitPage`.
2497
+ * Named `onScaleChange` to match the pptx/xlsx viewers so all five share one
2498
+ * notification shape. */
2499
+ onScaleChange?: (scale: number) => void;
2500
+ /** IX1 (design decision — NOT user-confirmed, integrator may veto). Called when
2501
+ * a hyperlink run is clicked. When omitted, the default is: external → open in a
2502
+ * new tab via core `openExternalHyperlink` (sanitised, noopener,noreferrer);
2503
+ * internal → jump to the page whose text contains the bookmark (best-effort). */
2504
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
2505
+ /** IX1 — master switch for hyperlink interactivity. Default `true`. When
2506
+ * `false`, the hyperlink machinery is not wired at all: no overlay hit region
2507
+ * is installed for link runs, so there is no pointer cursor, no title tooltip,
2508
+ * no default navigation (external new-tab / internal bookmark jump), and
2509
+ * `onHyperlinkClick` is never called. Links still render exactly as authored
2510
+ * (their colour/underline are painted on the canvas) but are inert, like plain
2511
+ * text. Set it to disable clickable links entirely — e.g. in a preview where
2512
+ * navigation must not leave the current view. */
2513
+ enableHyperlinks?: boolean;
1081
2514
  /** Called on parse or render errors. */
1082
2515
  onError?: (err: Error) => void;
1083
2516
  }
1084
2517
 
2518
+ /** A duotone effect resolved to its two endpoint colours. Both are 6-char
2519
+ * uppercase hex WITHOUT a leading `#` (the form the Rust parsers emit). `clr1`
2520
+ * is the dark endpoint (luminance 0), `clr2` the light endpoint (luminance 1),
2521
+ * matching the child order of `<a:duotone>` in §20.1.8.23. Any per-colour
2522
+ * transforms (lumMod/lumOff/tint/satMod/…) are already baked into these hexes
2523
+ * by the parser's colour-resolution machinery. */
2524
+ declare interface Duotone {
2525
+ /** First `EG_ColorChoice` child — the dark endpoint. 6-char hex, no `#`. */
2526
+ clr1: string;
2527
+ /** Second `EG_ColorChoice` child — the light endpoint. 6-char hex, no `#`. */
2528
+ clr2: string;
2529
+ }
2530
+
2531
+ /** ECMA-376 §17.8.3.3-.6 — one embedded font-style slot from
2532
+ * `word/fontTable.xml`, resolved to its obfuscated part path + fontKey. */
2533
+ export declare interface EmbeddedFontRef {
2534
+ fontName: string;
2535
+ style: 'regular' | 'bold' | 'italic' | 'boldItalic';
2536
+ partPath: string;
2537
+ fontKey: string;
2538
+ }
2539
+
2540
+ /** ECMA-376 §17.18.24 ST_Em — the emphasis-mark styles a run may carry via
2541
+ * `<w:em w:val>` (§17.3.2.12). `'none'` is filtered out by the parser, so the
2542
+ * model only ever carries one of these four positive marks (or `undefined`). */
2543
+ declare type EmphasisMark = 'dot' | 'comma' | 'circle' | 'underDot';
2544
+
1085
2545
  export declare interface FieldRun {
1086
2546
  /** "page" | "numPages" | "other" */
1087
2547
  fieldType: string;
@@ -1100,6 +2560,51 @@ export declare interface FieldRun {
1100
2560
  smallCaps?: boolean;
1101
2561
  doubleStrikethrough?: boolean;
1102
2562
  highlight?: string | null;
2563
+ /** ECMA-376 §17.3.2.12 `<w:em w:val>` — emphasis (boten / 圏点) mark, mirrors
2564
+ * {@link DocxTextRun.emphasisMark} (§17.18.24 ST_Em). Absent (or the
2565
+ * authored `val="none"`) ⇒ no mark. */
2566
+ emphasisMark?: EmphasisMark;
2567
+ }
2568
+
2569
+ /**
2570
+ * IX2 public find-result shape, shared by all three viewers.
2571
+ *
2572
+ * `findText` returns an ordered list of {@link FindMatch}. Every match carries
2573
+ * its ordinal position (`matchIndex`, 0-based, document order — the same index
2574
+ * `findNext` / `findPrev` cycle through), the matched `text`, and a
2575
+ * format-specific `location`. The location is where the three formats
2576
+ * legitimately differ — a docx match lives on a page, a pptx match on a slide,
2577
+ * an xlsx match in a sheet cell — so `FindMatch` is generic over it rather than
2578
+ * forcing an artificial common shape. Each viewer instantiates it with its own
2579
+ * location type:
2580
+ *
2581
+ * - `DocxViewer.findText` → `FindMatch<DocxMatchLocation>` ({ page })
2582
+ * - `PptxViewer.findText` → `FindMatch<PptxMatchLocation>` ({ slide })
2583
+ * - `XlsxViewer.findText` → `FindMatch<XlsxMatchLocation>` ({ sheet, ref, … })
2584
+ *
2585
+ * The generic default is `unknown` so `FindMatch` can be referenced without a
2586
+ * type argument (e.g. in generic UI code) while each viewer's return type stays
2587
+ * precise.
2588
+ */
2589
+ export declare interface FindMatch<Loc = unknown> {
2590
+ /** 0-based ordinal among all matches, in document order. This is the index
2591
+ * `findNext`/`findPrev` make active, so a caller can correlate the array it
2592
+ * got from `findText` with the active-match reported by navigation. */
2593
+ matchIndex: number;
2594
+ /** The text that matched (the query as it appears in the document — its
2595
+ * original case, not the folded form used for case-insensitive matching). */
2596
+ text: string;
2597
+ /** Where the match is, in the format's own coordinates. */
2598
+ location: Loc;
2599
+ }
2600
+
2601
+ /** Options for {@link findMatches}. */
2602
+ export declare interface FindMatchesOptions {
2603
+ /**
2604
+ * Match case exactly. Default `false` (case-insensitive, like a browser's
2605
+ * find-in-page). IX2 default — an integrator can pass `true`.
2606
+ */
2607
+ caseSensitive?: boolean;
1103
2608
  }
1104
2609
 
1105
2610
  /**
@@ -1158,6 +2663,49 @@ export declare interface HeadersFooters {
1158
2663
  even: HeaderFooter | null;
1159
2664
  }
1160
2665
 
2666
+ /**
2667
+ * Shared hyperlink model + URL sanitisation for docx / pptx / xlsx (IX1).
2668
+ *
2669
+ * All three formats carry the same two ECMA-376 concepts:
2670
+ * - an **external** hyperlink — an absolute URL resolved from a relationship
2671
+ * part target (`document.xml.rels` for docx §17.16.22, the slide rels for
2672
+ * pptx §21.1.2.3.5, the worksheet rels for xlsx §18.3.1.47), with
2673
+ * `TargetMode="External"`.
2674
+ * - an **internal** hyperlink — a jump within the document itself:
2675
+ * docx `w:anchor` -> a `<w:bookmarkStart w:name>` (§17.16.23), pptx
2676
+ * `action="ppaction://hlinksldjump"` -> a slide, xlsx `location` -> a defined
2677
+ * name or a `Sheet!A1` cell reference.
2678
+ *
2679
+ * The parsers (Rust, one per format) do the format-specific rels lookup and hand
2680
+ * each run / shape / cell a {@link HyperlinkTarget}. Everything downstream — the
2681
+ * text-layer overlay, the viewer default click behaviour, and any integrator
2682
+ * callback — is format-agnostic and consumes this one shape. Keeping the type +
2683
+ * the pure `sanitizeHyperlinkUrl` predicate here (not duplicated per package)
2684
+ * follows the cross-package unification principle: a scheme-allowlist bug fixed
2685
+ * once is fixed everywhere.
2686
+ */
2687
+ /**
2688
+ * A resolved hyperlink attached to a run, shape, or cell.
2689
+ *
2690
+ * - `external` — `url` is the raw target as authored in the file. It is NOT
2691
+ * guaranteed safe; run it through {@link sanitizeHyperlinkUrl} before
2692
+ * navigating. It is kept verbatim here so an integrator can apply its own
2693
+ * policy (e.g. allow `file:` on a trusted intranet viewer).
2694
+ * - `internal` — `ref` is the in-document destination, verbatim from the file:
2695
+ * docx: the bookmark name (`w:anchor`).
2696
+ * pptx: the internal action (e.g. `ppaction://hlinksldjump`), with the
2697
+ * resolved 0-based `slideIndex` when the rels target names a slide.
2698
+ * xlsx: the `location` string (a defined name or `Sheet1!A1`).
2699
+ */
2700
+ export declare type HyperlinkTarget = {
2701
+ kind: 'external';
2702
+ url: string;
2703
+ } | {
2704
+ kind: 'internal';
2705
+ ref: string;
2706
+ slideIndex?: number;
2707
+ };
2708
+
1161
2709
  export declare interface ImageRun {
1162
2710
  /**
1163
2711
  * Embedded zip path of the raster blip (e.g. `word/media/image1.png`) — the
@@ -1215,6 +2763,19 @@ export declare interface ImageRun {
1215
2763
  * transparency. Implements a:clrChange (make-background-transparent).
1216
2764
  */
1217
2765
  colorReplaceFrom?: string;
2766
+ /**
2767
+ * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
2768
+ * colours (through the document theme). Absent ⇒ no duotone. When present the
2769
+ * renderer decodes the raster once, remaps it along the `clr1`→`clr2`
2770
+ * luminance ramp, and caches the recoloured bitmap under a colour-suffixed key.
2771
+ */
2772
+ duotone?: Duotone;
2773
+ /**
2774
+ * ECMA-376 §20.1.8.6 `<a:alphaModFix@amt>` opacity as 0..1. Absent ⇒ fully
2775
+ * opaque. When present the renderer multiplies the picture's `globalAlpha` by
2776
+ * this fraction.
2777
+ */
2778
+ alpha?: number;
1218
2779
  /**
1219
2780
  * Wrap mode for anchor images:
1220
2781
  * "square" | "topAndBottom" | "none" | "tight" | "through"
@@ -1264,6 +2825,18 @@ export declare interface ImageRun {
1264
2825
  anchorYRelativeFrom?: string | null;
1265
2826
  }
1266
2827
 
2828
+ declare interface LegendManualLayout {
2829
+ /** `"edge"` = `x`/`y` are fractions from top-left of chart space;
2830
+ * `"factor"` = fractions offset from the default position. */
2831
+ xMode: string;
2832
+ yMode: string;
2833
+ /** Fractions of chart space width/height. */
2834
+ x: number;
2835
+ y: number;
2836
+ w: number;
2837
+ h: number;
2838
+ }
2839
+
1267
2840
  /** DrawingML line-end (arrow head). ECMA-376 §20.1.8.3 CT_LineEndProperties.
1268
2841
  * Maps 1:1 to core's `ArrowEnd`. */
1269
2842
  export declare interface LineEnd {
@@ -1275,6 +2848,24 @@ export declare interface LineEnd {
1275
2848
  len: string;
1276
2849
  }
1277
2850
 
2851
+ /** ECMA-376 §17.6.8 `<w:lnNumType>` — line numbering for a section. Mirrors the
2852
+ * Rust `LineNumbering`. A number is drawn in the left margin of each body line
2853
+ * whose count is a multiple of `countBy`. Absent on {@link SectionProps}
2854
+ * (`lineNumbering` undefined) ⇒ line numbering off. */
2855
+ export declare interface LineNumbering {
2856
+ /** `@w:countBy` — only lines whose number is a multiple of this display a
2857
+ * number. Required (absent ⇒ the whole struct is absent per §17.6.8). */
2858
+ countBy: number;
2859
+ /** `@w:start` — the starting number after each restart. Default 1. */
2860
+ start: number;
2861
+ /** `@w:distance` in pt (twips ÷ 20) — gap between the text margin and the
2862
+ * number glyphs. Absent ⇒ implementation-defined (renderer uses a default). */
2863
+ distance?: number;
2864
+ /** `@w:restart` (§17.18.47): "newPage" (default) | "newSection" |
2865
+ * "continuous" — when the counter resets to `start`. */
2866
+ restart: string;
2867
+ }
2868
+
1278
2869
  export declare interface LineSpacing {
1279
2870
  value: number;
1280
2871
  rule: 'auto' | 'exact' | 'atLeast';
@@ -1326,6 +2917,35 @@ declare interface LoadOptions_2 {
1326
2917
  * via `@font-face` in your application CSS.
1327
2918
  */
1328
2919
  useGoogleFonts?: boolean;
2920
+ /**
2921
+ * Password for an encrypted OOXML file ([MS-OFFCRYPTO] Agile Encryption).
2922
+ *
2923
+ * Password-protected Office documents are CFB (OLE2) containers, not ZIPs.
2924
+ * When this is set and the input is Agile-encrypted, `load()` decrypts it on
2925
+ * the main thread (via WebCrypto) and parses the recovered plaintext ZIP.
2926
+ *
2927
+ * Errors (thrown as {@link import('../errors/ooxml-error').OoxmlError}):
2928
+ * - no `password` on an encrypted file → code `'encrypted'`
2929
+ * - wrong `password` → code `'invalid-password'`
2930
+ * - a non-Agile scheme (Standard / Extensible / legacy) → code
2931
+ * `'unsupported-encryption'`
2932
+ *
2933
+ * Note: Agile Encryption uses a high password-hash spin count (commonly
2934
+ * 100,000), so decryption of a protected file adds roughly a second of
2935
+ * WebCrypto work before parsing begins.
2936
+ *
2937
+ * Security notes:
2938
+ * - This value is held as an ordinary JS `string` in memory for the
2939
+ * duration of key derivation. The library does not zero it, and does
2940
+ * not wrap it in a `SecureString`-equivalent — it becomes eligible for
2941
+ * garbage collection like any other string once nothing references it,
2942
+ * but no explicit wipe is performed. It is never logged or included in
2943
+ * thrown errors.
2944
+ * - Decryption recovers the plaintext but does not verify the file's HMAC
2945
+ * data-integrity tag ([MS-OFFCRYPTO] §2.3.4.14), so ciphertext tampering
2946
+ * is not detected — see "Security & Privacy" in the README.
2947
+ */
2948
+ password?: string;
1329
2949
  /**
1330
2950
  * Override the URL the parser worker fetches the WebAssembly module from.
1331
2951
  *
@@ -1370,6 +2990,23 @@ declare interface LoadOptions_2 {
1370
2990
  math?: MathRenderer;
1371
2991
  }
1372
2992
 
2993
+ /**
2994
+ * The slice of one run a match covers: the run's index in the original `runs[]`
2995
+ * and the `[start, end)` character range within that run's own `text`. A match
2996
+ * that straddles N runs yields N of these (the first sliced from its start
2997
+ * offset to the run end, the last from 0 to its end offset, any middle run
2998
+ * whole). The viewer measures each slice against that run's font to get a pixel
2999
+ * rectangle.
3000
+ */
3001
+ declare interface MatchRunSlice {
3002
+ /** Index into the original `runs[]` handed to {@link buildTextIndex}. */
3003
+ runIndex: number;
3004
+ /** Start offset within `runs[runIndex].text` (inclusive). */
3005
+ start: number;
3006
+ /** End offset within `runs[runIndex].text` (exclusive). */
3007
+ end: number;
3008
+ }
3009
+
1373
3010
  /** Accent (`m:acc`), e.g. hat, bar, vector arrow over the base. */
1374
3011
  declare interface MathAccent {
1375
3012
  kind: 'accent';
@@ -1392,6 +3029,31 @@ declare interface MathBar {
1392
3029
  base: MathNode[];
1393
3030
  }
1394
3031
 
3032
+ /** Border-box object (`m:borderBox`, §22.1.2.11): a border/strikes around the
3033
+ * base. Absent flags ⇒ a full rectangular box. */
3034
+ declare interface MathBorderBox {
3035
+ kind: 'borderBox';
3036
+ /** §22.1.2 hide* — when true the corresponding edge is NOT drawn. */
3037
+ hideTop?: boolean;
3038
+ hideBot?: boolean;
3039
+ hideLeft?: boolean;
3040
+ hideRight?: boolean;
3041
+ /** §22.1.2 strike* — strikeBLTR = bottom-left→top-right, strikeTLBR =
3042
+ * top-left→bottom-right diagonal. */
3043
+ strikeH?: boolean;
3044
+ strikeV?: boolean;
3045
+ strikeBltr?: boolean;
3046
+ strikeTlbr?: boolean;
3047
+ base: MathNode[];
3048
+ }
3049
+
3050
+ /** Box object (`m:box`, §22.1.2.13): a logical grouping (operator emulator /
3051
+ * line-break control). Draws NO border — a transparent group around `base`. */
3052
+ declare interface MathBox {
3053
+ kind: 'box';
3054
+ base: MathNode[];
3055
+ }
3056
+
1395
3057
  declare interface MathDelimiter {
1396
3058
  kind: 'delimiter';
1397
3059
  /** opening char (default '('). */
@@ -1449,7 +3111,23 @@ declare interface MathNary {
1449
3111
  body: MathNode[];
1450
3112
  }
1451
3113
 
1452
- declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup;
3114
+ declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup | MathPhant | MathSPre | MathBox | MathBorderBox;
3115
+
3116
+ /** Phantom object (`m:phant`, §22.1.2.81): contributes the spacing of `base`
3117
+ * while optionally hiding it and/or zeroing individual dimensions. */
3118
+ declare interface MathPhant {
3119
+ kind: 'phant';
3120
+ /** §22.1.2.96 `m:show` — `false` hides the base (invisible but occupies space,
3121
+ * i.e. `<mphantom>`); `true` (default) shows it and the phant only tweaks
3122
+ * spacing. */
3123
+ show: boolean;
3124
+ /** §22.1.2 zeroWid / zeroAsc / zeroDesc — suppress width / ascent / descent so
3125
+ * the base takes no space along that axis. Omitted ⇒ false. */
3126
+ zeroWid?: boolean;
3127
+ zeroAsc?: boolean;
3128
+ zeroDesc?: boolean;
3129
+ base: MathNode[];
3130
+ }
1453
3131
 
1454
3132
  declare interface MathRadical {
1455
3133
  kind: 'radical';
@@ -1492,6 +3170,15 @@ declare interface MathScript {
1492
3170
  sub?: MathNode[];
1493
3171
  }
1494
3172
 
3173
+ /** Pre-sub-superscript object (`m:sPre`, §22.1.2.99): sub + sup to the LEFT of
3174
+ * the base (e.g. ²₁A). */
3175
+ declare interface MathSPre {
3176
+ kind: 'sPre';
3177
+ sub: MathNode[];
3178
+ sup: MathNode[];
3179
+ base: MathNode[];
3180
+ }
3181
+
1495
3182
  declare type MathStyle = 'roman' | 'italic' | 'bold' | 'boldItalic';
1496
3183
 
1497
3184
  declare interface MathSvg {
@@ -1544,6 +3231,19 @@ export declare interface NumberingInfo {
1544
3231
  * this family. Absent ⇒ the renderer falls back to
1545
3232
  * {@link NumberingInfo.fontFamily}. */
1546
3233
  fontFamilyEastAsia?: string | null;
3234
+ /** ECMA-376 §17.9.24 — the numbering level rPr's `w:color` (hex 6 without
3235
+ * `#`, lowercased). Colors the marker glyph only, never the paragraph's
3236
+ * runs. Absent ⇒ the renderer falls back to
3237
+ * {@link DocParagraph.paragraphMarkColor} (§17.3.1.29 — Word layers the
3238
+ * level rPr over the paragraph mark's run properties) and finally to its
3239
+ * default ink. An explicit `w:val="auto"` is absent here + {@link colorAuto}. */
3240
+ color?: string | null;
3241
+ /** ECMA-376 §17.3.2.6 / ST_HexColorAuto (§17.18.39) — true when the level
3242
+ * rPr carries an EXPLICIT `w:color w:val="auto"`. Auto names no concrete
3243
+ * color but is not "unset": it breaks the paragraph-mark fallback, so the
3244
+ * marker draws the automatic (default) ink instead of
3245
+ * {@link DocParagraph.paragraphMarkColor}. */
3246
+ colorAuto?: boolean;
1547
3247
  /** ECMA-376 §17.9.9/§17.9.20 — when the level uses a `<w:lvlPicBulletId>`,
1548
3248
  * the marker is this image (zip path, e.g. `word/media/image1.gif`), drawn in
1549
3249
  * place of {@link NumberingInfo.text}. Absent ⇒ ordinary text/glyph marker. */
@@ -1556,6 +3256,114 @@ export declare interface NumberingInfo {
1556
3256
  picBulletHeightPt?: number;
1557
3257
  }
1558
3258
 
3259
+ /**
3260
+ * Typed error thrown by the docx / pptx / xlsx `load()` factories for failures
3261
+ * that carry a stable, programmatic {@link OoxmlErrorCode} (e.g. a
3262
+ * password-protected or legacy-binary file detected from its container magic).
3263
+ *
3264
+ * Note on workers: `instanceof OoxmlError` does not survive a structured-clone
3265
+ * across the worker boundary. Detection that needs a typed error is therefore
3266
+ * done on the main thread (before the worker is involved) so a genuine
3267
+ * `OoxmlError` instance is thrown to the caller. Errors that must cross the
3268
+ * worker boundary should carry the `code` string and be reconstructed on the
3269
+ * main side.
3270
+ */
3271
+ export declare class OoxmlError extends Error {
3272
+ readonly code: OoxmlErrorCode;
3273
+ constructor(code: OoxmlErrorCode, message: string);
3274
+ }
3275
+
3276
+ /**
3277
+ * Machine-readable code for a typed load-time failure.
3278
+ *
3279
+ * The container-level failures the `load()` factories detect on the main thread
3280
+ * before handing bytes to the parser worker (see `sniffCfb` / `decryptOoxml`).
3281
+ * This is the seed of the broader typed-error surface tracked as PD4 (OoxmlError
3282
+ * typed errors). Add codes here rather than throwing bare `Error(string)`, so
3283
+ * callers can `switch` on `err.code` instead of matching message text.
3284
+ *
3285
+ * - `'encrypted'` — password-protected, but no `password` was
3286
+ * supplied (pass `LoadOptions.password` to decrypt).
3287
+ * - `'invalid-password'` — a `password` was supplied but did not match.
3288
+ * - `'unsupported-encryption'`— encrypted with a scheme other than Agile
3289
+ * (Standard / Extensible / a legacy binary encryptor), which this library
3290
+ * cannot decrypt (PD8 implements Agile only).
3291
+ * - `'legacy-binary-format'` — a raw .doc / .xls / .ppt (not OOXML).
3292
+ * - `'not-ooxml'` — a CFB of an unrecognised kind, or otherwise
3293
+ * not an OOXML ZIP.
3294
+ */
3295
+ export declare type OoxmlErrorCode = 'encrypted' | 'invalid-password' | 'unsupported-encryption' | 'legacy-binary-format' | 'not-ooxml';
3296
+
3297
+ /**
3298
+ * The default action a viewer takes for an **external** hyperlink click when
3299
+ * the integrator supplies no `onHyperlinkClick` handler: sanitise the URL and,
3300
+ * if allowed, open it in a new tab with `noopener,noreferrer` so the opened page
3301
+ * gets no `window.opener` handle back into this document. A blocked scheme is a
3302
+ * silent no-op (returns `false`) — the click does nothing rather than navigate
3303
+ * somewhere dangerous.
3304
+ *
3305
+ * Internal targets are intentionally NOT handled here: the in-document jump
3306
+ * (page / slide / cell) is format-specific and lives in each viewer.
3307
+ *
3308
+ * Split out (not inlined in three viewers) so the "open in new tab, drop opener,
3309
+ * refuse unsafe schemes" policy is defined once. `win` is injected for tests;
3310
+ * defaults to the ambient `window`.
3311
+ *
3312
+ * @returns `true` if navigation was initiated, `false` if the URL was blocked.
3313
+ */
3314
+ export declare function openExternalHyperlink(url: string, allowed?: readonly string[], win?: Pick<Window, 'open'> | undefined): boolean;
3315
+
3316
+ /** ECMA-376 §17.18.4 CT_Border for one edge of `<w:pgBorders>`. Mirrors the Rust
3317
+ * `PageBorderEdge`. Same shape as a paragraph border edge. */
3318
+ export declare interface PageBorderEdge {
3319
+ /** `@w:val` — ST_Border line style ("single" | "double" | "dashed" | …). */
3320
+ style: string;
3321
+ /** `@w:color` hex 6, or absent for "auto" (renderer defaults to black). */
3322
+ color?: string;
3323
+ /** `@w:sz` in pt (eighths of a point ÷ 8). */
3324
+ width: number;
3325
+ /** `@w:space` in pt — a POINT measure (§17.18.68, 0–31) for page borders, NOT
3326
+ * twips — the inset from the `offsetFrom` reference. */
3327
+ space: number;
3328
+ }
3329
+
3330
+ /** ECMA-376 §17.6.10 `<w:pgBorders>` — page borders drawn around each page of a
3331
+ * section. Mirrors the Rust `PageBorders`. Each edge is a CT_Border (§17.18.4);
3332
+ * the container carries the placement globals. Absent on {@link SectionProps}
3333
+ * (`pageBorders` undefined) ⇒ no page border (the common case). Art borders
3334
+ * (§17.18.2 decorative-image styles) are unsupported — the renderer draws only
3335
+ * the standard line styles (single/double/dashed/dotted/thick/…). */
3336
+ export declare interface PageBorders {
3337
+ /** `@w:offsetFrom` (§17.18.63): "page" ⇒ each edge's `space` is from the PAGE
3338
+ * edge; "text" (the default) ⇒ from the text margin. */
3339
+ offsetFrom: string;
3340
+ /** `@w:display` (§17.18.62): "allPages" (default) | "firstPage" |
3341
+ * "notFirstPage" — which physical pages of the section show the border. */
3342
+ display: string;
3343
+ /** `@w:zOrder` (§17.18.64): "front" (default; over text) | "back" (under). */
3344
+ zOrder: string;
3345
+ top?: PageBorderEdge;
3346
+ bottom?: PageBorderEdge;
3347
+ left?: PageBorderEdge;
3348
+ right?: PageBorderEdge;
3349
+ }
3350
+
3351
+ /** ECMA-376 §17.6.12 `<w:pgNumType>` — a section's page-numbering settings.
3352
+ * Mirrors the Rust `PageNumType`. Only the two attributes that change the
3353
+ * DISPLAYED page number are carried:
3354
+ * - `start` — the number shown on the FIRST page of the section (§17.6.12);
3355
+ * absent ⇒ numbering continues from the previous section's highest number.
3356
+ * Kept as a possibly-zero / possibly-negative integer (Word writes `start="0"`).
3357
+ * - `fmt` — the ST_NumberFormat (§17.18.59) for the section's page numbers
3358
+ * (decimal / upperRoman / lowerLetter / …); absent ⇒ decimal.
3359
+ * `chapStyle`/`chapSep` (chapter-prefixed numbering) are out of scope for this
3360
+ * pass and never surfaced. Field names match the Rust `PageNumType` serialization
3361
+ * (`start`, `fmt`). */
3362
+ export declare interface PageNumType {
3363
+ start?: number;
3364
+ fmt?: string;
3365
+ }
3366
+
1559
3367
  export declare interface ParaBorderEdge {
1560
3368
  style: string;
1561
3369
  color: string | null;
@@ -1599,6 +3407,22 @@ export declare type PathCmd = {
1599
3407
  cmd: 'close';
1600
3408
  };
1601
3409
 
3410
+ /** ECMA-376 §17.3.3.23 `<w:ptab>` — an absolute-position tab. Advances to a
3411
+ * position derived from {@link PTabRun.alignment} and {@link PTabRun.relativeTo},
3412
+ * independent of the paragraph's custom tab stops / default-tab interval. */
3413
+ export declare interface PTabRun {
3414
+ /** ST_PTabAlignment (§17.18.71): where on the line the tab lands, and how the
3415
+ * following text aligns to it. */
3416
+ alignment: 'left' | 'center' | 'right';
3417
+ /** ST_PTabRelativeTo (§17.18.73): the base the position is measured from —
3418
+ * the text margins or the paragraph indents. */
3419
+ relativeTo: 'margin' | 'indent';
3420
+ /** ST_PTabLeader (§17.18.72): the character repeated to fill the tab gap. */
3421
+ leader: 'none' | 'dot' | 'hyphen' | 'underscore' | 'middleDot';
3422
+ /** Resolved run font size (pt) — matches the surrounding text's leader/gap. */
3423
+ fontSize: number;
3424
+ }
3425
+
1602
3426
  export declare interface RenderPageOptions {
1603
3427
  /** Canvas CSS width in px; height is auto-computed from page aspect ratio.
1604
3428
  * Applies per CALL — pages of different physical widths (per-section pgSz,
@@ -1608,7 +3432,10 @@ export declare interface RenderPageOptions {
1608
3432
  width?: number;
1609
3433
  dpr?: number;
1610
3434
  defaultTextColor?: string;
1611
- /** Called for each rendered text segment. Used to build a transparent text selection overlay. */
3435
+ /** Called for each rendered text segment. Used to build a transparent text
3436
+ * selection overlay. On a vertical (§17.6.20 tbRl) page `x`/`y` are the
3437
+ * PHYSICAL top-left and `transform` is the CSS rotation the overlay span
3438
+ * applies about its top-left; absent for horizontal pages. */
1612
3439
  onTextRun?: (run: {
1613
3440
  text: string;
1614
3441
  x: number;
@@ -1617,17 +3444,36 @@ export declare interface RenderPageOptions {
1617
3444
  h: number;
1618
3445
  fontSize: number;
1619
3446
  font: string;
3447
+ transform?: string;
1620
3448
  }) => void;
1621
3449
  /** Default `true`. When false, ECMA-376 §17.13.5 track-changes runs render
1622
3450
  * in their normal style (no author colour, no underline / strikethrough)
1623
3451
  * — equivalent to Word's "Final / No Markup" view. */
1624
3452
  showTrackChanges?: boolean;
1625
- }
3453
+ /** ECMA-376 §17.16.5.16 DATE / §17.16.5.72 TIME — the "current" instant a
3454
+ * DATE/TIME field formats through its `\@` date picture (§17.16.4.1). A `Date`
3455
+ * or epoch-ms number. Default = the real current time at render. Set a fixed
3456
+ * value for deterministic / reproducible DATE/TIME field output. */
3457
+ currentDate?: Date | number;
3458
+ }
3459
+
3460
+ /** IX6 — options for {@link DocxDocument.renderPageToBitmap}: the serializable
3461
+ * render knobs plus an OPTIONAL `onTextRun`. The callback stays main-thread (it
3462
+ * never crosses the wire); in worker mode the proxy invokes it with the runs
3463
+ * the worker shipped back beside the bitmap, so a caller gets the selection /
3464
+ * find geometry on the same path in both modes. */
3465
+ export declare type RenderPageToBitmapOptions = WireRenderPageOptions & {
3466
+ onTextRun?: (run: DocxTextRunInfo) => void;
3467
+ };
1626
3468
 
1627
3469
  export declare interface RubyAnnotation {
1628
3470
  text: string;
1629
3471
  /** Annotation font size in pt. Word stores this as half-points in `<w:hps>`. */
1630
3472
  fontSizePt: number;
3473
+ /** Distance “between the phonetic guide base text and the phonetic guide
3474
+ * text” in pt. Word stores this as half-points in `<w:hpsRaise>`
3475
+ * (ECMA-376 §17.3.3.12). */
3476
+ hpsRaisePt?: number;
1631
3477
  }
1632
3478
 
1633
3479
  export declare interface RunRevision {
@@ -1639,6 +3485,50 @@ export declare interface RunRevision {
1639
3485
  date?: string;
1640
3486
  }
1641
3487
 
3488
+ /**
3489
+ * A secondary value axis (combo charts). Mirrors the primary value-axis
3490
+ * properties but lives in its own object so the flat primary-axis fields stay
3491
+ * untouched. Parsed from the right-hand `<c:valAx>` (`axPos="r"`,
3492
+ * `<c:crosses val="max">`).
3493
+ */
3494
+ declare interface SecondaryValueAxis {
3495
+ /** `<c:scaling><c:min val>`. null = derive from the series data. */
3496
+ min: number | null;
3497
+ /** `<c:scaling><c:max val>`. null = derive from the series data. */
3498
+ max: number | null;
3499
+ /** `<c:title>` plain text. null = no title. */
3500
+ title: string | null;
3501
+ /** `<c:delete val="1"/>` — hide labels/ticks entirely. */
3502
+ hidden: boolean;
3503
+ /** `<c:numFmt formatCode>` for tick labels. */
3504
+ formatCode?: string | null;
3505
+ /** `<c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
3506
+ fontColor?: string | null;
3507
+ /** `<c:txPr>` tick-label font size (hpt). */
3508
+ fontSizeHpt?: number | null;
3509
+ /** `<c:spPr><a:ln><a:solidFill>` axis-line color (hex without '#'). */
3510
+ lineColor?: string | null;
3511
+ /** `<c:spPr><a:ln w>` axis-line width in EMU. */
3512
+ lineWidthEmu?: number | null;
3513
+ /** `<c:spPr><a:ln><a:noFill>` — hide just the axis rule. */
3514
+ lineHidden: boolean;
3515
+ /** `<c:majorTickMark>` — "cross" (default) | "out" | "in" | "none". */
3516
+ majorTickMark: string;
3517
+ /**
3518
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between
3519
+ * major ticks/gridlines on THIS secondary axis, overriding the Excel-style
3520
+ * auto "nice" step. null/undefined ⇒ auto step (byte-stable). Symmetric with
3521
+ * {@link ChartModel.valAxisMajorUnit} on the primary axis.
3522
+ */
3523
+ majorUnit?: number | null;
3524
+ /** `<c:title>` run-prop font size (hpt). */
3525
+ titleFontSizeHpt?: number | null;
3526
+ /** `<c:title>` run-prop bold flag. */
3527
+ titleFontBold?: boolean | null;
3528
+ /** `<c:title>` run-prop color (hex without '#'). */
3529
+ titleFontColor?: string | null;
3530
+ }
3531
+
1642
3532
  /** ECMA-376 §17.6.13 `<w:pgSz>` + §17.6.11 `<w:pgMar>` — a section's page
1643
3533
  * geometry: page size + margins + header/footer distances (pt). Mirrors the Rust
1644
3534
  * `SectionGeom`. Carried on a {@link BodyElement} `sectionBreak` arm (`geom`) so a
@@ -1683,6 +3573,17 @@ export declare interface SectionProps {
1683
3573
  * spec default). Non-final sections carry their start type on their own
1684
3574
  * SectionBreak marker. */
1685
3575
  sectionStart?: string | null;
3576
+ /** ECMA-376 §17.6.20 `<w:textDirection w:val>` — the section's flow direction,
3577
+ * using the TRANSITIONAL ST_TextDirection enum Word writes (Part 4 §14.11.7:
3578
+ * `lrTb`|`tbRl`|`btLr`|`lrTbV`|`tbLrV`|`tbRlV`), NOT the Part 1 §17.18.93
3579
+ * Strict set. Absent / `null` ⇒ "lrTb" (horizontal, left→right / top→bottom,
3580
+ * the default). `"tbRl"` = vertical Japanese (glyphs stack top→bottom, lines
3581
+ * advance right→left); the renderer (see `isVerticalSection`) lays the page out
3582
+ * horizontally and rotates it +90° at paint for the vertical values
3583
+ * (`tbRl`/`tbRlV`/`tbLrV`), keeping CJK glyphs upright and Latin sideways. Only
3584
+ * a non-default value is emitted by the parser, so horizontal documents keep
3585
+ * byte-identical rendering. */
3586
+ textDirection?: string | null;
1686
3587
  /** ECMA-376 §17.6.5 w:docGrid/@w:type — "default" | "lines" | "linesAndChars" | "snapToChars". */
1687
3588
  docGridType?: string | null;
1688
3589
  /** ECMA-376 §17.6.5 w:docGrid/@w:linePitch in pt. When docGridType is "lines" or
@@ -1701,6 +3602,23 @@ export declare interface SectionProps {
1701
3602
  * body text flows top-to-bottom through `count` columns (newspaper fill);
1702
3603
  * see {@link computeColumns}. */
1703
3604
  columns?: ColumnsSpec | null;
3605
+ /** ECMA-376 §17.6.12 `<w:pgNumType>` — the body (final) section's page-numbering
3606
+ * settings (start / fmt). `null`/absent ⇒ numbering continues; decimal. The
3607
+ * renderer resolves the displayed page number per physical page from this plus
3608
+ * the per-section `SectionBreak.pageNumType` markers. */
3609
+ pageNumType?: PageNumType | null;
3610
+ /** ECMA-376 §17.6.10 `<w:pgBorders>` — page borders for this section.
3611
+ * `null`/absent ⇒ no page border (the common case). */
3612
+ pageBorders?: PageBorders | null;
3613
+ /** ECMA-376 §17.6.8 `<w:lnNumType>` — line numbering for this section.
3614
+ * `null`/absent ⇒ line numbering off. */
3615
+ lineNumbering?: LineNumbering | null;
3616
+ /** ECMA-376 §17.6.23 `<w:vAlign w:val>` — body vertical alignment between the
3617
+ * top/bottom margins ("top" | "center" | "both" | "bottom"). `null`/absent ⇒
3618
+ * "top" (body flows from the top margin unchanged). "both" (vertical
3619
+ * justification) is parsed but rendered as "top" until distribution is
3620
+ * implemented (see renderer note). */
3621
+ vAlign?: string | null;
1704
3622
  }
1705
3623
 
1706
3624
  declare type ShapeFill = {
@@ -1755,7 +3673,7 @@ export declare interface ShapeRun {
1755
3673
  groupHeightPt?: number | null;
1756
3674
  /** Draw behind text when true (wp:anchor behindDoc="1"). */
1757
3675
  behindDoc?: boolean;
1758
- /** Document-order index within a group; lower values render first. */
3676
+ /** ECMA-376 §20.4.2.3 `wp:anchor/@relativeHeight`: lower values render first. */
1759
3677
  zOrder: number;
1760
3678
  /** Normalized [0,1] custom-geometry sub-paths. Empty when `presetGeometry`
1761
3679
  * is set; the renderer chooses between buildCustomPath and buildShapePath. */
@@ -1763,8 +3681,10 @@ export declare interface ShapeRun {
1763
3681
  /** OOXML <a:prstGeom prst> name (e.g. "rect", "ellipse", "rtTriangle").
1764
3682
  * When set the renderer calls core's buildShapePath with `adjValues`. */
1765
3683
  presetGeometry?: string | null;
1766
- /** Up to four <a:gd name="adj{n}"> values from prstGeom/avLst (0–100000). */
1767
- adjValues?: number[];
3684
+ /** <a:gd name="adj{n}"> values from prstGeom/avLst in adj1..adj8 order.
3685
+ * `null` preserves omitted named guides so the preset engine can use the
3686
+ * geometry's default for that index. */
3687
+ adjValues?: Array<number | null>;
1768
3688
  fill: ShapeFill | null;
1769
3689
  stroke: string | null;
1770
3690
  strokeWidth?: number;
@@ -1793,6 +3713,15 @@ export declare interface ShapeRun {
1793
3713
  wrapSide?: string | null;
1794
3714
  /** Text rendered INSIDE the shape's bounding box (`<wps:txbx><w:txbxContent>`). */
1795
3715
  textBlocks?: ShapeText[];
3716
+ /** ECMA-376 §20.1.4.1.17 `<wps:style><a:fontRef>` — the shape's DEFAULT text
3717
+ * color (hex, no `#`). A text-box run ({@link ShapeTextRun}) with no explicit
3718
+ * {@link ShapeTextRun.color} inherits this before falling back to the
3719
+ * document/theme default (black); an explicit run color still wins. This is
3720
+ * the color axis of the fontRef only — the `@idx` (major/minor/none) font-face
3721
+ * selection is out of scope (fonts resolve via rFonts/docDefaults). Mirrors
3722
+ * pptx's per-shape default text color from the placeholder fontRef. Absent ⇒
3723
+ * no shape default (the run color or black applies). */
3724
+ defaultTextColor?: string | null;
1796
3725
  /** "t" | "ctr" | "b" — vertical anchor for the shape's text body (`<wps:bodyPr @anchor>`). */
1797
3726
  textAnchor?: string | null;
1798
3727
  /** ECMA-376 §21.1.2.1.1 auto-fit mode from `<wps:bodyPr>`, normalized to the
@@ -1805,6 +3734,24 @@ export declare interface ShapeRun {
1805
3734
  textInsetT?: number;
1806
3735
  textInsetR?: number;
1807
3736
  textInsetB?: number;
3737
+ /** ECMA-376 §20.1.10.83 ST_TextVerticalType — the text-body flow direction from
3738
+ * `<wps:bodyPr vert>` / `<a:bodyPr vert>`. Recognised vertical values:
3739
+ * "vert" (all glyphs 90° CW, chars T→B, lines R→L), "vert270" (all glyphs 270°
3740
+ * CW = 90° CCW, chars B→T, lines L→R), and "eaVert" (East-Asian upright: CJK
3741
+ * stands upright, non-EA rotated 90°, chars T→B, lines R→L). "horz"/absent ⇒
3742
+ * horizontal (unchanged). Unrecognised values ("mongolianVert", "wordArtVert",
3743
+ * …) fall back to horizontal until implemented. */
3744
+ textVert?: string | null;
3745
+ /** ECMA-376 Part 4 §19.1.2.23 `<v:textpath>` — WordArt text laid on the
3746
+ * shape path (a text watermark). When set the renderer draws this string,
3747
+ * scaled to fill the box (`fitshape`), rotated by {@link ShapeRun.rotation},
3748
+ * filled with {@link ShapeRun.fill} at {@link ShapeRun.fillOpacity} alpha —
3749
+ * INSTEAD of a fill/stroke panel + body text. */
3750
+ textPath?: TextPath | null;
3751
+ /** ECMA-376 Part 4 §19.1.2.5 `<v:fill opacity>` — fill alpha in `[0, 1]`
3752
+ * (default 1 = opaque). Used with {@link ShapeRun.textPath} to draw the
3753
+ * watermark semi-transparently. Absent ⇒ opaque. */
3754
+ fillOpacity?: number | null;
1808
3755
  }
1809
3756
 
1810
3757
  export declare interface ShapeText {
@@ -1820,6 +3767,8 @@ export declare interface ShapeText {
1820
3767
  * (image blocks / legacy single-format paragraphs). Absent for image-only
1821
3768
  * paragraphs. */
1822
3769
  runs?: ShapeTextRun[];
3770
+ /** ECMA-376 §17.9 paragraph numbering for text-box paragraphs. */
3771
+ numbering?: NumberingInfo | null;
1823
3772
  alignment: string;
1824
3773
  /** ECMA-376 §17.3.1.33 `<w:spacing w:before>` of this text-box paragraph, in
1825
3774
  * pt — reserved ABOVE the paragraph inside the box. Absent/0 ⇒ no offset. */
@@ -1857,6 +3806,19 @@ export declare interface ShapeText {
1857
3806
  * base direction for the UAX#9 reordering pass (the body renderer reads the
1858
3807
  * identical field). */
1859
3808
  bidi?: boolean;
3809
+ /** ECMA-376 §17.3.1.9 `<w:contextualSpacing>` — resolved through the style
3810
+ * chain in the parser. When set, this text-box paragraph drops its OWN
3811
+ * contribution to the collapsed gap against an ADJACENT paragraph that
3812
+ * shares its {@link ShapeText.styleId} (per-side, Word-adjudicated — issue
3813
+ * #1015; identical to {@link DocParagraph.contextualSpacing} via the
3814
+ * renderer's `contextualSpacingAdjust`). Absent ⇒ no suppression. */
3815
+ contextualSpacing?: boolean;
3816
+ /** Resolved paragraph style id of this text-box paragraph — the explicit
3817
+ * `<w:pStyle>`, else the document default paragraph style, else "Normal" (the
3818
+ * same stable id {@link DocParagraph.styleId} carries). Paired with
3819
+ * {@link ShapeText.contextualSpacing} to group adjacent same-style paragraphs
3820
+ * for §17.3.1.9. */
3821
+ styleId?: string | null;
1860
3822
  /** Zip path of an inline image inside this text-box paragraph
1861
3823
  * (`<w:drawing><wp:inline><a:blip r:embed>`), e.g. `word/media/image1.emf`.
1862
3824
  * Absent for a text-only paragraph. */
@@ -1889,6 +3851,8 @@ export declare interface ShapeTextRun {
1889
3851
  fontFamilyEastAsia?: string | null;
1890
3852
  bold?: boolean;
1891
3853
  italic?: boolean;
3854
+ /** ECMA-376 §17.3.3.25 ruby annotation (furigana) for text-box runs. */
3855
+ ruby?: RubyAnnotation | null;
1892
3856
  }
1893
3857
 
1894
3858
  export declare interface TableBorders {
@@ -1940,7 +3904,107 @@ export declare interface TblpPr {
1940
3904
  tblpYSpec?: 'inline' | 'top' | 'center' | 'bottom' | 'inside' | 'outside' | string;
1941
3905
  }
1942
3906
 
3907
+ /** ECMA-376 Part 4 §19.1.2.23 `<v:textpath>` — a WordArt vector text path,
3908
+ * emitted by Word for text watermarks (the `PowerPlusWaterMarkObject` shape).
3909
+ * The text is stretched to fit the shape box (`fitshape`, the WordArt
3910
+ * `#_x0000_t136` shapetype default), so its drawn size derives from the shape
3911
+ * geometry rather than the nominal `font-size` in the textpath style. */
3912
+ export declare interface TextPath {
3913
+ /** The `string` attribute — the watermark text (e.g. "DRAFT"). */
3914
+ string: string;
3915
+ /** `font-family` from the textpath style (quotes stripped). */
3916
+ fontFamily?: string | null;
3917
+ bold?: boolean;
3918
+ italic?: boolean;
3919
+ }
3920
+
1943
3921
  /** Serializable subset of RenderPageOptions (callbacks cannot cross the wire). */
1944
3922
  export declare type WireRenderPageOptions = Omit<RenderPageOptions, 'onTextRun'>;
1945
3923
 
3924
+ /**
3925
+ * IX9 — the shared zoom API contract for every viewer (DocxViewer, PptxViewer,
3926
+ * DocxScrollViewer, PptxScrollViewer, XlsxViewer).
3927
+ *
3928
+ * This module owns ONLY the pure, DOM-free pieces of the contract: the type
3929
+ * ({@link ZoomableViewer}), the discrete zoom-step ladder ({@link nextZoomStep} /
3930
+ * {@link prevZoomStep}), the fit-to-content scale math ({@link fitScale}), and the
3931
+ * range clamp ({@link clampScale}). Each viewer implements the interface with its
3932
+ * own scale field and re-render path; this keeps ONE definition of "what a zoom
3933
+ * factor means" and "what the +/- steps are" across all five, so a host can drive
3934
+ * any viewer through the same six calls without special-casing the format.
3935
+ *
3936
+ * SCALE SEMANTICS (the contract): a scale of `1` means 100% — the content at its
3937
+ * natural size (a docx page at `widthPt × PT_TO_PX`, a pptx slide at
3938
+ * `slideWidth / EMU_PER_PX`, an xlsx grid at `cellScale` 1). `getScale()` and
3939
+ * `setScale(n)` speak this user-facing factor for EVERY viewer.
3940
+ *
3941
+ * KNOWN FAMILY DIFFERENCE — the INITIAL scale right after load (deliberate,
3942
+ * documented rather than papered over): the single-canvas viewers (DocxViewer /
3943
+ * PptxViewer) and XlsxViewer start at `1` (or the effective factor implied by an
3944
+ * explicit `width` option); the continuous-scroll viewers (DocxScrollViewer /
3945
+ * PptxScrollViewer) AUTO-FIT to the container on first layout, so their
3946
+ * `getScale()` right after load reports the fit-to-width BASE factor (≠ 1 unless
3947
+ * the container happens to match the natural width). The unit is identical — only
3948
+ * the starting point differs, because fit-to-width is the natural resting state
3949
+ * of a continuous document viewer.
3950
+ *
3951
+ * PRE-LOAD `setScale` (family-unified, IX9 F1): a `setScale` called before the
3952
+ * content is loaded / before the layout is established is LATCHED — never
3953
+ * silently dropped — and applied once the viewer establishes its scale (the
3954
+ * single-canvas viewers honour it on the first render; the scroll viewers apply
3955
+ * it right after the base fit establishes, firing `onScaleChange` at application
3956
+ * time). `getScale()` reports the latched factor while it is pending.
3957
+ *
3958
+ * API SHAPE (idiomatic default — the integrator MAY veto; see the IX9 PR): a
3959
+ * six-method surface plus one change notification (`onScaleChange`). Deliberately
3960
+ * NO new UI here — the contract is API only (design decision IX9 §4). Touch-pinch
3961
+ * (IX8) is out of scope.
3962
+ */
3963
+ /**
3964
+ * The zoom contract every viewer satisfies. All scales are the user-facing factor
3965
+ * where `1` = 100% (see the module note). `fitWidth`/`fitPage` are async because a
3966
+ * fit re-renders at the new scale; the getters/steppers resolve synchronously.
3967
+ */
3968
+ declare interface ZoomableViewer {
3969
+ /** The current zoom factor (`1` = 100%). Never throws — returns the default
3970
+ * (`1`) before anything is loaded, or the latched pending factor when a
3971
+ * pre-load `setScale` is waiting to be applied (see the module note). */
3972
+ getScale(): number;
3973
+ /** Set the absolute zoom factor (`1` = 100%), clamped to the viewer's
3974
+ * `[zoomMin, zoomMax]`. Re-renders at the new scale and fires `onScaleChange`
3975
+ * when the clamped value actually changes. Called BEFORE the content is
3976
+ * loaded / the layout is established, the (clamped) factor is LATCHED and
3977
+ * applied once the viewer establishes its scale — family-unified semantics
3978
+ * (IX9 F1): never silently dropped by any viewer. */
3979
+ setScale(scale: number): void | Promise<void>;
3980
+ /** Step up to the next larger rung of the shared zoom ladder (25 %→400 %),
3981
+ * clamped to `zoomMax`. Equivalent to `setScale(nextZoomStep(getScale()))`. */
3982
+ zoomIn(): void | Promise<void>;
3983
+ /** Step down to the next smaller ladder rung, clamped to `zoomMin`. */
3984
+ zoomOut(): void | Promise<void>;
3985
+ /** Fit the content's WIDTH to the container (the common "fit width" / "fit
3986
+ * page width" verb). Sets the scale so one page/slide/sheet-column-run spans
3987
+ * the available width, then re-renders. Resolves once the fit render settles.
3988
+ *
3989
+ * PERSISTENCE is viewer-implementation-dependent (deliberate, by family): the
3990
+ * single-canvas viewers (DocxViewer / PptxViewer) and XlsxViewer apply the fit
3991
+ * ONE-SHOT — they observe no container resizes, so a later resize does NOT
3992
+ * re-fit (call `fitWidth()` again after a layout change). The continuous-
3993
+ * scroll viewers (DocxScrollViewer / PptxScrollViewer) re-fit their width-fit
3994
+ * base on every container resize, so a `fitWidth()` there effectively
3995
+ * PERSISTS across resizes (the resize re-fit preserves the width-fit state). */
3996
+ fitWidth(): void | Promise<void>;
3997
+ /** Fit the WHOLE content (width AND height) inside the container, so an entire
3998
+ * page/slide is visible without scrolling. Sets the scale to the smaller of the
3999
+ * width- and height-fit factors, then re-renders.
4000
+ *
4001
+ * PERSISTENCE is viewer-implementation-dependent, and — unlike `fitWidth` —
4002
+ * a page fit does NOT persist across container resizes on ANY viewer: the
4003
+ * single-canvas viewers and XlsxViewer observe no resizes at all (one-shot),
4004
+ * and the continuous-scroll viewers' resize handler re-applies the WIDTH fit
4005
+ * (preserving the zoom multiplier), not the page fit. Re-invoke `fitPage()`
4006
+ * after a layout change to re-fit. */
4007
+ fitPage(): void | Promise<void>;
4008
+ }
4009
+
1946
4010
  export { }