@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.
@@ -61,13 +61,40 @@ export declare interface BlipBullet {
61
61
  /** MIME type of the blip at {@link BlipBullet.imagePath} (e.g. `image/png`). */
62
62
  mimeType: string;
63
63
  /**
64
- * `<a:buSzPct val>` (ECMA-376 §21.1.2.4.3) as a percentage of the text size
64
+ * `<a:buSzPct val>` (ECMA-376 §21.1.2.4.9) as a percentage of the text size
65
65
  * (100 = same size). `null` when no explicit `<a:buSzPct>` is present, in
66
66
  * which case the renderer uses the spec default of 100%.
67
67
  */
68
68
  sizePct: number | null;
69
+ /**
70
+ * `<a:buSzPts val>` (ECMA-376 §21.1.2.4.10) — an ABSOLUTE marker size in
71
+ * points, independent of the text size. Mutually exclusive with {@link
72
+ * BlipBullet.sizePct} (both are the one `EG_TextBulletSize` choice). Omitted
73
+ * when no explicit `<a:buSzPts>` is present; when present it takes precedence.
74
+ */
75
+ sizePts?: number;
69
76
  }
70
77
 
78
+ /**
79
+ * Populate a highlight overlay layer with a box per matched run-slice, grouped
80
+ * by shape frame (with the shape's rotation) so each box lands on the drawn
81
+ * glyphs.
82
+ *
83
+ * All coordinates are PERCENTAGES of `cssWidth`/`cssHeight`, and the container's
84
+ * own size is left untouched (`width:100%;height:100%` from the caller), so the
85
+ * highlights track the canvas's ACTUAL rendered box even when a consumer scales
86
+ * the canvas down with external CSS — mirroring {@link buildPptxTextLayer}.
87
+ *
88
+ * @param layer the overlay div (cleared here; sized `100%` by the caller).
89
+ * @param runs the slide's runs (same array the slide was rendered from).
90
+ * @param matches the slide's matches (run-slices + active flag).
91
+ * @param cssWidth the slide's intended CSS width (px, number) — the % denominator.
92
+ * @param cssHeight the slide's intended CSS height (px, number) — the % denominator.
93
+ * @param measureForFont returns a width-measurer primed with a run's font.
94
+ * @param colors optional colour overrides.
95
+ */
96
+ export declare function buildPptxHighlightLayer(layer: HTMLDivElement, runs: PptxTextRunInfo[], matches: PptxHighlightMatch[], cssWidth: number, cssHeight: number, measureForFont: (font: string) => (s: string) => number, colors?: PptxHighlightColors): void;
97
+
71
98
  /**
72
99
  * Build the transparent text-selection overlay for a rendered pptx slide. Unlike
73
100
  * docx (flat spans), pptx groups runs into one positioned `<div>` per shape frame
@@ -77,15 +104,34 @@ export declare interface BlipBullet {
77
104
  * shape div (`inShapeX`/`inShapeY`). Extracted verbatim from
78
105
  * `PptxViewer._buildTextLayer` so the pager (PptxViewer) and the continuous-scroll
79
106
  * viewer (PptxScrollViewer, WS4) share one implementation; public API for
80
- * integrators (design §10). MAIN render mode only `onTextRun` cannot cross the
81
- * worker boundary.
107
+ * integrators (design §10). IX6 usable in BOTH render modes: worker mode
108
+ * collects the same `PptxTextRunInfo[]` off-thread and ships it back beside the
109
+ * bitmap, so the overlay is built from identical geometry regardless of thread.
82
110
  *
83
- * @param layer the overlay div.
111
+ * IX1 when a run carries a resolved `hyperlink` (from `<a:hlinkClick>`) and an
112
+ * `onHyperlinkClick` callback is supplied, its span becomes a click target
113
+ * (`cursor:pointer`, a `title` tooltip, and a `click` handler). A plain span
114
+ * (no hyperlink) is byte-identical to before. A JS click handler is used rather
115
+ * than an `<a href>` so the URL never bypasses the viewer's sanitisation.
116
+ *
117
+ * The overlay's coordinates are all PERCENTAGES of `cssWidth`/`cssHeight` (the
118
+ * slide's intended CSS-px box), never literal px, and the container's own
119
+ * `width`/`height` are left untouched (the caller sizes it `width:100%;
120
+ * height:100%` so it fills the wrapper). This lets the overlay track the canvas's
121
+ * ACTUAL rendered box even when a consumer scales the canvas down with external
122
+ * CSS (e.g. `width:100%!important;height:auto`): the wrapper (and therefore the
123
+ * `100%` container) shrinks with the canvas, and every `%`-placed child scales
124
+ * with it, so nothing overflows the wrapper into an ancestor's scroll area.
125
+ *
126
+ * @param layer the overlay div (sized `width:100%;height:100%` by the caller).
84
127
  * @param runs per-run + per-shape geometry from `renderSlide({ onTextRun })`.
85
- * @param cssWidth the rendered canvas's CSS width (px, number).
86
- * @param cssHeight the rendered canvas's CSS height (px, number).
128
+ * @param cssWidth the slide's intended CSS width (px, number) — the % denominator.
129
+ * @param cssHeight the slide's intended CSS height (px, number) — the % denominator.
130
+ * @param onHyperlinkClick called with the run's resolved {@link HyperlinkTarget}
131
+ * when a hyperlink span is clicked. Omit to leave links
132
+ * non-interactive (spans stay plain, selectable text).
87
133
  */
88
- export declare function buildPptxTextLayer(layer: HTMLDivElement, runs: PptxTextRunInfo[], cssWidth: number, cssHeight: number): void;
134
+ export declare function buildPptxTextLayer(layer: HTMLDivElement, runs: PptxTextRunInfo[], cssWidth: number, cssHeight: number, onHyperlinkClick?: (target: HyperlinkTarget) => void): void;
89
135
 
90
136
  /**
91
137
  * PPTX bullet marker. The shared core {@link CoreBullet} union
@@ -95,6 +141,13 @@ export declare function buildPptxTextLayer(layer: HTMLDivElement, runs: PptxText
95
141
  */
96
142
  export declare type Bullet = Bullet_2 | BlipBullet;
97
143
 
144
+ /**
145
+ * A paragraph's bullet marker. For `char`, the marker size is EITHER `sizePct`
146
+ * (a percentage of the run size — ECMA-376 §21.1.2.4.9 `<a:buSzPct>`) OR `sizePts`
147
+ * (an absolute size in points — §21.1.2.4.10 `<a:buSzPts>`), never both: they are
148
+ * the one `EG_TextBulletSize` xsd:choice. `sizePts` is optional (absent when no
149
+ * `<a:buSzPts>` was declared); when present it takes precedence over `sizePct`.
150
+ */
98
151
  declare type Bullet_2 = {
99
152
  type: 'none';
100
153
  } | {
@@ -104,11 +157,13 @@ declare type Bullet_2 = {
104
157
  char: string;
105
158
  color: string | null;
106
159
  sizePct: number | null;
160
+ sizePts?: number;
107
161
  fontFamily: string | null;
108
162
  } | {
109
163
  type: 'autoNum';
110
164
  numType: string;
111
165
  startAt: number | null;
166
+ color: string | null;
112
167
  };
113
168
 
114
169
  /**
@@ -136,6 +191,30 @@ declare interface ChartDataLabelOverride {
136
191
  fontSizeHpt?: number;
137
192
  /** `<a:defRPr b="1">` inside the per-idx rich text. */
138
193
  fontBold?: boolean;
194
+ /** Per-point callout box (`<c:dLbl><c:spPr>`, ECMA-376 §21.2.2.47/§21.2.2.197):
195
+ * overrides the series-default box for this one slice. */
196
+ labelBox?: ChartLabelBox;
197
+ /**
198
+ * Per-point label-content flags (`<c:dLbl>` §21.2.2.47 carries the same
199
+ * show-flag group as the series `<c:dLbls>` §21.2.2.49: §21.2.2.189
200
+ * `<c:showVal>`, §21.2.2.177 `<c:showCatName>`, §21.2.2.180 `<c:showSerName>`,
201
+ * §21.2.2.187 `<c:showPercent>`). When present they OVERRIDE the series-level
202
+ * defaults for that one point (e.g. sample-14 slide-7's pie sets
203
+ * `showCatName=0 showPercent=1` per slice while the series default is
204
+ * `showCatName=1`, so each label is percent only). undefined = inherit the
205
+ * series default for that flag.
206
+ */
207
+ showVal?: boolean;
208
+ showCatName?: boolean;
209
+ showSerName?: boolean;
210
+ showPercent?: boolean;
211
+ /**
212
+ * `<c:dLbl><c:delete val="1"/>` (ECMA-376 §21.2.2.43) — the point's label is
213
+ * removed. Distinguishes a genuine delete from a `<c:dLbl>` that only carries
214
+ * style / flag overrides with no `<c:tx>` (both otherwise present as
215
+ * `text === ''`). true = skip the label; undefined/absent = not deleted.
216
+ */
217
+ deleted?: boolean;
139
218
  }
140
219
 
141
220
  declare interface ChartDataPointOverride {
@@ -146,6 +225,18 @@ declare interface ChartDataPointOverride {
146
225
  markerSize?: number;
147
226
  markerFill?: string;
148
227
  markerLine?: string;
228
+ /**
229
+ * `<c:dPt><c:explosion val>` (ECMA-376 §21.2.2.61) — the amount this
230
+ * pie/doughnut slice is moved out from the center. The schema type is
231
+ * `CT_UnsignedInt` (unbounded `xsd:unsignedInt`); the spec text only says
232
+ * "the amount the data point shall be moved from the center of the pie"
233
+ * and does not itself define units or a 0–100 range. We treat it as a
234
+ * de-facto percentage of the outer radius (0–100 typical), matching
235
+ * Office's UI (the Point Explosion slider caps at 100%) rather than a
236
+ * spec-mandated bound. undefined/absent = 0 (no explosion, flush with the
237
+ * ring). Only consulted by the pie/doughnut renderer.
238
+ */
239
+ explosion?: number;
149
240
  }
150
241
 
151
242
  /**
@@ -185,6 +276,74 @@ declare interface ChartErrBars {
185
276
  dash?: string;
186
277
  }
187
278
 
279
+ /**
280
+ * One box-and-whisker series (chartEx `boxWhisker`, MS 2014 chartex ext). Each
281
+ * `<cx:series>` references its own raw sample points via `<cx:dataId>`; the
282
+ * parser groups them by category and threads the `<cx:layoutPr>` flags. The
283
+ * renderer derives the statistics.
284
+ */
285
+ declare interface ChartexBoxSeries {
286
+ /** Series display name (`<cx:tx><cx:v>`). */
287
+ name: string;
288
+ /** Fill (hex, no '#') — theme accent cycled by series index. null = fall
289
+ * back to the renderer palette. */
290
+ color?: string | null;
291
+ /** Raw sample values grouped by category (outer = category index parallel to
292
+ * {@link ChartexBoxWhisker.categories}, inner = the points in that group). */
293
+ valuesByCategory: number[][];
294
+ /** `<cx:visibility meanMarker>` — draw the mean `×`. */
295
+ meanMarker: boolean;
296
+ /** `<cx:visibility meanLine>` — draw a mean connector line across categories. */
297
+ meanLine: boolean;
298
+ /** `<cx:visibility outliers>` — draw outlier points. */
299
+ showOutliers: boolean;
300
+ /** `<cx:visibility nonoutliers>` — draw the interior (non-outlier) sample
301
+ * points as jittered dots on top of the box. Flag parsed; interior-dot
302
+ * rendering is pending a fixture that enables it (every sample-24 series
303
+ * ships `nonoutliers="0"`, so there is nothing to verify against yet). */
304
+ showNonoutliers: boolean;
305
+ /** `<cx:statistics quartileMethod>` — "exclusive" (Excel default) | "inclusive". */
306
+ quartileMethod: string;
307
+ }
308
+
309
+ /** A chartEx box-and-whisker chart: unique categories + one series per column. */
310
+ declare interface ChartexBoxWhisker {
311
+ /** Unique category labels in first-seen order. */
312
+ categories: string[];
313
+ /** One entry per `<cx:series>`. */
314
+ series: ChartexBoxSeries[];
315
+ }
316
+
317
+ /** A chartEx sunburst: the flat rows the renderer folds into a ring tree. */
318
+ declare interface ChartexSunburst {
319
+ rows: ChartexSunburstRow[];
320
+ }
321
+
322
+ /**
323
+ * One row of a chartEx `sunburst`: the branch→…→leaf label chain (empty
324
+ * trailing segments trimmed) and its size value.
325
+ */
326
+ declare interface ChartexSunburstRow {
327
+ /** Label chain root→leaf. */
328
+ path: string[];
329
+ /** `<cx:numDim type="size">` value attaching to the deepest node in `path`. */
330
+ size: number;
331
+ }
332
+
333
+ /** Callout-box style for a pie/doughnut data label — the white (or themed)
334
+ * rounded rectangle with a thin border Word draws around a `bestFit` label
335
+ * placed outside its slice. From the label's `<c:spPr>` (§21.2.2.197). All
336
+ * fields optional: absent → transparent / unbordered. Mirror of Rust
337
+ * `ChartLabelBox`. */
338
+ declare interface ChartLabelBox {
339
+ /** `<a:solidFill>` resolved hex (no `#`). Box background. */
340
+ fill?: string;
341
+ /** `<a:ln><a:solidFill>` resolved hex (no `#`). Border stroke. */
342
+ borderColor?: string;
343
+ /** `<a:ln w>` border width in EMU (12700 EMU = 1 pt). */
344
+ borderWidthEmu?: number;
345
+ }
346
+
188
347
  /**
189
348
  * `<c:manualLayout>` block. Fractions are of the chart-space rect.
190
349
  * `xMode`/`yMode`: "edge" = absolute fraction from top-left, "factor" =
@@ -205,6 +364,14 @@ export declare interface ChartModel {
205
364
  title: string | null;
206
365
  categories: string[];
207
366
  series: ChartSeries[];
367
+ /**
368
+ * §21.2.2.227 `<c:varyColors val="1"/>` on a SINGLE-series bar/column chart:
369
+ * color each data point (bar) from the theme/palette sequence and list one
370
+ * legend entry per point, matching Office. Set by the shared parser ONLY for
371
+ * that non-pie, single-series case (pie/doughnut already vary by point via
372
+ * `chartType` + `dataPointColors`); absent/false otherwise.
373
+ */
374
+ varyColors?: boolean | null;
208
375
  /** Show data labels on bars / points / slices. */
209
376
  showDataLabels: boolean;
210
377
  /** Explicit Y-axis minimum (OOXML `<c:valAx><c:min>`). */
@@ -315,6 +482,35 @@ export declare interface ChartModel {
315
482
  valAxisTitleFontBold?: boolean | null;
316
483
  /** `<c:valAx><c:title>` run-prop color (hex without '#'). null = default. */
317
484
  valAxisTitleFontColor?: string | null;
485
+ /** `<c:catAx><c:txPr>…<a:latin typeface>` tick-label font. */
486
+ catAxisFontFace?: string | null;
487
+ /** `<c:valAx><c:txPr>…<a:latin typeface>` tick-label font. */
488
+ valAxisFontFace?: string | null;
489
+ /** `<c:catAx><c:title>…<a:latin typeface>` axis-title font. */
490
+ catAxisTitleFontFace?: string | null;
491
+ /** `<c:valAx><c:title>…<a:latin typeface>` axis-title font. */
492
+ valAxisTitleFontFace?: string | null;
493
+ /** `<c:dLbls><c:txPr>…<a:latin typeface>` data-label font. */
494
+ dataLabelFontFace?: string | null;
495
+ /** `<c:legend><c:txPr>…<a:latin typeface>` legend font. */
496
+ legendFontFace?: string | null;
497
+ /** `<c:legend><c:txPr>…<a:solidFill>` legend text color (hex without '#'). */
498
+ legendFontColor?: string | null;
499
+ /** `<c:legend><c:txPr>` legend font size (OOXML hundredths of a point). */
500
+ legendFontSizeHpt?: number | null;
501
+ /** `<c:legend><c:txPr>…defRPr@b` legend bold flag. */
502
+ legendFontBold?: boolean | null;
503
+ /**
504
+ * Theme font-scheme faces (`<a:fontScheme>`, ECMA-376 §20.1.4.2). Latin
505
+ * heading (majorFont) and body (minorFont) typefaces, used as the fallback
506
+ * for any chart text element whose own `<c:txPr>` supplies no `<a:latin>`.
507
+ * null when the theme is not threaded to the chart (then the renderer's
508
+ * built-in sans-serif remains, byte-stable). Axis titles / chart title use
509
+ * the major (heading) face; tick labels / data labels / legend use the
510
+ * minor (body) face — matching Office's default chart text styling.
511
+ */
512
+ themeMajorFontLatin?: string | null;
513
+ themeMinorFontLatin?: string | null;
318
514
  /** Explicit chart border color (hex without '#') from
319
515
  * `<c:chartSpace><c:spPr><a:ln><a:solidFill><a:srgbClr>`. Only set when the
320
516
  * XML explicitly declares a paintable line; null otherwise (no default
@@ -389,6 +585,163 @@ export declare interface ChartModel {
389
585
  * value axis (the common case). See {@link SecondaryValueAxis}.
390
586
  */
391
587
  secondaryValAxis?: SecondaryValueAxis | null;
588
+ /**
589
+ * `<c:date1904>` (ECMA-376 §21.2.2.38). When true the chart's serial
590
+ * date-times resolve against the 1904 date system (base 1904-01-01) instead
591
+ * of the default 1900 system. Threaded to the date formatters for date-axis
592
+ * category labels and value-axis tick labels. Omitted/false ⇒ 1900 system.
593
+ * Note: per §21.2.2.38 the element's `val` defaults to true when present but
594
+ * the attribute is omitted, so `<c:date1904/>` alone means date1904=true.
595
+ */
596
+ date1904?: boolean;
597
+ /**
598
+ * `<c:doughnutChart><c:holeSize val>` (ECMA-376 §21.2.2.60,
599
+ * `ST_HoleSizePercent` §21.2.3.55) — the doughnut hole diameter as a
600
+ * percentage 1–90 of the outer diameter. Ignored for pie (which has no
601
+ * hole). null/undefined = use the renderer's doughnut default when the
602
+ * element is absent. Note the ECMA `CT_HoleSize` schema default is 10%, but
603
+ * a real doughnut file always writes an explicit `<c:holeSize>` (Excel /
604
+ * PowerPoint emit 50–75%); the renderer falls back to 50% only for the
605
+ * pathological absent case.
606
+ */
607
+ holeSize?: number | null;
608
+ /**
609
+ * `<c:pieChart | doughnutChart><c:firstSliceAng val>` (ECMA-376 §21.2.2.52,
610
+ * `ST_FirstSliceAng` §21.2.3.15) — the angle in degrees (0–360, clockwise
611
+ * from the 12 o'clock position) at which the first slice begins.
612
+ * null/undefined = 0 (start at 12 o'clock), which matches the renderer's
613
+ * historical fixed −90° (canvas up) start.
614
+ */
615
+ firstSliceAngle?: number | null;
616
+ /**
617
+ * `<c:chartSpace><c:chart><c:dispBlanksAs val>` (ECMA-376 §21.2.2.42,
618
+ * `ST_DispBlanksAs` §21.2.3.10) — how blank (null) cells are plotted on
619
+ * line/area charts:
620
+ * - "gap" → leave a gap (break the line). The renderer's historical
621
+ * behavior and the model default when the element is absent.
622
+ * - "zero" → plot the blank as the value 0 (the point drops to the axis).
623
+ * - "span" → skip the blank but connect its neighbours with a straight
624
+ * line (bridge the gap).
625
+ * Note the XSD `@val` default is "zero" (applies when `<c:dispBlanksAs/>` is
626
+ * present but the attribute is omitted); when the ELEMENT is absent entirely
627
+ * Office falls back to "gap", which is what we model as the default. Only
628
+ * consulted for the line and area families. null/undefined = "gap".
629
+ */
630
+ dispBlanksAs?: string | null;
631
+ /**
632
+ * `<c:valAx><c:majorGridlines>` presence (ECMA-376 §21.2.2.100). `false` when
633
+ * the value axis exists but omits the element (Office suppresses value
634
+ * gridlines). null/undefined ⇒ the renderer's historical always-on value
635
+ * gridlines (byte-stable). `true` is redundant with the default but honored.
636
+ */
637
+ valAxisMajorGridlines?: boolean | null;
638
+ /**
639
+ * `<c:catAx><c:majorGridlines>` presence (§21.2.2.100). `true` turns on
640
+ * category-axis gridlines (Office omits them by default). null/undefined/false
641
+ * ⇒ no category gridlines (the historical default, byte-stable).
642
+ */
643
+ catAxisMajorGridlines?: boolean | null;
644
+ /**
645
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
646
+ * color (hex without `#`) — ECMA-376 §21.2.2.100. When set, the value-axis
647
+ * major gridlines are stroked in this color instead of the renderer's faint
648
+ * `#e0e0e0` default (e.g. sample-1 slide 5's `accent3` gridlines). null/absent
649
+ * ⇒ the historical default (byte-stable).
650
+ */
651
+ valAxisGridlineColor?: string | null;
652
+ /**
653
+ * `<c:valAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. When
654
+ * set, the value-axis gridline stroke width is derived from this (floored so a
655
+ * hairline stays visible). null/absent ⇒ the renderer's 0.5 px default.
656
+ */
657
+ valAxisGridlineWidthEmu?: number | null;
658
+ /**
659
+ * `<c:catAx><c:majorGridlines><c:spPr><a:ln><a:solidFill>` resolved gridline
660
+ * color (hex without `#`). Only meaningful when {@link catAxisMajorGridlines}
661
+ * is on. null/absent ⇒ the faint default.
662
+ */
663
+ catAxisGridlineColor?: string | null;
664
+ /** `<c:catAx><c:majorGridlines><c:spPr><a:ln w>` gridline width in EMU. */
665
+ catAxisGridlineWidthEmu?: number | null;
666
+ /** `<c:valAx><c:minorGridlines>` presence (§21.2.2.109). Only drawn when a
667
+ * minor step is resolvable (see {@link valAxisMinorUnit}). */
668
+ valAxisMinorGridlines?: boolean | null;
669
+ /**
670
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between major
671
+ * gridlines/ticks, overriding the Excel-style auto "nice" step. null/undefined
672
+ * ⇒ auto step (byte-stable).
673
+ */
674
+ valAxisMajorUnit?: number | null;
675
+ /** `<c:valAx><c:minorUnit val>` (§21.2.2.112) — explicit minor step. Drives
676
+ * minor gridlines/ticks when present. null ⇒ no minor divisions. */
677
+ valAxisMinorUnit?: number | null;
678
+ /**
679
+ * `<c:valAx><c:scaling><c:logBase val>` (§21.2.2.98, `ST_LogBase` §21.2.3.25)
680
+ * — logarithmic value-axis base (>= 2). When set, values map to pixels in log
681
+ * space and gridlines fall on powers of the base. null/undefined ⇒ linear
682
+ * (byte-stable).
683
+ */
684
+ valAxisLogBase?: number | null;
685
+ /**
686
+ * `<c:valAx><c:scaling><c:orientation val>` (§21.2.2.130, `ST_Orientation`
687
+ * §21.2.3.30) — "minMax" (normal) | "maxMin" (reversed, so the value axis runs
688
+ * top→bottom max→min). null/undefined/"minMax" ⇒ normal (byte-stable).
689
+ */
690
+ valAxisOrientation?: 'minMax' | 'maxMin' | string | null;
691
+ /** `<c:catAx><c:scaling><c:orientation val>` — "maxMin" reverses the category
692
+ * axis left↔right. null/"minMax" ⇒ normal. */
693
+ catAxisOrientation?: 'minMax' | 'maxMin' | string | null;
694
+ /**
695
+ * `<c:catAx><c:tickLblPos val>` (§21.2.2.207, `ST_TickLblPos` §21.2.3.47) —
696
+ * "nextTo" (default) | "low" | "high" | "none". "none" hides the category tick
697
+ * labels. null/undefined ⇒ nextTo (byte-stable).
698
+ */
699
+ catAxisTickLabelPos?: string | null;
700
+ /** `<c:valAx><c:tickLblPos val>` (§21.2.2.207). "none" hides value tick labels. */
701
+ valAxisTickLabelPos?: string | null;
702
+ /**
703
+ * `<c:catAx><c:txPr><a:bodyPr rot>` (DrawingML `ST_Angle`, 60000ths of a
704
+ * degree) — category tick-label rotation. e.g. -2700000 = -45°. null/undefined
705
+ * /0 ⇒ horizontal labels (byte-stable).
706
+ */
707
+ catAxisLabelRotation?: number | null;
708
+ /**
709
+ * `<c:stockChart><c:hiLowLines>` presence (ECMA-376 §21.2.2.60). When true
710
+ * the stock renderer draws a vertical line spanning each category's low↔high
711
+ * value. Only set for `chartType === "stock"`; null/undefined on every other
712
+ * chart type (byte-stable).
713
+ */
714
+ stockHiLowLines?: boolean | null;
715
+ /**
716
+ * `<c:hiLowLines><c:spPr><a:ln><a:solidFill>` resolved color (hex, no `#`).
717
+ * null = the renderer's default gray hi-lo line.
718
+ */
719
+ stockHiLowLineColor?: string | null;
720
+ /**
721
+ * `<c:stockChart><c:upDownBars>` presence (ECMA-376 §21.2.2.227). Parsed so a
722
+ * stock file carrying open-close up/down bars is recognized; the renderer does
723
+ * NOT yet draw them (tracked follow-up). null/undefined when absent.
724
+ */
725
+ stockUpDownBars?: boolean | null;
726
+ /**
727
+ * Structured box-and-whisker data (`chartType === 'boxWhisker'`). Present
728
+ * ONLY for boxWhisker charts; null/absent otherwise so the flat
729
+ * `categories`/`series` model the other chartEx renderers consume is
730
+ * untouched. The renderer computes quartiles / mean / whiskers / outliers.
731
+ */
732
+ chartexBox?: ChartexBoxWhisker | null;
733
+ /**
734
+ * Structured sunburst hierarchy (`chartType === 'sunburst'`). Present ONLY
735
+ * for sunburst charts; null/absent otherwise.
736
+ */
737
+ chartexSunburst?: ChartexSunburst | null;
738
+ /**
739
+ * Theme accent palette (`accent1..6`, hex without '#') for chartEx charts
740
+ * that color by branch/series index (boxWhisker series, sunburst branches).
741
+ * null/absent when the resolver supplies no default palette (pptx); the
742
+ * renderer then falls back to its own `CHART_PALETTE`.
743
+ */
744
+ chartexAccents?: string[] | null;
392
745
  }
393
746
 
394
747
  export declare interface ChartSeries {
@@ -498,6 +851,31 @@ export declare interface ChartSeries {
498
851
  * series.
499
852
  */
500
853
  bubbleSizes?: (number | null)[] | null;
854
+ /**
855
+ * `<c:ser><c:smooth val>` (ECMA-376 §21.2.2.194) — line/area series flag
856
+ * requesting a smoothed (spline) curve through the points instead of straight
857
+ * segments. Only consulted for the line and area families (scatter carries its
858
+ * smoothing in `ChartModel.scatterStyle`). null/undefined/false = straight
859
+ * polyline (the default; byte-stable for series that never set it).
860
+ */
861
+ smooth?: boolean | null;
862
+ /**
863
+ * `<c:ser><c:trendline>` per-series trendlines (ECMA-376 §21.2.2.211,
864
+ * `CT_Trendline`). A series can carry several (e.g. a linear fit + a moving
865
+ * average). null/undefined/empty = no trendline (the default; byte-stable for
866
+ * series that never declare one).
867
+ */
868
+ trendLines?: ChartTrendline[] | null;
869
+ /**
870
+ * `<c:ser><c:spPr><a:ln><a:noFill/>` (ECMA-376 §21.2.2.198 CT_ShapeProperties
871
+ * → DrawingML §20.1.2.2.24 CT_LineProperties). true when the series connecting
872
+ * line is explicitly turned OFF. For a scatter/line series this OVERRIDES the
873
+ * chart-group `<c:scatterStyle>` (§21.2.2.42) / line default — Excel and
874
+ * PowerPoint draw markers only (no connecting line) even when the group style
875
+ * is `lineMarker`. null/undefined = no explicit line-off, so the group default
876
+ * governs (byte-stable for series that carry a paintable line).
877
+ */
878
+ lineHidden?: boolean | null;
501
879
  }
502
880
 
503
881
  declare interface ChartSeriesDataLabels {
@@ -512,6 +890,51 @@ declare interface ChartSeriesDataLabels {
512
890
  fontBold?: boolean;
513
891
  /** Series-level font size for data labels (OOXML hundredths of a point). */
514
892
  fontSizeHpt?: number;
893
+ /** Series-default callout box (`<c:dLbls><c:spPr>`, ECMA-376 §21.2.2.49/
894
+ * §21.2.2.197). When present the pie/doughnut renderer draws Word's boxed
895
+ * callout layout (box + optional leader line) instead of plain text. */
896
+ labelBox?: ChartLabelBox;
897
+ /** `<c:dLbls><c:showLeaderLines val>` (§21.2.2.183) — draw leader lines from
898
+ * a pulled-away label back to its slice. Default false. */
899
+ showLeaderLines?: boolean;
900
+ /** `<c:leaderLines><c:spPr><a:ln><a:solidFill>` (§21.2.2.92) resolved hex
901
+ * (no `#`). undefined → renderer uses a neutral grey. */
902
+ leaderLineColor?: string;
903
+ /** `<c:leaderLines><c:spPr><a:ln w>` leader-line width in EMU. */
904
+ leaderLineWidthEmu?: number;
905
+ }
906
+
907
+ /**
908
+ * `<c:ser><c:trendline>` (ECMA-376 §21.2.2.211). A regression/smoothing curve
909
+ * fitted to the series' data points.
910
+ */
911
+ declare interface ChartTrendline {
912
+ /**
913
+ * `<c:trendlineType val>` (§21.2.2.213, `ST_TrendlineType` §21.2.3.50):
914
+ * "linear" | "exp" | "log" | "power" | "poly" | "movingAvg". The renderer
915
+ * currently draws "linear" (least squares) and "movingAvg"; other types parse
916
+ * but are not yet plotted (tracked as a follow-up).
917
+ */
918
+ trendlineType: string;
919
+ /** `<c:order val>` — polynomial order (`poly`, default 2). */
920
+ order?: number | null;
921
+ /** `<c:period val>` — moving-average window (`movingAvg`, default 2). */
922
+ period?: number | null;
923
+ /** `<c:forward val>` — units to extend the line past the last point. */
924
+ forward?: number | null;
925
+ /** `<c:backward val>` — units to extend the line before the first point. */
926
+ backward?: number | null;
927
+ /** `<c:intercept val>` — forced y-intercept (linear/exp). null = free fit. */
928
+ intercept?: number | null;
929
+ /** `<c:dispRSqr val="1">` — show the R² value (label; not yet rendered). */
930
+ dispRSqr?: boolean | null;
931
+ /** `<c:dispEq val="1">` — show the fit equation (label; not yet rendered). */
932
+ dispEq?: boolean | null;
933
+ /** `<c:spPr><a:ln><a:solidFill>` trendline color (hex without '#'). null =
934
+ * inherit the series color. */
935
+ lineColor?: string | null;
936
+ /** `<c:spPr><a:ln w>` trendline width in EMU. */
937
+ lineWidthEmu?: number | null;
515
938
  }
516
939
 
517
940
  /**
@@ -519,7 +942,7 @@ declare interface ChartSeriesDataLabels {
519
942
  * grouping (`Pct` = percent-stacked) so renderers do not need to inspect
520
943
  * separate `barDir`/`grouping` fields.
521
944
  */
522
- declare type ChartType = 'line' | 'stackedLine' | 'stackedLinePct' | 'clusteredBar' | 'clusteredBarH' | 'stackedBar' | 'stackedBarH' | 'stackedBarPct' | 'stackedBarHPct' | 'area' | 'stackedArea' | 'stackedAreaPct' | 'pie' | 'doughnut' | 'scatter' | 'bubble' | 'radar' | 'waterfall' | string;
945
+ 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;
523
946
 
524
947
  /**
525
948
  * Translucent overlay drawn over a finished slide so it reads faintly
@@ -535,6 +958,19 @@ export declare interface DimOptions {
535
958
  opacity: number;
536
959
  }
537
960
 
961
+ /** A duotone effect resolved to its two endpoint colours. Both are 6-char
962
+ * uppercase hex WITHOUT a leading `#` (the form the Rust parsers emit). `clr1`
963
+ * is the dark endpoint (luminance 0), `clr2` the light endpoint (luminance 1),
964
+ * matching the child order of `<a:duotone>` in §20.1.8.23. Any per-colour
965
+ * transforms (lumMod/lumOff/tint/satMod/…) are already baked into these hexes
966
+ * by the parser's colour-resolution machinery. */
967
+ declare interface Duotone {
968
+ /** First `EG_ColorChoice` child — the dark endpoint. 6-char hex, no `#`. */
969
+ clr1: string;
970
+ /** Second `EG_ColorChoice` child — the light endpoint. 6-char hex, no `#`. */
971
+ clr2: string;
972
+ }
973
+
538
974
  /**
539
975
  * An OMML equation embedded in a paragraph (ECMA-376 §22.1). Parsed into the
540
976
  * shared math AST and rendered by `@silurus/ooxml-core`'s math engine.
@@ -567,6 +1003,47 @@ export declare interface FillRect {
567
1003
  b?: number;
568
1004
  }
569
1005
 
1006
+ /**
1007
+ * IX2 public find-result shape, shared by all three viewers.
1008
+ *
1009
+ * `findText` returns an ordered list of {@link FindMatch}. Every match carries
1010
+ * its ordinal position (`matchIndex`, 0-based, document order — the same index
1011
+ * `findNext` / `findPrev` cycle through), the matched `text`, and a
1012
+ * format-specific `location`. The location is where the three formats
1013
+ * legitimately differ — a docx match lives on a page, a pptx match on a slide,
1014
+ * an xlsx match in a sheet cell — so `FindMatch` is generic over it rather than
1015
+ * forcing an artificial common shape. Each viewer instantiates it with its own
1016
+ * location type:
1017
+ *
1018
+ * - `DocxViewer.findText` → `FindMatch<DocxMatchLocation>` ({ page })
1019
+ * - `PptxViewer.findText` → `FindMatch<PptxMatchLocation>` ({ slide })
1020
+ * - `XlsxViewer.findText` → `FindMatch<XlsxMatchLocation>` ({ sheet, ref, … })
1021
+ *
1022
+ * The generic default is `unknown` so `FindMatch` can be referenced without a
1023
+ * type argument (e.g. in generic UI code) while each viewer's return type stays
1024
+ * precise.
1025
+ */
1026
+ export declare interface FindMatch<Loc = unknown> {
1027
+ /** 0-based ordinal among all matches, in document order. This is the index
1028
+ * `findNext`/`findPrev` make active, so a caller can correlate the array it
1029
+ * got from `findText` with the active-match reported by navigation. */
1030
+ matchIndex: number;
1031
+ /** The text that matched (the query as it appears in the document — its
1032
+ * original case, not the folded form used for case-insensitive matching). */
1033
+ text: string;
1034
+ /** Where the match is, in the format's own coordinates. */
1035
+ location: Loc;
1036
+ }
1037
+
1038
+ /** Options for {@link findMatches}. */
1039
+ export declare interface FindMatchesOptions {
1040
+ /**
1041
+ * Match case exactly. Default `false` (case-insensitive, like a browser's
1042
+ * find-in-page). IX2 default — an integrator can pass `true`.
1043
+ */
1044
+ caseSensitive?: boolean;
1045
+ }
1046
+
570
1047
  /** ECMA-376 §20.1.8.17 (CT_GlowEffect) — coloured halo with blur radius. */
571
1048
  export declare interface Glow {
572
1049
  color: string;
@@ -592,6 +1069,49 @@ export declare interface GradientStop {
592
1069
  /** How {@link PptxViewer} presents hidden slides (`<p:sld show="0">`). */
593
1070
  export declare type HiddenSlideMode = 'show' | 'skip' | 'dim';
594
1071
 
1072
+ /**
1073
+ * Shared hyperlink model + URL sanitisation for docx / pptx / xlsx (IX1).
1074
+ *
1075
+ * All three formats carry the same two ECMA-376 concepts:
1076
+ * - an **external** hyperlink — an absolute URL resolved from a relationship
1077
+ * part target (`document.xml.rels` for docx §17.16.22, the slide rels for
1078
+ * pptx §21.1.2.3.5, the worksheet rels for xlsx §18.3.1.47), with
1079
+ * `TargetMode="External"`.
1080
+ * - an **internal** hyperlink — a jump within the document itself:
1081
+ * docx `w:anchor` -> a `<w:bookmarkStart w:name>` (§17.16.23), pptx
1082
+ * `action="ppaction://hlinksldjump"` -> a slide, xlsx `location` -> a defined
1083
+ * name or a `Sheet!A1` cell reference.
1084
+ *
1085
+ * The parsers (Rust, one per format) do the format-specific rels lookup and hand
1086
+ * each run / shape / cell a {@link HyperlinkTarget}. Everything downstream — the
1087
+ * text-layer overlay, the viewer default click behaviour, and any integrator
1088
+ * callback — is format-agnostic and consumes this one shape. Keeping the type +
1089
+ * the pure `sanitizeHyperlinkUrl` predicate here (not duplicated per package)
1090
+ * follows the cross-package unification principle: a scheme-allowlist bug fixed
1091
+ * once is fixed everywhere.
1092
+ */
1093
+ /**
1094
+ * A resolved hyperlink attached to a run, shape, or cell.
1095
+ *
1096
+ * - `external` — `url` is the raw target as authored in the file. It is NOT
1097
+ * guaranteed safe; run it through {@link sanitizeHyperlinkUrl} before
1098
+ * navigating. It is kept verbatim here so an integrator can apply its own
1099
+ * policy (e.g. allow `file:` on a trusted intranet viewer).
1100
+ * - `internal` — `ref` is the in-document destination, verbatim from the file:
1101
+ * docx: the bookmark name (`w:anchor`).
1102
+ * pptx: the internal action (e.g. `ppaction://hlinksldjump`), with the
1103
+ * resolved 0-based `slideIndex` when the rels target names a slide.
1104
+ * xlsx: the `location` string (a defined name or `Sheet1!A1`).
1105
+ */
1106
+ export declare type HyperlinkTarget = {
1107
+ kind: 'external';
1108
+ url: string;
1109
+ } | {
1110
+ kind: 'internal';
1111
+ ref: string;
1112
+ slideIndex?: number;
1113
+ };
1114
+
595
1115
  /**
596
1116
  * Image fill — ECMA-376 §20.1.8.14 (CT_BlipFillProperties). The embedded blip
597
1117
  * is carried as a zip path + MIME; the renderer fetches the bytes on demand via
@@ -621,6 +1141,14 @@ export declare interface ImageFill {
621
1141
  tile?: TileInfo;
622
1142
  /** `a:blip > a:alphaModFix@amt` as a fraction (0.0–1.0). Absent = opaque. */
623
1143
  alpha?: number;
1144
+ /**
1145
+ * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
1146
+ * colours (through the slide theme). Absent ⇒ no duotone. When present the
1147
+ * renderer maps the blip's luminance ramp between the two colours (core
1148
+ * `applyDuotone`) — the same recolour a `<p:pic>` duotone applies, wired onto
1149
+ * the picture-FILL path (§20.1.8.14) by issue #889.
1150
+ */
1151
+ duotone?: Duotone;
624
1152
  }
625
1153
 
626
1154
  declare interface LegendManualLayout {
@@ -684,6 +1212,35 @@ declare interface LoadOptions_2 {
684
1212
  * via `@font-face` in your application CSS.
685
1213
  */
686
1214
  useGoogleFonts?: boolean;
1215
+ /**
1216
+ * Password for an encrypted OOXML file ([MS-OFFCRYPTO] Agile Encryption).
1217
+ *
1218
+ * Password-protected Office documents are CFB (OLE2) containers, not ZIPs.
1219
+ * When this is set and the input is Agile-encrypted, `load()` decrypts it on
1220
+ * the main thread (via WebCrypto) and parses the recovered plaintext ZIP.
1221
+ *
1222
+ * Errors (thrown as {@link import('../errors/ooxml-error').OoxmlError}):
1223
+ * - no `password` on an encrypted file → code `'encrypted'`
1224
+ * - wrong `password` → code `'invalid-password'`
1225
+ * - a non-Agile scheme (Standard / Extensible / legacy) → code
1226
+ * `'unsupported-encryption'`
1227
+ *
1228
+ * Note: Agile Encryption uses a high password-hash spin count (commonly
1229
+ * 100,000), so decryption of a protected file adds roughly a second of
1230
+ * WebCrypto work before parsing begins.
1231
+ *
1232
+ * Security notes:
1233
+ * - This value is held as an ordinary JS `string` in memory for the
1234
+ * duration of key derivation. The library does not zero it, and does
1235
+ * not wrap it in a `SecureString`-equivalent — it becomes eligible for
1236
+ * garbage collection like any other string once nothing references it,
1237
+ * but no explicit wipe is performed. It is never logged or included in
1238
+ * thrown errors.
1239
+ * - Decryption recovers the plaintext but does not verify the file's HMAC
1240
+ * data-integrity tag ([MS-OFFCRYPTO] §2.3.4.14), so ciphertext tampering
1241
+ * is not detected — see "Security & Privacy" in the README.
1242
+ */
1243
+ password?: string;
687
1244
  /**
688
1245
  * Override the URL the parser worker fetches the WebAssembly module from.
689
1246
  *
@@ -728,6 +1285,23 @@ declare interface LoadOptions_2 {
728
1285
  math?: MathRenderer;
729
1286
  }
730
1287
 
1288
+ /**
1289
+ * The slice of one run a match covers: the run's index in the original `runs[]`
1290
+ * and the `[start, end)` character range within that run's own `text`. A match
1291
+ * that straddles N runs yields N of these (the first sliced from its start
1292
+ * offset to the run end, the last from 0 to its end offset, any middle run
1293
+ * whole). The viewer measures each slice against that run's font to get a pixel
1294
+ * rectangle.
1295
+ */
1296
+ declare interface MatchRunSlice {
1297
+ /** Index into the original `runs[]` handed to {@link buildTextIndex}. */
1298
+ runIndex: number;
1299
+ /** Start offset within `runs[runIndex].text` (inclusive). */
1300
+ start: number;
1301
+ /** End offset within `runs[runIndex].text` (exclusive). */
1302
+ end: number;
1303
+ }
1304
+
731
1305
  /** Accent (`m:acc`), e.g. hat, bar, vector arrow over the base. */
732
1306
  declare interface MathAccent {
733
1307
  kind: 'accent';
@@ -750,6 +1324,31 @@ declare interface MathBar {
750
1324
  base: MathNode[];
751
1325
  }
752
1326
 
1327
+ /** Border-box object (`m:borderBox`, §22.1.2.11): a border/strikes around the
1328
+ * base. Absent flags ⇒ a full rectangular box. */
1329
+ declare interface MathBorderBox {
1330
+ kind: 'borderBox';
1331
+ /** §22.1.2 hide* — when true the corresponding edge is NOT drawn. */
1332
+ hideTop?: boolean;
1333
+ hideBot?: boolean;
1334
+ hideLeft?: boolean;
1335
+ hideRight?: boolean;
1336
+ /** §22.1.2 strike* — strikeBLTR = bottom-left→top-right, strikeTLBR =
1337
+ * top-left→bottom-right diagonal. */
1338
+ strikeH?: boolean;
1339
+ strikeV?: boolean;
1340
+ strikeBltr?: boolean;
1341
+ strikeTlbr?: boolean;
1342
+ base: MathNode[];
1343
+ }
1344
+
1345
+ /** Box object (`m:box`, §22.1.2.13): a logical grouping (operator emulator /
1346
+ * line-break control). Draws NO border — a transparent group around `base`. */
1347
+ declare interface MathBox {
1348
+ kind: 'box';
1349
+ base: MathNode[];
1350
+ }
1351
+
753
1352
  declare interface MathDelimiter {
754
1353
  kind: 'delimiter';
755
1354
  /** opening char (default '('). */
@@ -807,7 +1406,23 @@ declare interface MathNary {
807
1406
  body: MathNode[];
808
1407
  }
809
1408
 
810
- declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup;
1409
+ declare type MathNode = MathRun | MathFraction | MathScript | MathNary | MathDelimiter | MathRadical | MathLimit | MathArray | MathGroupChr | MathBar | MathAccent | MathFunc | MathGroup | MathPhant | MathSPre | MathBox | MathBorderBox;
1410
+
1411
+ /** Phantom object (`m:phant`, §22.1.2.81): contributes the spacing of `base`
1412
+ * while optionally hiding it and/or zeroing individual dimensions. */
1413
+ declare interface MathPhant {
1414
+ kind: 'phant';
1415
+ /** §22.1.2.96 `m:show` — `false` hides the base (invisible but occupies space,
1416
+ * i.e. `<mphantom>`); `true` (default) shows it and the phant only tweaks
1417
+ * spacing. */
1418
+ show: boolean;
1419
+ /** §22.1.2 zeroWid / zeroAsc / zeroDesc — suppress width / ascent / descent so
1420
+ * the base takes no space along that axis. Omitted ⇒ false. */
1421
+ zeroWid?: boolean;
1422
+ zeroAsc?: boolean;
1423
+ zeroDesc?: boolean;
1424
+ base: MathNode[];
1425
+ }
811
1426
 
812
1427
  declare interface MathRadical {
813
1428
  kind: 'radical';
@@ -850,6 +1465,15 @@ declare interface MathScript {
850
1465
  sub?: MathNode[];
851
1466
  }
852
1467
 
1468
+ /** Pre-sub-superscript object (`m:sPre`, §22.1.2.99): sub + sup to the LEFT of
1469
+ * the base (e.g. ²₁A). */
1470
+ declare interface MathSPre {
1471
+ kind: 'sPre';
1472
+ sub: MathNode[];
1473
+ sup: MathNode[];
1474
+ base: MathNode[];
1475
+ }
1476
+
853
1477
  declare type MathStyle = 'roman' | 'italic' | 'bold' | 'boldItalic';
854
1478
 
855
1479
  declare interface MathSvg {
@@ -883,6 +1507,63 @@ export declare interface NoFill {
883
1507
  fillType: 'none';
884
1508
  }
885
1509
 
1510
+ /**
1511
+ * Typed error thrown by the docx / pptx / xlsx `load()` factories for failures
1512
+ * that carry a stable, programmatic {@link OoxmlErrorCode} (e.g. a
1513
+ * password-protected or legacy-binary file detected from its container magic).
1514
+ *
1515
+ * Note on workers: `instanceof OoxmlError` does not survive a structured-clone
1516
+ * across the worker boundary. Detection that needs a typed error is therefore
1517
+ * done on the main thread (before the worker is involved) so a genuine
1518
+ * `OoxmlError` instance is thrown to the caller. Errors that must cross the
1519
+ * worker boundary should carry the `code` string and be reconstructed on the
1520
+ * main side.
1521
+ */
1522
+ export declare class OoxmlError extends Error {
1523
+ readonly code: OoxmlErrorCode;
1524
+ constructor(code: OoxmlErrorCode, message: string);
1525
+ }
1526
+
1527
+ /**
1528
+ * Machine-readable code for a typed load-time failure.
1529
+ *
1530
+ * The container-level failures the `load()` factories detect on the main thread
1531
+ * before handing bytes to the parser worker (see `sniffCfb` / `decryptOoxml`).
1532
+ * This is the seed of the broader typed-error surface tracked as PD4 (OoxmlError
1533
+ * typed errors). Add codes here rather than throwing bare `Error(string)`, so
1534
+ * callers can `switch` on `err.code` instead of matching message text.
1535
+ *
1536
+ * - `'encrypted'` — password-protected, but no `password` was
1537
+ * supplied (pass `LoadOptions.password` to decrypt).
1538
+ * - `'invalid-password'` — a `password` was supplied but did not match.
1539
+ * - `'unsupported-encryption'`— encrypted with a scheme other than Agile
1540
+ * (Standard / Extensible / a legacy binary encryptor), which this library
1541
+ * cannot decrypt (PD8 implements Agile only).
1542
+ * - `'legacy-binary-format'` — a raw .doc / .xls / .ppt (not OOXML).
1543
+ * - `'not-ooxml'` — a CFB of an unrecognised kind, or otherwise
1544
+ * not an OOXML ZIP.
1545
+ */
1546
+ export declare type OoxmlErrorCode = 'encrypted' | 'invalid-password' | 'unsupported-encryption' | 'legacy-binary-format' | 'not-ooxml';
1547
+
1548
+ /**
1549
+ * The default action a viewer takes for an **external** hyperlink click when
1550
+ * the integrator supplies no `onHyperlinkClick` handler: sanitise the URL and,
1551
+ * if allowed, open it in a new tab with `noopener,noreferrer` so the opened page
1552
+ * gets no `window.opener` handle back into this document. A blocked scheme is a
1553
+ * silent no-op (returns `false`) — the click does nothing rather than navigate
1554
+ * somewhere dangerous.
1555
+ *
1556
+ * Internal targets are intentionally NOT handled here: the in-document jump
1557
+ * (page / slide / cell) is format-specific and lives in each viewer.
1558
+ *
1559
+ * Split out (not inlined in three viewers) so the "open in new tab, drop opener,
1560
+ * refuse unsafe schemes" policy is defined once. `win` is injected for tests;
1561
+ * defaults to the ambient `window`.
1562
+ *
1563
+ * @returns `true` if navigation was initiated, `false` if the URL was blocked.
1564
+ */
1565
+ export declare function openExternalHyperlink(url: string, allowed?: readonly string[], win?: Pick<Window, 'open'> | undefined): boolean;
1566
+
886
1567
  /**
887
1568
  * PPTX paragraph. Extends the shared core `Paragraph` with the PPTX-only
888
1569
  * `eaLnBrk` flag that the pptx parser emits but the shared core model does not
@@ -905,6 +1586,14 @@ export declare interface Paragraph extends Paragraph_2 {
905
1586
  * emits an effective boolean.
906
1587
  */
907
1588
  eaLnBrk: boolean;
1589
+ /**
1590
+ * `<a:pPr defTabSz>` (ECMA-376 §21.1.2.2.7) — the default tab-stop interval in
1591
+ * EMU. When a `\t` has no reachable explicit `a:tabLst` stop, it advances to
1592
+ * the next multiple of this grid (issue #1006). Absent ⇒ the renderer uses the
1593
+ * PowerPoint universal default of 914400 EMU (1 inch). Omitted from JSON when
1594
+ * the parser found no explicit value.
1595
+ */
1596
+ defTabSz?: number;
908
1597
  }
909
1598
 
910
1599
  declare interface Paragraph_2 {
@@ -1053,6 +1742,13 @@ export declare interface PictureElement {
1053
1742
  };
1054
1743
  /** a:blip > a:alphaModFix@amt as 0..1. Undefined = fully opaque. */
1055
1744
  alpha?: number;
1745
+ /**
1746
+ * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
1747
+ * colours (through the slide theme). Undefined ⇒ no duotone. When present the
1748
+ * renderer decodes the raster once, remaps it along the `clr1`→`clr2`
1749
+ * luminance ramp, and caches the recoloured bitmap under a colour-suffixed key.
1750
+ */
1751
+ duotone?: Duotone;
1056
1752
  /**
1057
1753
  * `<p:spPr><a:custGeom>` clipping path. Same `PathCmd` model as
1058
1754
  * `ShapeElement.custGeom` (one entry per `<a:path>`; coords normalized
@@ -1095,6 +1791,21 @@ export declare interface PptxComment {
1095
1791
  text: string;
1096
1792
  }
1097
1793
 
1794
+ export declare interface PptxHighlightColors {
1795
+ match?: string;
1796
+ active?: string;
1797
+ }
1798
+
1799
+ export declare interface PptxHighlightMatch {
1800
+ slices: MatchRunSlice[];
1801
+ active: boolean;
1802
+ }
1803
+
1804
+ /** Where a pptx match lives: its 0-based slide index. */
1805
+ export declare interface PptxMatchLocation {
1806
+ slide: number;
1807
+ }
1808
+
1098
1809
  /**
1099
1810
  * Headless PPTX rendering engine.
1100
1811
  *
@@ -1116,15 +1827,24 @@ export declare class PptxPresentation {
1116
1827
  private _mode;
1117
1828
  private _presentation;
1118
1829
  private _meta;
1830
+ /** Lazily-built `partName → slide index` map for internal hyperlink slide
1831
+ * jumps (IX-nav). Cleared on {@link destroy}; built on first
1832
+ * {@link getSlideIndexByPartName}/{@link resolveInternalTarget} from either
1833
+ * the parsed slides (main) or the worker meta's `partNames` (worker). */
1834
+ private _slidePartIndex;
1119
1835
  private _mediaCache;
1120
1836
  private _imageCache;
1837
+ /** Google-Fonts `FontFace` objects this deck preloaded into `document.fonts`
1838
+ * (main mode only — in worker mode the worker owns them and terminates with
1839
+ * its own FontFaceSet). Released in {@link destroy} so they do not leak into
1840
+ * the shared FontFaceSet for the lifetime of the SPA (deduped + refcounted in
1841
+ * core, so a web font shared with another open deck survives until both go). */
1842
+ private _googleFontFaces;
1121
1843
  /** One stable closure per instance: the decoded-bitmap and SVG caches key on
1122
1844
  * this identity to scope decodes per deck (so two open decks never swap
1123
1845
  * images for a shared zip path like ppt/media/image1.png). Reusing the same
1124
1846
  * reference across every render also lets those caches hit across slides. */
1125
1847
  private readonly _fetchImage;
1126
- private _workerReady;
1127
- private _workerReadyCallbacks;
1128
1848
  /** Opt-in OMML equation engine, injected once at {@link load}. Every
1129
1849
  * `renderSlide` / `presentSlide` reuses it — equations render when present,
1130
1850
  * and are skipped (engine tree-shaken) when omitted. */
@@ -1132,7 +1852,6 @@ export declare class PptxPresentation {
1132
1852
  private constructor();
1133
1853
  /** Parse a PPTX from URL or ArrayBuffer. */
1134
1854
  static load(source: string | ArrayBuffer, opts?: LoadOptions): Promise<PptxPresentation>;
1135
- private _waitForWorker;
1136
1855
  private _parse;
1137
1856
  /** Total number of slides in the loaded presentation. */
1138
1857
  get slideCount(): number;
@@ -1173,6 +1892,44 @@ export declare class PptxPresentation {
1173
1892
  * caller's policy (see {@link PptxViewer}'s `hiddenSlideMode` modes).
1174
1893
  */
1175
1894
  isHidden(slideIndex: number): boolean;
1895
+ /** The per-slide `partName` array (`sldIdLst` order) from either the parsed
1896
+ * model (main) or the worker meta (worker). Backs the lazy part-index map. */
1897
+ private _partNames;
1898
+ /** Lazily build (and cache) the `partName → index` map. Nulled by
1899
+ * {@link destroy} so a reused reference never serves a stale deck's indices. */
1900
+ private _partIndex;
1901
+ /**
1902
+ * Resolve a slide's OPC part name (e.g. `ppt/slides/slide3.xml`) to its
1903
+ * 0-based index in `sldIdLst` order, or `undefined` when no slide has that
1904
+ * part name. This is the map an internal hyperlink slide jump
1905
+ * (`<a:hlinkClick action="ppaction://hlinksldjump" r:id>`, ECMA-376
1906
+ * §21.1.2.3.5) resolves against: the click's rel Target names a slide part, and
1907
+ * this turns it into the index a viewer can navigate to. Works in both `main`
1908
+ * and `worker` mode (the part names ride along in the worker meta).
1909
+ */
1910
+ getSlideIndexByPartName(partName: string): number | undefined;
1911
+ /**
1912
+ * Resolve an internal hyperlink target string to a 0-based slide index, or
1913
+ * `undefined` when it names no reachable slide. Handles both
1914
+ * `<a:hlinkClick @action>` classes (§21.1.2.3.5):
1915
+ *
1916
+ * - a **relative** show jump — `ppaction://hlinkshowjump?jump=firstslide |
1917
+ * lastslide | nextslide | previousslide` — resolved arithmetically from
1918
+ * `currentIndex` (clamped at the deck ends);
1919
+ * - a **specific** slide-part jump — `ppaction://hlinksldjump`, whose
1920
+ * resolved target is a slide-rel part name like `../slides/slide3.xml` —
1921
+ * resolved through {@link getSlideIndexByPartName}.
1922
+ *
1923
+ * `ref` is the internal reference a `HyperlinkTarget` of kind `'internal'`
1924
+ * carries: the raw `ppaction://…` action string for a relative jump, or the
1925
+ * resolved slide-part target string for a specific jump. A viewer's
1926
+ * `onHyperlinkClick` default calls this with `ref` and the current slide, then
1927
+ * navigates to the returned index.
1928
+ *
1929
+ * @param ref the internal action/target string.
1930
+ * @param currentIndex the 0-based slide the jump is relative to (default 0).
1931
+ */
1932
+ resolveInternalTarget(ref: string, currentIndex?: number): number | undefined;
1176
1933
  /** Render a slide onto the given canvas. */
1177
1934
  renderSlide(canvas: HTMLCanvasElement | OffscreenCanvas, slideIndex: number, opts?: RenderSlideOptions): Promise<void>;
1178
1935
  /**
@@ -1185,6 +1942,16 @@ export declare class PptxPresentation {
1185
1942
  * when done, or its backing memory is held until GC.
1186
1943
  */
1187
1944
  renderSlideToBitmap(slideIndex: number, opts?: RenderSlideToBitmapOptions): Promise<ImageBitmap>;
1945
+ /**
1946
+ * IX6 — collect a slide's text-run geometry (`PptxTextRunInfo[]`) without
1947
+ * painting a visible canvas. Works in BOTH modes: worker mode renders the
1948
+ * slide off-thread and ships only the runs (no bitmap transfer); main mode
1949
+ * renders to a throwaway offscreen canvas. Used by the find controller to scan
1950
+ * every slide for matches. Run geometry is in CSS px (independent of dpr) and
1951
+ * dimming does not move glyphs, so only `width` is threaded — matching the
1952
+ * historical main-mode `_collectSlideRuns`.
1953
+ */
1954
+ collectSlideRuns(slideIndex: number, width?: number): Promise<PptxTextRunInfo[]>;
1188
1955
  /**
1189
1956
  * Extract raw media bytes for a zip path referenced by {@link MediaElement}.
1190
1957
  * Results are cached by path for the lifetime of this instance.
@@ -1199,6 +1966,23 @@ export declare class PptxPresentation {
1199
1966
  * decoded lazily rather than inlined as base64 at parse time.
1200
1967
  */
1201
1968
  getImage(imagePath: string, mimeType: string): Promise<Blob>;
1969
+ /**
1970
+ * Project the presentation to GitHub-flavoured markdown: title slides become
1971
+ * `#` headings, body shapes become nested bullets at each paragraph's `lvl`,
1972
+ * tables become pipe tables, charts become summarised bullets, and speaker
1973
+ * notes and comments are collated. Positioning, animations, images, and
1974
+ * drawing detail are discarded — the projection is meant for AI ingestion and
1975
+ * full-text search, not layout.
1976
+ *
1977
+ * Runs entirely in the worker off the archive opened at {@link load} (no
1978
+ * re-copy of the file, no re-parse of the model on the main thread), so it
1979
+ * works in BOTH `mode: 'main'` and `mode: 'worker'`.
1980
+ *
1981
+ * @example
1982
+ * const pres = await PptxPresentation.load(buffer);
1983
+ * const md = await pres.toMarkdown();
1984
+ */
1985
+ toMarkdown(): Promise<string>;
1202
1986
  /**
1203
1987
  * Render a slide and attach canvas-native playback controls for any
1204
1988
  * embedded audio/video. Returns a {@link PresentationHandle} that owns the
@@ -1210,7 +1994,7 @@ export declare class PptxPresentation {
1210
1994
  destroy(): void;
1211
1995
  }
1212
1996
 
1213
- export declare class PptxScrollViewer {
1997
+ export declare class PptxScrollViewer implements ZoomableViewer {
1214
1998
  private _pres;
1215
1999
  private readonly _injected;
1216
2000
  private readonly _opts;
@@ -1234,6 +2018,18 @@ export declare class PptxScrollViewer {
1234
2018
  * than a `_scale === 1` sentinel because a fit scale of exactly 1 is a valid
1235
2019
  * established state (a 1× fit would otherwise be re-fit forever). */
1236
2020
  private _scaleEstablished;
2021
+ /**
2022
+ * IX9 F1 — a `setScale` factor requested BEFORE the base fit is established
2023
+ * (pre-load, or a zero-width container), already clamped to
2024
+ * `[zoomMin, zoomMax]`, or `null` when none is pending. The single-canvas
2025
+ * viewers latch a pre-load `setScale` and honour it on the first render; the
2026
+ * scroll viewers used to silently DROP it — the family-unified semantics are
2027
+ * "latch and apply once the layout establishes". `relayout()` applies (and
2028
+ * clears) this right after establishing the base, firing `onScaleChange` at
2029
+ * application time; `getScale()` reports it while pending so the caller sees
2030
+ * the same value a single-canvas viewer would show.
2031
+ */
2032
+ private _pendingScale;
1237
2033
  /** Live slots keyed by slide index. */
1238
2034
  private readonly _slots;
1239
2035
  /** Recyclable detached slots (canvas + textLayer reused across slides). */
@@ -1248,6 +2044,23 @@ export declare class PptxScrollViewer {
1248
2044
  * reporting an error so a rejection that lands after teardown is swallowed
1249
2045
  * rather than surfaced to a `onError` on a dead viewer. */
1250
2046
  private _destroyed;
2047
+ /**
2048
+ * Concurrent-load latch (generation token). Every self-loading `load()`
2049
+ * increments this and captures the value; after its engine finishes loading it
2050
+ * re-checks the live value and BAILS (destroying its own just-loaded engine) if
2051
+ * a newer `load()` has since started. Without it, two overlapping
2052
+ * `load(A)`/`load(B)` calls race the WASM parse / worker init, and whichever
2053
+ * RESOLVES last wins the swap — even the stale `load(A)` resolving after
2054
+ * `load(B)`; the loser's freshly created engine (never installed, or installed
2055
+ * then overwritten) then leaks its worker + pinned WASM allocation. The latch
2056
+ * composes with SC20: the check runs AFTER the new engine loads but BEFORE the
2057
+ * field assignment, `previous?.destroy()`, and the recycle/relayout post-load
2058
+ * work, so a superseded load never touches `this._pres` nor frees the current
2059
+ * (newer) engine. Only the self-loading path uses it — the injected path throws
2060
+ * up-front and never reaches here. `destroy()` also bumps it so a load in flight
2061
+ * at teardown is treated as superseded and its engine cleaned up.
2062
+ */
2063
+ private _loadGen;
1251
2064
  /** Worker mode: slide indices whose bitmap render is currently dispatched to the
1252
2065
  * engine. Coalesces a scroll storm — we never dispatch a second render for a
1253
2066
  * slide whose first is still in flight — and lets us drop slides that scrolled
@@ -1278,11 +2091,14 @@ export declare class PptxScrollViewer {
1278
2091
  * is host-agnostic. */
1279
2092
  private _settleTimer;
1280
2093
  private _wheelListener;
1281
- /** One-shot latch for the worker-mode text-selection warning. The overlay is a
1282
- * main-mode-only feature: in worker mode the per-run `onTextRun` geometry
1283
- * cannot cross the worker boundary, so an `enableTextSelection` overlay stays
1284
- * empty. We warn once (parity with `PptxViewer`) rather than per slot. */
1285
- private _warnedNoTextSelection;
2094
+ /** Gesture-only pointer anchor for the NEXT `setScale`, in scrollHost-viewport
2095
+ * px (`{ x, y }` from the wheel event, relative to the scroll host's top-left).
2096
+ * Set by the Ctrl/⌘+wheel handler right before it calls `setScale` so the zoom
2097
+ * pivots on the cursor ("zoom toward the pointer") in BOTH axes; consumed and
2098
+ * cleared by `setScale`. `null` for every non-gesture source (the public
2099
+ * `setScale`, the +/- steppers, `fitWidth`/`fitPage`, the resize re-fit), which
2100
+ * keep the historical viewport-TOP re-anchor so their behaviour is unchanged. */
2101
+ private _pendingZoomAnchor;
1286
2102
  /** Observes the container so a width change re-fits the base scale. Disconnected
1287
2103
  * in `destroy()`. */
1288
2104
  private _resizeObserver;
@@ -1358,6 +2174,12 @@ export declare class PptxScrollViewer {
1358
2174
  * `_positionSlot` (the flush-left floor), and by `_syncSpacerWidth` (the spacer
1359
2175
  * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
1360
2176
  private _padH;
2177
+ /** Index of the slide whose slot spans content-offset `y` (largest `i` with
2178
+ * `offsets[i] <= y`), for the pointer-anchored zoom re-anchor. Mirrors the
2179
+ * `topIndex` search `computeVisibleRange` runs for the scrollTop, but for an
2180
+ * ARBITRARY content-y (the pointer, not the viewport top). Clamped into
2181
+ * `[0, n-1]`; a `y` below the first slide (inside the leading pad) yields 0. */
2182
+ private _slideIndexAtOffset;
1361
2183
  private _range;
1362
2184
  private _syncSpacer;
1363
2185
  /** Horizontal scroll extent: the (uniform deck-wide) slide width plus both
@@ -1406,10 +2228,6 @@ export declare class PptxScrollViewer {
1406
2228
  * can pass for an old-epoch resolution). We gate them on the captured epoch.
1407
2229
  */
1408
2230
  private _renderSlot;
1409
- /** Warn once when an `enableTextSelection` overlay was requested but the render
1410
- * mode is `worker` (so the overlay stays empty). Same wording as
1411
- * `PptxViewer` — one warning per viewer, not per slot. */
1412
- private _maybeWarnNoTextSelection;
1413
2231
  /** Route an async render failure to `onError`, or `console.error` when none is
1414
2232
  * set (so failures are never fully silent), and never after teardown. */
1415
2233
  private _reportRenderError;
@@ -1445,7 +2263,10 @@ export declare class PptxScrollViewer {
1445
2263
  * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
1446
2264
  * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
1447
2265
  * then re-anchor VERTICALLY so the slide currently under the viewport top stays
1448
- * fixed. A no-op when nothing is loaded or when the clamped scale is unchanged.
2266
+ * fixed. A no-op when the clamped scale is unchanged. Called BEFORE the deck is
2267
+ * loaded / the base fit is established, the clamped factor is LATCHED (IX9 F1,
2268
+ * family-unified with the single-canvas viewers) and applied by `relayout()`
2269
+ * once the layout establishes — `onScaleChange` fires then.
1449
2270
  *
1450
2271
  * FLICKER-FREE (design §7): this does NOT re-render the visible slides inline.
1451
2272
  * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
@@ -1466,6 +2287,39 @@ export declare class PptxScrollViewer {
1466
2287
  * can no longer return below the floor to the original base fit through this API.
1467
2288
  */
1468
2289
  setScale(scale: number): void;
2290
+ /** IX9 {@link ZoomableViewer} — the current zoom factor, where `1` = 100% (a
2291
+ * slide at its natural EMU→px size). This is the viewer's absolute `_scale`
2292
+ * (`slideWidth/EMU_PER_PX × _scale` is the drawn width), so it reads `1` at
2293
+ * true 100% and, after the initial fit-to-width, the base fit factor. Before
2294
+ * the fit is established it reports a latched pre-load `setScale` (IX9 F1) if
2295
+ * one is pending — matching what a single-canvas viewer would show — else `1`. */
2296
+ getScale(): number;
2297
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
2298
+ * ladder above the current factor (clamped to `zoomMax` by {@link setScale}). */
2299
+ zoomIn(): void;
2300
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
2301
+ zoomOut(): void;
2302
+ /**
2303
+ * IX9 {@link ZoomableViewer} — fit a slide's WIDTH to the container (the classic
2304
+ * continuous-scroll "fit width"). Sets the scale to the width-fit base for the
2305
+ * current container, then re-anchors + re-renders via {@link setScale}. Defers
2306
+ * (no-op) while the container is unlaid-out. The `zoomMin`/`zoomMax` clamp still
2307
+ * applies, so a fit below `zoomMin` pins to `zoomMin`.
2308
+ */
2309
+ fitWidth(): void;
2310
+ /**
2311
+ * IX9 {@link ZoomableViewer} — fit a WHOLE slide (width and height) inside the
2312
+ * container so one slide is visible without scrolling; takes the tighter of the
2313
+ * width/height fit. Uses the deck-wide (uniform) slide size. Defers while
2314
+ * unlaid-out.
2315
+ */
2316
+ fitPage(): void;
2317
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: the width-fit factor is the
2318
+ * established base (`_baseScale`); the page-fit additionally bounds by the
2319
+ * container height against the (uniform) slide height. Applies via
2320
+ * {@link setScale} so the flicker-free re-anchor / settle path and
2321
+ * `onScaleChange` all run. */
2322
+ private _fit;
1469
2323
  /**
1470
2324
  * CSS preview of the visible window at the current `_scale` (design §7
1471
2325
  * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
@@ -1492,12 +2346,11 @@ export declare class PptxScrollViewer {
1492
2346
  private _scheduleSettle;
1493
2347
  /** Full-resolution settle re-render of the visible window (design §7 mechanisms
1494
2348
  * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
1495
- * swap (main) / same-canvas transfer (worker). Main mode also rebuilds the text
1496
- * overlay and clears its preview transform; in worker mode the overlay is
1497
- * permanently empty (text selection is main-mode-only), so the transform is
1498
- * inert there and is reset on recycle. Dispatched at the CURRENT epoch; the
1499
- * existing epoch gate discards it if a later `setScale` supersedes it
1500
- * mid-render. */
2349
+ * swap (main) / same-canvas transfer (worker). Both modes rebuild the text
2350
+ * overlay from the fresh render's run geometry (IX6 — worker mode collects the
2351
+ * runs off-thread via `_renderSlotBitmap`) and clear the preview transform.
2352
+ * Dispatched at the CURRENT epoch; the existing epoch gate discards it if a
2353
+ * later `setScale` supersedes it mid-render. */
1501
2354
  private _settleRender;
1502
2355
  /**
1503
2356
  * Settle-render one slot at the current scale (design §7 mechanism 3).
@@ -1539,6 +2392,31 @@ export declare class PptxScrollViewer {
1539
2392
  scrollToSlide(index: number, opts?: {
1540
2393
  behavior?: 'auto' | 'smooth';
1541
2394
  }): void;
2395
+ /**
2396
+ * IX1 hyperlink click dispatch (mirrors {@link PptxViewer._onHyperlinkClick}).
2397
+ * When the integrator supplies `opts.onHyperlinkClick` it OWNS the click (no
2398
+ * default). Otherwise: an external link opens in a new tab via the shared,
2399
+ * scheme-sanitised {@link openExternalHyperlink}; an internal slide jump scrolls
2400
+ * to the target slide via {@link scrollToSlide} once the action resolves to a
2401
+ * slide index (a jump resolving to no reachable slide is a safe no-op).
2402
+ */
2403
+ /**
2404
+ * IX1 — the click handler passed to the text-layer overlay, or `undefined` when
2405
+ * `enableHyperlinks` is `false`. This is the single gate that disables hyperlink
2406
+ * interactivity: {@link buildPptxTextLayer} renders link runs exactly like plain
2407
+ * runs when no handler is supplied, so no hit region, cursor, tooltip, listener,
2408
+ * or navigation is wired (a custom `onHyperlinkClick` is suppressed too). When
2409
+ * enabled, the returned handler dispatches through {@link _onHyperlinkClick}.
2410
+ */
2411
+ private _hyperlinkHandler;
2412
+ private _onHyperlinkClick;
2413
+ /** Populate an internal {@link HyperlinkTarget}'s `slideIndex` from its `ref`
2414
+ * via the engine's stamped part names. Relative `hlinkshowjump` verbs are
2415
+ * resolved against the slide currently at the viewport top
2416
+ * (`_range().topIndex`); a `../slides/slideN.xml` part target resolves through
2417
+ * the part-name map. An already-set index, an external target, and an
2418
+ * unresolvable ref all pass through unchanged (safe no-op). */
2419
+ private _resolveInternalSlideIndex;
1542
2420
  /**
1543
2421
  * Re-fit the base scale on a container resize while PRESERVING the current zoom
1544
2422
  * multiplier (design §11), then re-anchor + re-render. A `ResizeObserver` fires
@@ -1572,6 +2450,8 @@ export declare class PptxScrollViewer {
1572
2450
  /* Excluded from this release type: baseScaleForTest */
1573
2451
  /* Excluded from this release type: renderEpochForTest */
1574
2452
  /* Excluded from this release type: resizeForTest */
2453
+ /* Excluded from this release type: contentAtViewportYForTest */
2454
+ /* Excluded from this release type: viewportYOfForTest */
1575
2455
  /**
1576
2456
  * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
1577
2457
  * engine) destroy the engine. An injected engine is left intact — the caller
@@ -1624,9 +2504,10 @@ export declare interface PptxScrollViewerOptions extends Omit<RenderSlideOptions
1624
2504
  paddingRight?: number;
1625
2505
  /** Slides kept mounted beyond the viewport on each side. Default 1. */
1626
2506
  overscan?: number;
1627
- /** Per-slide transparent text-selection overlay. MAIN render mode only:
1628
- * in worker mode `onTextRun` cannot cross the worker boundary, so the overlay
1629
- * stays empty and the viewer logs one warning (design §11). */
2507
+ /** Per-slide transparent text-selection overlay. IX6 works in BOTH render
2508
+ * modes: in worker mode the per-run geometry is collected off-thread and
2509
+ * shipped back beside the slide bitmap, so the overlay is populated identically
2510
+ * to main mode (no more empty overlay / one-time warning). */
1630
2511
  enableTextSelection?: boolean;
1631
2512
  /** Minimum zoom scale — a DIMENSIONLESS multiplier over the 96-dpi natural
1632
2513
  * slide size (10% = 0.1), matching `DocxScrollViewer`. Default 0.1. */
@@ -1670,6 +2551,12 @@ export declare interface PptxScrollViewerOptions extends Omit<RenderSlideOptions
1670
2551
  * `computeVisibleRange` (the first slide intersecting the viewport top,
1671
2552
  * EXCLUDING overscan). */
1672
2553
  onVisibleSlideChange?: (topIndex: number, total: number) => void;
2554
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100% = a slide
2555
+ * at its natural EMU→px size): from {@link PptxScrollViewer.setScale},
2556
+ * `zoomIn`/`zoomOut`, `fitWidth`/`fitPage`, a Ctrl/⌘+wheel gesture, or a
2557
+ * container-resize re-fit. Named `onScaleChange` to match the single-canvas
2558
+ * viewers so all five share one notification shape. */
2559
+ onScaleChange?: (scale: number) => void;
1673
2560
  /** Error callback. When set, `load()` invokes it and resolves (otherwise the
1674
2561
  * error is rethrown — shared viewer error contract). It ALSO fires for async
1675
2562
  * per-slot render failures (both main `renderSlide` and worker
@@ -1677,6 +2564,24 @@ export declare interface PptxScrollViewerOptions extends Omit<RenderSlideOptions
1677
2564
  * crashing the loop. Without an `onError`, render failures are logged via
1678
2565
  * `console.error` so they are never fully silent. */
1679
2566
  onError?: (err: Error) => void;
2567
+ /**
2568
+ * IX1 (design decision — NOT user-confirmed, integrator may veto). Fires on a
2569
+ * hyperlink click in any mounted slide's text overlay (requires
2570
+ * {@link enableTextSelection}). Default when omitted: external →
2571
+ * {@link openExternalHyperlink} (new tab, sanitised, noopener); internal
2572
+ * slide-jump → {@link scrollToSlide} once the action resolves to a slide index
2573
+ * via {@link PptxPresentation.resolveInternalTarget} (a jump that resolves to
2574
+ * no reachable slide is a safe no-op). When provided, the viewer calls this
2575
+ * instead and takes NO default action.
2576
+ */
2577
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
2578
+ /** IX1 — master switch for hyperlink interactivity. Default `true`. When
2579
+ * `false`, the hyperlink machinery is not wired at all: the overlay's link
2580
+ * spans are non-interactive, so there is no pointer cursor, no title tooltip,
2581
+ * no default navigation (external new-tab / internal slide jump), and
2582
+ * `onHyperlinkClick` is never called. Links still render exactly as authored
2583
+ * but are inert, like plain text. */
2584
+ enableHyperlinks?: boolean;
1680
2585
  }
1681
2586
 
1682
2587
  /** Information about a rendered text segment for building a transparent selection overlay. */
@@ -1709,6 +2614,14 @@ export declare interface PptxTextRunInfo {
1709
2614
  * `vert="vert270"` → -90). The CSS overlay must add this to `rotation`.
1710
2615
  */
1711
2616
  textBodyRotation?: number;
2617
+ /**
2618
+ * Resolved hyperlink target for this run (IX1), classified into the shared
2619
+ * {@link HyperlinkTarget} shape. Present only for runs whose `<a:rPr>` carried
2620
+ * an `<a:hlinkClick>`; the overlay makes such spans clickable. The glyph
2621
+ * drawing (colour + underline) is unaffected — this is metadata for the
2622
+ * transparent overlay only.
2623
+ */
2624
+ hyperlink?: HyperlinkTarget;
1712
2625
  }
1713
2626
 
1714
2627
  /**
@@ -1721,9 +2634,18 @@ export declare interface PptxTextRunInfo {
1721
2634
  *
1722
2635
  * For custom layouts (multi-canvas, thumbnails, scroll view) use PptxPresentation directly.
1723
2636
  */
1724
- export declare class PptxViewer {
2637
+ export declare class PptxViewer implements ZoomableViewer {
1725
2638
  private readonly canvas;
1726
2639
  private readonly wrapper;
2640
+ /**
2641
+ * IX9 explicit zoom factor (`1` = 100% = the slide at its natural EMU→px
2642
+ * width), or `null` when the caller has never invoked a zoom method. `null`
2643
+ * preserves the pre-IX9 render path EXACTLY: the slide renders at `opts.width`
2644
+ * (or `canvas.offsetWidth || 960` when unset), so default rendering is
2645
+ * byte-identical. The first zoom call latches a number here, after which
2646
+ * {@link _targetWidth} derives the render width from it.
2647
+ */
2648
+ private _scale;
1727
2649
  /** The canvas's DOM position BEFORE the constructor reparented it into
1728
2650
  * {@link wrapper}, captured so {@link destroy} can return the caller-owned
1729
2651
  * canvas to exactly where it was. `null` parent = canvas was passed
@@ -1734,6 +2656,13 @@ export declare class PptxViewer {
1734
2656
  * (empty string if it was unset), restored on {@link destroy}. */
1735
2657
  private readonly _originalDisplay;
1736
2658
  private textLayer;
2659
+ /** IX2 — the find-highlight overlay layer (always created, above the text
2660
+ * layer, `pointer-events:none`). */
2661
+ private highlightLayer;
2662
+ /** IX2 — find state (per-slide runs, matches, active cursor). */
2663
+ private _find;
2664
+ /** Private 2d context for measuring highlight text (own 1×1 canvas). */
2665
+ private _measureCtx;
1737
2666
  private engine;
1738
2667
  private readonly opts;
1739
2668
  private currentSlide;
@@ -1744,14 +2673,39 @@ export declare class PptxViewer {
1744
2673
  * render path. The media-playback path keeps a 2d context (via presentSlide),
1745
2674
  * so this is obtained only when worker mode renders without media playback. */
1746
2675
  private _bitmapCtx;
1747
- private _warnedNoTextSelection;
2676
+ /** Set by {@link destroy} (first line). Guards {@link _reportRenderError} so a
2677
+ * render rejection that lands AFTER teardown is swallowed rather than surfaced
2678
+ * to an `onError` / `console.error` on a dead viewer — parity with the scroll
2679
+ * viewers' `_destroyed` flag. */
2680
+ private _destroyed;
2681
+ /**
2682
+ * Concurrent-load latch (generation token). Every {@link load} increments this
2683
+ * and captures the value; after its engine finishes loading it re-checks the
2684
+ * live value and BAILS (destroying its own just-loaded engine) if a newer
2685
+ * `load()` has since started. Without it, two overlapping `load(A)`/`load(B)`
2686
+ * calls race the WASM parse / worker init, and whichever RESOLVES last wins the
2687
+ * swap — even the stale `load(A)` resolving after `load(B)`; the loser's freshly
2688
+ * created engine (never installed, or installed then overwritten) then leaks its
2689
+ * worker + pinned WASM allocation. The latch composes with SC20: the check runs
2690
+ * AFTER the new engine loads but BEFORE the field assignment and
2691
+ * `previous?.destroy()`, so a superseded load never touches `this.engine` nor
2692
+ * frees the current (newer) engine. {@link destroy} also bumps it so a load in
2693
+ * flight at teardown is treated as superseded and its engine cleaned up.
2694
+ */
2695
+ private _loadGen;
1748
2696
  constructor(canvas: HTMLCanvasElement, opts?: PptxViewerOptions);
1749
2697
  /**
1750
2698
  * Load a PPTX from URL or ArrayBuffer and render the first slide.
1751
2699
  *
1752
- * Error contract (shared by all three viewers): on failure, if an `onError`
1753
- * callback was provided it is invoked and `load` resolves normally; if not,
1754
- * the error is rethrown so it is never silently swallowed.
2700
+ * Error contract (shared by all three viewers):
2701
+ * - Parse/load failure (the underlying `PptxPresentation.load()` call itself
2702
+ * rejects): if an `onError` callback was provided it is invoked and `load`
2703
+ * resolves normally; if not, the error is rethrown so it is never silently
2704
+ * swallowed.
2705
+ * - Render failure (the first slide fails to draw AFTER a successful
2706
+ * parse/load): routed to the shared `_reportRenderError` contract (`onError`
2707
+ * if provided, else `console.error` — never silent) and `load` still
2708
+ * RESOLVES, matching every subsequent navigation call.
1755
2709
  */
1756
2710
  load(source: string | ArrayBuffer): Promise<void>;
1757
2711
  /** Navigate to a specific slide (0-indexed). */
@@ -1784,8 +2738,124 @@ export declare class PptxViewer {
1784
2738
  getNotes(slideIndex: number): string | null;
1785
2739
  /** The underlying <canvas> element. */
1786
2740
  get canvasElement(): HTMLCanvasElement;
2741
+ /** Natural (100%) CSS-px width of a slide — `slideWidth(EMU) / EMU_PER_PX`.
2742
+ * 0 when nothing is loaded. The scale-1 reference every zoom factor
2743
+ * multiplies. */
2744
+ private _naturalWidthPx;
2745
+ /**
2746
+ * The width (CSS px) the render paths draw the slide at, honouring the zoom
2747
+ * state. `_scale === null` (no zoom method ever called) ⇒ the pre-IX9 value
2748
+ * `opts.width ?? (canvas.offsetWidth || 960)` verbatim (byte-identical
2749
+ * default). Once a factor latched ⇒ `naturalWidth × scale` (rounded), so the
2750
+ * slide is exactly `scale ×` its natural size regardless of `opts.width`.
2751
+ */
2752
+ private _targetWidth;
2753
+ /** IX9 {@link ZoomableViewer} — the current zoom factor (`1` = 100%). Before
2754
+ * any zoom method is called this is the EFFECTIVE scale implied by the render
2755
+ * width: `targetWidth / naturalWidth`, or `1` when nothing is loaded. */
2756
+ getScale(): number;
2757
+ private _zoomMin;
2758
+ private _zoomMax;
2759
+ /**
2760
+ * IX9 {@link ZoomableViewer} — set the absolute zoom factor (`1` = 100% = the
2761
+ * slide at its natural EMU→px width), clamped to `[zoomMin, zoomMax]`, and
2762
+ * re-render the current slide at the new size. Fires `onScaleChange` when the
2763
+ * clamped factor actually changes. Resolves once the re-render settles.
2764
+ */
2765
+ setScale(scale: number): Promise<void>;
2766
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
2767
+ * ladder (clamped to `zoomMax`). */
2768
+ zoomIn(): Promise<void>;
2769
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
2770
+ zoomOut(): Promise<void>;
2771
+ /**
2772
+ * IX9 {@link ZoomableViewer} — fit the current slide's WIDTH to the host
2773
+ * container (the element the canvas lives in), then re-render. Defers (no-op)
2774
+ * when nothing is loaded or the container is unlaid-out. Routes through
2775
+ * {@link setScale}.
2776
+ */
2777
+ fitWidth(): Promise<void>;
2778
+ /**
2779
+ * IX9 {@link ZoomableViewer} — fit the WHOLE current slide (width and height)
2780
+ * inside the container so it is fully visible; takes the tighter of the
2781
+ * width/height fit. Defers when unloaded / unlaid-out.
2782
+ */
2783
+ fitPage(): Promise<void>;
2784
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: measure the natural slide
2785
+ * size + the container box, ask core's pure `fitScale`, apply via setScale. */
2786
+ private _fit;
1787
2787
  private renderCurrentSlide;
2788
+ /** Draw the find-highlight boxes for the current slide from its runs. */
2789
+ private _buildHighlightLayer;
2790
+ /** A width-measurer primed with `font`, backed by a private 1×1 canvas. */
2791
+ private _measureForFont;
2792
+ /** IX6 — collect a slide's runs for search without touching the visible
2793
+ * canvas. Delegates to `collectSlideRuns`, which works in BOTH modes (worker:
2794
+ * off-thread, ships only the runs; main: throwaway offscreen canvas). Used for
2795
+ * slides other than the one on screen. */
2796
+ private _collectSlideRuns;
2797
+ /**
2798
+ * IX2 — find every occurrence of `query` across all slides and highlight them
2799
+ * (a soft box per match on the highlight overlay). Returns every match in
2800
+ * document order, each tagged with its `{ slide }` (0-based). Case-insensitive
2801
+ * by default; pass `{ caseSensitive: true }` for an exact match.
2802
+ *
2803
+ * Scans all slides (each rendered once offscreen to read its text; the visible
2804
+ * slide reuses its on-screen render). IX6 — works in BOTH `mode: 'main'` and
2805
+ * `mode: 'worker'`: in worker mode each slide's run geometry is collected
2806
+ * off-thread and shipped back, so find returns the same matches on the same
2807
+ * code path. An empty query clears the find.
2808
+ */
2809
+ findText(query: string, opts?: FindMatchesOptions): Promise<FindMatch<PptxMatchLocation>[]>;
2810
+ /**
2811
+ * IX2 — move to the next match (wrap-around), navigating to its slide if
2812
+ * needed, and draw it in the active-match colour. Returns the now-active
2813
+ * match, or `null` when there are none. Call {@link findText} first.
2814
+ */
2815
+ findNext(): Promise<FindMatch<PptxMatchLocation> | null>;
2816
+ /** IX2 — move to the previous match (wrap-around). */
2817
+ findPrev(): Promise<FindMatch<PptxMatchLocation> | null>;
2818
+ /** IX2 — clear all highlights and reset the find state. */
2819
+ clearFind(): void;
2820
+ private _activateMatch;
2821
+ /** Rebuild the highlight overlay for the current slide from cached runs. */
2822
+ private _redrawHighlights;
1788
2823
  private _buildTextLayer;
2824
+ /**
2825
+ * IX1 — the click handler passed to the text-layer overlay, or `undefined` when
2826
+ * `enableHyperlinks` is `false`. This is the single gate that disables hyperlink
2827
+ * interactivity: {@link buildPptxTextLayer} renders link runs exactly like plain
2828
+ * runs when no handler is supplied, so no hit region, cursor, tooltip, listener,
2829
+ * or navigation is wired (a custom `onHyperlinkClick` is suppressed too). When
2830
+ * enabled, the returned handler dispatches through {@link _onHyperlinkClick}.
2831
+ */
2832
+ private _hyperlinkHandler;
2833
+ /**
2834
+ * IX1/IX-nav hyperlink click dispatch. An internal target is first *enriched*
2835
+ * with its resolved 0-based `slideIndex` (via
2836
+ * {@link PptxPresentation.resolveInternalTarget}, relative to the current
2837
+ * slide) so a jump verb / slide-part ref arrives already mapped — this is the
2838
+ * field that was previously always `undefined`. When the integrator supplies
2839
+ * `opts.onHyperlinkClick` it OWNS the (enriched) click and takes NO default
2840
+ * action. Otherwise the viewer's default policy applies: an external link
2841
+ * opens in a new tab via the shared, scheme-sanitised
2842
+ * {@link openExternalHyperlink}; an internal slide jump navigates via
2843
+ * {@link goToSlide} to the resolved index (a target that resolves to no
2844
+ * reachable slide is a safe no-op).
2845
+ */
2846
+ private _onHyperlinkClick;
2847
+ /** Populate an internal {@link HyperlinkTarget}'s `slideIndex` from its `ref`
2848
+ * (a `ppaction://hlinkshowjump?jump=…` verb resolved relative to the current
2849
+ * slide, or a `../slides/slideN.xml` part target resolved through the stamped
2850
+ * part-name map — no filename-suffix heuristic). Any already-set `slideIndex`
2851
+ * is kept; an external target and an unresolvable ref pass through unchanged so
2852
+ * the caller no-ops safely. */
2853
+ private _resolveInternalSlideIndex;
2854
+ /** PD14 render-error contract: route a render failure to `onError`, or
2855
+ * `console.error` when none is given (never fully silent), and never after
2856
+ * teardown. Mirrors the scroll viewers' `_reportRenderError` so all three
2857
+ * single-canvas viewers agree. */
2858
+ private _reportRenderError;
1789
2859
  /**
1790
2860
  * Clean up the viewer and terminate the background worker.
1791
2861
  *
@@ -1803,6 +2873,17 @@ export declare interface PptxViewerOptions extends RenderOptions, LoadOptions {
1803
2873
  onSlideChange?: (index: number, total: number) => void;
1804
2874
  /** Called on parse or render errors */
1805
2875
  onError?: (err: Error) => void;
2876
+ /** IX9 zoom contract ({@link ZoomableViewer}) — the clamp range for
2877
+ * {@link PptxViewer.setScale} / `zoomIn` / `zoomOut` / `fitWidth` / `fitPage`,
2878
+ * as user-facing zoom factors (`1` = 100% = the slide at its natural
2879
+ * EMU→px size). Defaults 0.1–4 (10%–400%), matching the other viewers. */
2880
+ zoomMin?: number;
2881
+ zoomMax?: number;
2882
+ /** IX9 — fires whenever the zoom factor actually changes (`1` = 100%): from
2883
+ * {@link PptxViewer.setScale}, `zoomIn`/`zoomOut`, or `fitWidth`/`fitPage`.
2884
+ * Named `onScaleChange` to match the docx/xlsx viewers so all five share one
2885
+ * notification shape. */
2886
+ onScaleChange?: (scale: number) => void;
1806
2887
  /**
1807
2888
  * Enable interactive audio/video playback. When true, slides are rendered
1808
2889
  * via {@link PptxPresentation.presentSlide} so media elements become
@@ -1834,6 +2915,25 @@ export declare interface PptxViewerOptions extends RenderOptions, LoadOptions {
1834
2915
  * in sync if {@link DimOptions} gains a field.
1835
2916
  */
1836
2917
  hiddenSlideDim?: Partial<DimOptions>;
2918
+ /**
2919
+ * IX1 (design decision — NOT user-confirmed, integrator may veto). Fires on a
2920
+ * hyperlink click (a text run whose `<a:rPr>` carried an `<a:hlinkClick>`;
2921
+ * requires {@link enableTextSelection} so the overlay spans exist). Default
2922
+ * when omitted: external → {@link openExternalHyperlink} (new tab, sanitised,
2923
+ * noopener); internal slide-jump → {@link goToSlide} once the action resolves
2924
+ * to a slide index via {@link PptxPresentation.resolveInternalTarget} (a jump
2925
+ * that resolves to no reachable slide is a safe no-op). When provided, the
2926
+ * viewer calls this instead and takes NO default action.
2927
+ */
2928
+ onHyperlinkClick?: (target: HyperlinkTarget) => void;
2929
+ /** IX1 — master switch for hyperlink interactivity. Default `true`. When
2930
+ * `false`, the hyperlink machinery is not wired at all: the overlay's link
2931
+ * spans are non-interactive, so there is no pointer cursor, no title tooltip,
2932
+ * no default navigation (external new-tab / internal slide jump), and
2933
+ * `onHyperlinkClick` is never called. Links still render exactly as authored
2934
+ * (theme `hlink` colour + underline are painted on the canvas) but are inert,
2935
+ * like plain text. */
2936
+ enableHyperlinks?: boolean;
1837
2937
  }
1838
2938
 
1839
2939
  export declare interface Presentation {
@@ -1949,6 +3049,14 @@ export declare interface RenderSlideToBitmapOptions {
1949
3049
  /* Excluded from this release type: skipMediaControls */
1950
3050
  /** Translucent overlay drawn over the finished slide (hidden-slide dimming). */
1951
3051
  dim?: DimOptions;
3052
+ /**
3053
+ * IX6 — receives the slide's text-run geometry (the same stream `renderSlide`
3054
+ * emits in main mode). Stays main-thread (never crosses the wire); in worker
3055
+ * mode the proxy invokes it with the runs the worker shipped back beside the
3056
+ * bitmap, so a caller builds the selection / find overlay on the SAME code
3057
+ * path in both modes.
3058
+ */
3059
+ onTextRun?: TextRunCallback;
1952
3060
  }
1953
3061
 
1954
3062
  /**
@@ -2004,6 +3112,13 @@ declare interface SecondaryValueAxis {
2004
3112
  lineHidden: boolean;
2005
3113
  /** `<c:majorTickMark>` — "cross" (default) | "out" | "in" | "none". */
2006
3114
  majorTickMark: string;
3115
+ /**
3116
+ * `<c:valAx><c:majorUnit val>` (§21.2.2.103) — explicit distance between
3117
+ * major ticks/gridlines on THIS secondary axis, overriding the Excel-style
3118
+ * auto "nice" step. null/undefined ⇒ auto step (byte-stable). Symmetric with
3119
+ * {@link ChartModel.valAxisMajorUnit} on the primary axis.
3120
+ */
3121
+ majorUnit?: number | null;
2007
3122
  /** `<c:title>` run-prop font size (hpt). */
2008
3123
  titleFontSizeHpt?: number | null;
2009
3124
  /** `<c:title>` run-prop bold flag. */
@@ -2077,12 +3192,40 @@ export declare interface ShapeElement {
2077
3192
  /** `<a:sp3d>` 3D shape properties (ECMA-376 §20.1.5.12). Parsed but not
2078
3193
  * rendered in Phase A. */
2079
3194
  sp3d?: Sp3d;
3195
+ /** `<p:nvSpPr><p:cNvPr @id>` — DrawingML cNvPr id. Present for file-authored
3196
+ * shapes (the attribute is schema-required); absent on parser-synthesized
3197
+ * shapes such as the SmartArt data-model fallback. */
3198
+ id?: string;
3199
+ /** `<p:nvSpPr><p:cNvPr @name>` — author-visible shape name (e.g. "Title 1").
3200
+ * The SmartArt data-model fallback synthesizes `name: "SmartArt"` with no
3201
+ * {@link ShapeElement.id} (see smartart-fallback-contrast.ts). */
3202
+ name?: string;
3203
+ /** Shape-level hyperlink target resolved from `<p:cNvPr><a:hlinkClick @r:id>`
3204
+ * via slide _rels (ECMA-376 §21.1.2.3.5). For an external link this is the
3205
+ * URL; for an internal slide jump it is the resolved internal part name.
3206
+ * Undefined when the shape carries no hlinkClick. */
3207
+ hyperlink?: string;
3208
+ /** Raw `<a:hlinkClick @action>` (e.g. `"ppaction://hlinksldjump"`) when the
3209
+ * shape link is an internal PowerPoint action rather than an external URL.
3210
+ * Undefined when absent. */
3211
+ hyperlinkAction?: string;
2080
3212
  }
2081
3213
 
2082
3214
  export declare interface Slide {
2083
3215
  index: number;
2084
3216
  /** 1-based slide number (index + 1); used to render slidenum fields */
2085
3217
  slideNumber: number;
3218
+ /**
3219
+ * The slide's normalized OPC part name (e.g. `ppt/slides/slide3.xml`),
3220
+ * resolved through `presentation.xml.rels` in `sldIdLst` order (ECMA-376
3221
+ * §19.3.1.42). An internal hyperlink slide jump
3222
+ * (`<a:hlinkClick action="ppaction://hlinksldjump" r:id>`, §21.1.2.3.5)
3223
+ * carries a rel Target that resolves to this same part name — so
3224
+ * {@link PptxPresentation.getSlideIndexByPartName} can turn a click into a
3225
+ * slide index. Absent (`undefined`) only for a slide whose part path was not
3226
+ * recorded; healthy and broken slides both carry it.
3227
+ */
3228
+ partName?: string;
2086
3229
  background: Fill | null;
2087
3230
  elements: SlideElement[];
2088
3231
  /**
@@ -2106,6 +3249,14 @@ export declare interface Slide {
2106
3249
  * slide modes (read it via `PptxPresentation.isHidden`).
2107
3250
  */
2108
3251
  hidden?: boolean;
3252
+ /**
3253
+ * RB7 partial degradation: set when this slide's part could not be parsed. The
3254
+ * deck still opens with the OTHER slides intact; this one is a placeholder
3255
+ * (`elements` empty) whose `parseError` names the offending part (e.g.
3256
+ * `"ppt/slides/slide3.xml: <detail>"`). Absent (`undefined`) for every healthy
3257
+ * slide. The renderer paints a visible error box instead of slide content.
3258
+ */
3259
+ parseError?: string;
2109
3260
  }
2110
3261
 
2111
3262
  export declare type SlideElement = ShapeElement | PictureElement | TableElement | ChartElement | MediaElement;
@@ -2225,7 +3376,10 @@ export declare interface TableRow {
2225
3376
  }
2226
3377
 
2227
3378
  export declare interface TabStop {
2228
- /** Position in EMU from the left edge of the text area (after lIns) */
3379
+ /** Position in EMU from the LEADING text-inset edge of the text area
3380
+ * logical, not physical (ECMA-376 §21.1.2.1): the left edge (after lIns)
3381
+ * in an LTR paragraph, the right edge (before rIns) in an RTL
3382
+ * (`<a:pPr rtl="1">`) paragraph. */
2229
3383
  pos: number;
2230
3384
  /** Alignment: "l" | "r" | "ctr" | "dec" */
2231
3385
  algn: string;
@@ -2243,6 +3397,19 @@ export declare interface TextBody extends TextBody_2 {
2243
3397
  * omitted from JSON when false. Only meaningful when `numCol > 1`.
2244
3398
  */
2245
3399
  rtlCol?: boolean;
3400
+ /**
3401
+ * `<a:bodyPr><a:prstTxWarp>` (ECMA-376 §20.1.9.19) — WordArt text warp. When
3402
+ * present the renderer maps each glyph through the named envelope
3403
+ * (presetTextWarpDefinitions) instead of laying text out flat. Omitted from
3404
+ * JSON when the body has no warp, so unwarped bodies are byte-identical.
3405
+ */
3406
+ textWarp?: {
3407
+ /** The `prst` name, e.g. `"textArchUp"`, `"textWave1"`. */
3408
+ preset: string;
3409
+ /** `<a:avLst>` adjust values (adj1, adj2, …) in thousandths of a percent.
3410
+ * Omitted when the author supplied none (preset defaults apply). */
3411
+ adj?: number[];
3412
+ };
2246
3413
  /**
2247
3414
  * Narrow the inherited `paragraphs` to the PPTX `Paragraph` so consumers see
2248
3415
  * the PPTX-only `eaLnBrk` flag. PPTX `Paragraph extends CoreParagraph`, so
@@ -2375,10 +3542,19 @@ export declare interface TextRunData {
2375
3542
  /** Set for OOXML field runs (e.g. "slidenum"). When set, renderer replaces text with field value. */
2376
3543
  fieldType?: string;
2377
3544
  /**
2378
- * Hyperlink target URL resolved from rPr > a:hlinkClick @r:id via the slide's _rels.
2379
- * Undefined for runs without a hyperlink. ECMA-376 §21.1.2.3.5 (CT_Hyperlink).
3545
+ * Hyperlink target resolved from rPr > a:hlinkClick @r:id via the slide's _rels.
3546
+ * For an external link this is the URL; for an internal slide jump it is the
3547
+ * resolved internal part name (e.g. "../slides/slide3.xml"). Undefined for runs
3548
+ * without a hyperlink. ECMA-376 §21.1.2.3.5 (CT_Hyperlink).
2380
3549
  */
2381
3550
  hyperlink?: string;
3551
+ /**
3552
+ * Raw `<a:hlinkClick @action>` string (e.g. "ppaction://hlinksldjump") when
3553
+ * present — its presence marks {@link hyperlink} as an INTERNAL PowerPoint
3554
+ * action (slide jump / first / last …) rather than an external URL. Undefined
3555
+ * when the hlinkClick has no @action. ECMA-376 §21.1.2.3.5. (IX1)
3556
+ */
3557
+ hyperlinkAction?: string;
2382
3558
  /**
2383
3559
  * Run-level drop shadow on glyphs (`<a:rPr><a:effectLst><a:outerShdw>`),
2384
3560
  * ECMA-376 §20.1.8.45. Independent of the shape-level shadow on `spPr`.
@@ -2428,4 +3604,90 @@ export declare interface TileInfo {
2428
3604
  algn: string;
2429
3605
  }
2430
3606
 
3607
+ /**
3608
+ * IX9 — the shared zoom API contract for every viewer (DocxViewer, PptxViewer,
3609
+ * DocxScrollViewer, PptxScrollViewer, XlsxViewer).
3610
+ *
3611
+ * This module owns ONLY the pure, DOM-free pieces of the contract: the type
3612
+ * ({@link ZoomableViewer}), the discrete zoom-step ladder ({@link nextZoomStep} /
3613
+ * {@link prevZoomStep}), the fit-to-content scale math ({@link fitScale}), and the
3614
+ * range clamp ({@link clampScale}). Each viewer implements the interface with its
3615
+ * own scale field and re-render path; this keeps ONE definition of "what a zoom
3616
+ * factor means" and "what the +/- steps are" across all five, so a host can drive
3617
+ * any viewer through the same six calls without special-casing the format.
3618
+ *
3619
+ * SCALE SEMANTICS (the contract): a scale of `1` means 100% — the content at its
3620
+ * natural size (a docx page at `widthPt × PT_TO_PX`, a pptx slide at
3621
+ * `slideWidth / EMU_PER_PX`, an xlsx grid at `cellScale` 1). `getScale()` and
3622
+ * `setScale(n)` speak this user-facing factor for EVERY viewer.
3623
+ *
3624
+ * KNOWN FAMILY DIFFERENCE — the INITIAL scale right after load (deliberate,
3625
+ * documented rather than papered over): the single-canvas viewers (DocxViewer /
3626
+ * PptxViewer) and XlsxViewer start at `1` (or the effective factor implied by an
3627
+ * explicit `width` option); the continuous-scroll viewers (DocxScrollViewer /
3628
+ * PptxScrollViewer) AUTO-FIT to the container on first layout, so their
3629
+ * `getScale()` right after load reports the fit-to-width BASE factor (≠ 1 unless
3630
+ * the container happens to match the natural width). The unit is identical — only
3631
+ * the starting point differs, because fit-to-width is the natural resting state
3632
+ * of a continuous document viewer.
3633
+ *
3634
+ * PRE-LOAD `setScale` (family-unified, IX9 F1): a `setScale` called before the
3635
+ * content is loaded / before the layout is established is LATCHED — never
3636
+ * silently dropped — and applied once the viewer establishes its scale (the
3637
+ * single-canvas viewers honour it on the first render; the scroll viewers apply
3638
+ * it right after the base fit establishes, firing `onScaleChange` at application
3639
+ * time). `getScale()` reports the latched factor while it is pending.
3640
+ *
3641
+ * API SHAPE (idiomatic default — the integrator MAY veto; see the IX9 PR): a
3642
+ * six-method surface plus one change notification (`onScaleChange`). Deliberately
3643
+ * NO new UI here — the contract is API only (design decision IX9 §4). Touch-pinch
3644
+ * (IX8) is out of scope.
3645
+ */
3646
+ /**
3647
+ * The zoom contract every viewer satisfies. All scales are the user-facing factor
3648
+ * where `1` = 100% (see the module note). `fitWidth`/`fitPage` are async because a
3649
+ * fit re-renders at the new scale; the getters/steppers resolve synchronously.
3650
+ */
3651
+ declare interface ZoomableViewer {
3652
+ /** The current zoom factor (`1` = 100%). Never throws — returns the default
3653
+ * (`1`) before anything is loaded, or the latched pending factor when a
3654
+ * pre-load `setScale` is waiting to be applied (see the module note). */
3655
+ getScale(): number;
3656
+ /** Set the absolute zoom factor (`1` = 100%), clamped to the viewer's
3657
+ * `[zoomMin, zoomMax]`. Re-renders at the new scale and fires `onScaleChange`
3658
+ * when the clamped value actually changes. Called BEFORE the content is
3659
+ * loaded / the layout is established, the (clamped) factor is LATCHED and
3660
+ * applied once the viewer establishes its scale — family-unified semantics
3661
+ * (IX9 F1): never silently dropped by any viewer. */
3662
+ setScale(scale: number): void | Promise<void>;
3663
+ /** Step up to the next larger rung of the shared zoom ladder (25 %→400 %),
3664
+ * clamped to `zoomMax`. Equivalent to `setScale(nextZoomStep(getScale()))`. */
3665
+ zoomIn(): void | Promise<void>;
3666
+ /** Step down to the next smaller ladder rung, clamped to `zoomMin`. */
3667
+ zoomOut(): void | Promise<void>;
3668
+ /** Fit the content's WIDTH to the container (the common "fit width" / "fit
3669
+ * page width" verb). Sets the scale so one page/slide/sheet-column-run spans
3670
+ * the available width, then re-renders. Resolves once the fit render settles.
3671
+ *
3672
+ * PERSISTENCE is viewer-implementation-dependent (deliberate, by family): the
3673
+ * single-canvas viewers (DocxViewer / PptxViewer) and XlsxViewer apply the fit
3674
+ * ONE-SHOT — they observe no container resizes, so a later resize does NOT
3675
+ * re-fit (call `fitWidth()` again after a layout change). The continuous-
3676
+ * scroll viewers (DocxScrollViewer / PptxScrollViewer) re-fit their width-fit
3677
+ * base on every container resize, so a `fitWidth()` there effectively
3678
+ * PERSISTS across resizes (the resize re-fit preserves the width-fit state). */
3679
+ fitWidth(): void | Promise<void>;
3680
+ /** Fit the WHOLE content (width AND height) inside the container, so an entire
3681
+ * page/slide is visible without scrolling. Sets the scale to the smaller of the
3682
+ * width- and height-fit factors, then re-renders.
3683
+ *
3684
+ * PERSISTENCE is viewer-implementation-dependent, and — unlike `fitWidth` —
3685
+ * a page fit does NOT persist across container resizes on ANY viewer: the
3686
+ * single-canvas viewers and XlsxViewer observe no resizes at all (one-shot),
3687
+ * and the continuous-scroll viewers' resize handler re-applies the WIDTH fit
3688
+ * (preserving the zoom multiplier), not the page fit. Re-invoke `fitPage()`
3689
+ * after a layout change to re-fit. */
3690
+ fitPage(): void | Promise<void>;
3691
+ }
3692
+
2431
3693
  export { }