@silurus/ooxml 0.75.3 → 0.75.4

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.
@@ -1,4 +1,4 @@
1
- //#region dist/.types-work/mathjax-BO1uUSCW.d.ts
1
+ //#region dist/.types-work/mathjax-Cp9KrvN5.d.ts
2
2
  //#region packages/core/src/math/mathjax.d.ts
3
3
  interface MathSvg {
4
4
  /** standalone `<svg>…</svg>` markup. */
@@ -29,7 +29,7 @@ interface MathRenderer {
29
29
  mathMLToSvg(mathml: string): Promise<MathSvg>;
30
30
  }
31
31
  //#endregion
32
- //#region dist/.types-work/hyperlink-CHHEOeCL.d.ts
32
+ //#region dist/.types-work/hyperlink-DTGUpBI3.d.ts
33
33
  //#region packages/core/src/types/math.d.ts
34
34
  type MathStyle = 'roman' | 'italic' | 'bold' | 'boldItalic';
35
35
  interface MathRun {
@@ -180,182 +180,654 @@ interface Duotone$1 {
180
180
  clr2: string;
181
181
  }
182
182
  //#endregion
183
- //#region packages/core/src/types/chart.d.ts
184
- interface ChartSeries {
185
- name: string;
186
- /** Hex without '#'. null = fall back to palette. */
187
- color: string | null;
188
- /** Numeric values; null = missing data point. */
189
- values: (number | null)[];
183
+ //#region packages/core/src/types/common.d.ts
184
+ type PathCmd$2 = {
185
+ cmd: 'moveTo';
186
+ x: number;
187
+ y: number;
188
+ } | {
189
+ cmd: 'lineTo';
190
+ x: number;
191
+ y: number;
192
+ } | {
193
+ cmd: 'cubicBezTo';
194
+ x1: number;
195
+ y1: number;
196
+ x2: number;
197
+ y2: number;
198
+ x: number;
199
+ y: number;
200
+ } | {
201
+ cmd: 'arcTo';
202
+ wr: number;
203
+ hr: number;
204
+ stAng: number;
205
+ swAng: number;
206
+ } | {
207
+ cmd: 'close';
208
+ };
209
+ type Fill = SolidFill | NoFill | GradientFill | PatternFill | ImageFill;
210
+ interface SolidFill {
211
+ fillType: 'solid';
212
+ color: string;
213
+ }
214
+ interface NoFill {
215
+ fillType: 'none';
216
+ }
217
+ interface GradientStop$1 {
218
+ position: number;
219
+ color: string;
220
+ }
221
+ interface GradientFill {
222
+ fillType: 'gradient';
223
+ stops: GradientStop$1[];
224
+ /** degrees: 0 = left→right, 90 = top→bottom */
225
+ angle: number;
226
+ /** 'linear' | 'radial' */
227
+ gradType: string;
228
+ }
229
+ /**
230
+ * Preset pattern fill — ECMA-376 §20.1.8.40 (CT_PatternFillProperties)
231
+ * with `preset` drawn from §20.1.10.59 (ST_PresetPatternVal).
232
+ */
233
+ interface PatternFill {
234
+ fillType: 'pattern';
235
+ /** Foreground hex colour — used for the "1" pixels of the preset bitmap. */
236
+ fg: string;
237
+ /** Background hex colour — used for the "0" pixels. */
238
+ bg: string;
239
+ /** Preset name, e.g. "pct25", "horz", "diagCross", "lgGrid". */
240
+ preset: string;
241
+ }
242
+ /**
243
+ * ECMA-376 §20.1.8.30 (CT_RelativeRect) — the destination rectangle a stretched
244
+ * blip is mapped into, as edge insets relative to the fill region. Values are
245
+ * fractions (ST_Percentage / 100000); **negative values let the image bleed
246
+ * past the box (overscan)**. Absent edges default to 0.
247
+ */
248
+ interface FillRect {
249
+ l?: number;
250
+ t?: number;
251
+ r?: number;
252
+ b?: number;
253
+ }
254
+ /**
255
+ * ECMA-376 §20.1.8.58 (CT_TileInfoProperties) — tiled blip-fill placement.
256
+ * The blip repeats at its native size (scaled by sx/sy) across the fill box.
257
+ * Mutually exclusive with {@link ImageFill.fillRect} (the `stretch` mode).
258
+ */
259
+ interface TileInfo {
260
+ /** Horizontal offset of the first tile, in EMU (`tx`). Default 0. */
261
+ tx: number;
262
+ /** Vertical offset of the first tile, in EMU (`ty`). Default 0. */
263
+ ty: number;
264
+ /** Horizontal tile scale as a fraction (`sx` / 100000). Default 1.0. */
265
+ sx: number;
266
+ /** Vertical tile scale as a fraction (`sy` / 100000). Default 1.0. */
267
+ sy: number;
268
+ /** Mirror mode: `'none' | 'x' | 'y' | 'xy'` (`flip`). Default `'none'`. */
269
+ flip: string;
190
270
  /**
191
- * Per-data-point colors (pie / doughnut). Hex without '#'. null inside the
192
- * array = use palette for that slice. Omit entirely for non-pie series.
271
+ * Anchor corner the tile grid registers against:
272
+ * `tl|t|tr|l|ctr|r|bl|b|br` (`algn`). The schema has no default; a host may
273
+ * apply a compatibility fallback when omitted.
193
274
  */
194
- dataPointColors?: (string | null)[] | null;
275
+ algn?: string;
276
+ }
277
+ /**
278
+ * Image fill — ECMA-376 §20.1.8.14 (CT_BlipFillProperties). The embedded blip
279
+ * is carried as a zip path + MIME; the renderer fetches the bytes on demand via
280
+ * {@link RenderOptions.fetchImage} (no base64 inlined at parse time). Both
281
+ * fill-modes are modelled and mutually exclusive: `stretch` (§20.1.8.56) carries
282
+ * {@link ImageFill.fillRect}; `tile` (§20.1.8.58) carries {@link ImageFill.tile}.
283
+ */
284
+ interface ImageFill {
285
+ fillType: 'image';
195
286
  /**
196
- * Per-data-point data-label text colors. Used by chartEx (`<cx:dataLabel idx>`)
197
- * to override label colour per bar sample-2's waterfall paints negative
198
- * values in red while positive values stay black. Null inside the array =
199
- * fall back to the chart-level `dataLabelFontColor`.
287
+ * Embedded zip path of the blip (e.g. "word/media/image1.png"), for the lazy
288
+ * byte-on-demand pipeline. The renderer fetches the bytes via a path-keyed
289
+ * loader ({@link RenderOptions.fetchImage}) instead of inlining base64.
200
290
  */
201
- dataLabelColors?: (string | null)[] | null;
291
+ imagePath: string;
292
+ /** MIME type of the blip at {@link ImageFill.imagePath} (e.g. `image/png`). */
293
+ mimeType: string;
202
294
  /**
203
- * Series-level data-label text colour (`<c:ser><c:dLbls><c:txPr>…solidFill`,
204
- * ECMA-376 §21.2.2.216). Hex without '#'. Stacked-bar charts colour each
205
- * segment's label independently (e.g. white on the dark segment, black on
206
- * the light one), which a single chart-level `dataLabelFontColor` can't
207
- * express. Takes precedence over `dataLabelFontColor`; null = no override.
295
+ * `<a:stretch><a:fillRect>` insets. Absent fills the whole box (or the
296
+ * fill is tiled see {@link ImageFill.tile}).
208
297
  */
209
- labelColor?: string | null;
298
+ fillRect?: FillRect;
210
299
  /**
211
- * Mixed chart: per-series chart type override. Currently only "line" (XLSX
212
- * and PPTX combo charts) is honoured; other values are treated as the
213
- * chart's primary type.
300
+ * `<a:tile>` descriptor. Present only when the blipFill is tiled; mutually
301
+ * exclusive with {@link ImageFill.fillRect}.
214
302
  */
215
- seriesType?: string | null;
303
+ tile?: TileInfo;
304
+ /** `a:blip > a:alphaModFix@amt` as a fraction (0.0–1.0). Absent = opaque. */
305
+ alpha?: number;
216
306
  /**
217
- * Combo chart: this series is plotted against the SECONDARY value axis
218
- * (`ChartModel.secondaryValAxis`) the `<c:valAx>` with `axPos="r"` /
219
- * `<c:crosses val="max">`. When false/absent the series uses the primary
220
- * (left) value-axis scale. PowerPoint's "Revenue vs. gross margin" combo
221
- * (sample-14 slide-8) puts the margin line on a 0–100% secondary axis.
307
+ * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
308
+ * colours (through the slide theme). Absent no duotone. When present the
309
+ * renderer maps the blip's luminance ramp between the two colours (core
310
+ * `applyDuotone`) the same recolour a `<p:pic>` duotone applies, wired onto
311
+ * the picture-FILL path (§20.1.8.14) by issue #889.
222
312
  */
223
- useSecondaryAxis?: boolean | null;
313
+ duotone?: Duotone$1;
314
+ }
315
+ interface Shadow {
316
+ color: string;
317
+ alpha: number;
318
+ blur: number;
319
+ dist: number;
320
+ /** degrees clockwise from East */
321
+ dir: number;
322
+ }
323
+ /** ECMA-376 §20.1.8.17 (CT_GlowEffect) — coloured halo with blur radius. */
324
+ interface Glow {
325
+ color: string;
326
+ alpha: number;
327
+ /** Blur radius in EMU. */
328
+ radius: number;
329
+ }
330
+ /** ECMA-376 §20.1.8.31 (CT_SoftEdgesEffect) — feather radius in EMU. */
331
+ interface SoftEdge {
332
+ radius: number;
333
+ }
334
+ /** ECMA-376 §20.1.8.27 (CT_ReflectionEffect) — mirrored copy below the
335
+ * shape with a linear alpha gradient. Carries the spec attributes whose
336
+ * defaults the renderer needs to interpret correctly. */
337
+ interface Reflection {
338
+ blur: number;
339
+ dist: number;
340
+ /** Direction in degrees, clockwise from East. */
341
+ dir: number;
342
+ /** Start alpha (0–1). Default 1.0. */
343
+ stA: number;
344
+ /** Start position along the gradient (0–1). Default 0. */
345
+ stPos: number;
346
+ /** End alpha. Default 0. */
347
+ endA: number;
348
+ /** End position. Default 1.0. */
349
+ endPos: number;
350
+ /** Horizontal scale (1.0 = same width). */
351
+ sx: number;
352
+ /** Vertical scale (-1.0 = full mirror). */
353
+ sy: number;
354
+ }
355
+ interface ArrowEnd {
356
+ /** OOXML type: "none" | "triangle" | "stealth" | "diamond" | "oval" | "arrow" */
357
+ type: string;
358
+ /** Width multiplier: "sm" | "med" | "lg" */
359
+ w: string;
360
+ /** Length multiplier: "sm" | "med" | "lg" */
361
+ len: string;
362
+ }
363
+ interface Stroke {
364
+ color: string;
365
+ /** Width in EMU */
366
+ width: number;
367
+ /** Authored non-solid DrawingML line paint. Solid lines use `color`. */
368
+ fill?: Exclude<Fill, {
369
+ fillType: 'image';
370
+ } | {
371
+ fillType: 'none';
372
+ }>;
373
+ /** OOXML prstDash value: "dash", "dot", "dashDot", "lgDash", "lgDashDot", etc. */
374
+ dashStyle?: string;
375
+ /** Canvas line cap normalized from DrawingML/VML (`flat` → `butt`). */
376
+ lineCap?: CanvasLineCap;
377
+ /** Arrow head at the start of the line */
378
+ headEnd?: ArrowEnd;
379
+ /** Arrow head at the end of the line */
380
+ tailEnd?: ArrowEnd;
224
381
  /**
225
- * Scatter-only X values (as strings). When null the series uses
226
- * `ChartModel.categories` as X.
382
+ * ECMA-376 §20.1.8.42 ST_CompoundLine. "sng" (default) | "dbl" |
383
+ * "thinThick" | "thickThin" | "tri". Absent means single line.
227
384
  */
228
- categories?: string[] | null;
385
+ cmpd?: string;
386
+ }
387
+ interface TextBody$1 {
388
+ /** Vertical anchor: "t" | "ctr" | "b" */
389
+ verticalAnchor: string;
390
+ paragraphs: Paragraph$1[];
391
+ /** Default pt size from lstStyle (overrides renderer default when present) */
392
+ defaultFontSize: number | null;
393
+ /** Inherited bold from layout/master defRPr (null = not set, use false as final default) */
394
+ defaultBold: boolean | null;
395
+ /** Inherited italic from layout/master defRPr (null = not set, use false as final default) */
396
+ defaultItalic: boolean | null;
397
+ /** Text insets in EMU (defaults: lIns=rIns=91440, tIns=bIns=45720) */
398
+ lIns: number;
399
+ rIns: number;
400
+ tIns: number;
401
+ bIns: number;
402
+ /** "square" = wrap, "none" = no wrap */
403
+ wrap: string;
404
+ /** Text direction: "horz" | "vert" | "vert270" | "eaVert" etc. */
405
+ vert: string;
406
+ /** Auto-fit: "sp" = shape grows to fit text, "norm" = font shrinks, "none" = no fit */
407
+ autoFit: string;
229
408
  /**
230
- * Resolved marker visibility for line/scatter series. ECMA-376 §21.2.2.32
231
- * `<c:marker><c:symbol>` defaults to "none" for line charts unless the
232
- * chart-level `<c:marker val="1"/>` or a per-series symbol opts in. When
233
- * undefined/null the renderer uses its own default (visible) so callers
234
- * that don't parse markers (e.g. pptx today) keep their existing behavior.
409
+ * `<a:normAutofit fontScale>` (ECMA-376 §21.1.2.1.3) — PowerPoint's stored,
410
+ * pre-computed font-shrink ratio for `autoFit === "norm"`, as a fraction
411
+ * (e.g. 0.625 for `fontScale="62500"`). Null/absent when PowerPoint stored no
412
+ * scale; the renderer then re-derives one. Applying the stored value matches
413
+ * PowerPoint exactly instead of guessing from our own text metrics.
235
414
  */
236
- showMarker?: boolean | null;
237
- /**
238
- * Excel number-format code for this series' values (ECMA-376 §21.2.2.37,
239
- * `<c:val>/<c:numRef>/<c:formatCode>`). Used to format data labels when the
240
- * chart-level `<c:dLbls><c:numFmt>` is not set. null = no series-level code.
415
+ fontScale?: number | null;
416
+ /** `<a:normAutofit lnSpcReduction>` — stored line-spacing reduction fraction
417
+ * (e.g. 0.20 for `lnSpcReduction="20000"`). Null/absent when not stored. */
418
+ lnSpcReduction?: number | null;
419
+ /**
420
+ * `<a:bodyPr numCol>` (ECMA-376 §20.1.10.34) — number of text columns inside
421
+ * the shape. Defaults to 1; values > 1 cause the renderer to flow paragraphs
422
+ * across N columns left-to-right, top-to-bottom.
241
423
  */
242
- valFormatCode?: string | null;
424
+ numCol?: number;
425
+ /** `<a:bodyPr spcCol>` — gap between columns in EMU. Default 0. */
426
+ spcCol?: number;
427
+ }
428
+ type SpaceLine = {
429
+ type: 'pct';
430
+ val: number;
431
+ } | {
432
+ type: 'pts';
433
+ val: number;
434
+ };
435
+ /**
436
+ * A paragraph's bullet marker. For `char`, the marker size is EITHER `sizePct`
437
+ * (a percentage of the run size — ECMA-376 §21.1.2.4.9 `<a:buSzPct>`) OR `sizePts`
438
+ * (an absolute size in points — §21.1.2.4.10 `<a:buSzPts>`), never both: they are
439
+ * the one `EG_TextBulletSize` xsd:choice. `sizePts` is optional (absent when no
440
+ * `<a:buSzPts>` was declared); when present it takes precedence over `sizePct`.
441
+ */
442
+ type Bullet$1 = {
443
+ type: 'none';
444
+ } | {
445
+ type: 'inherit';
446
+ } | {
447
+ type: 'char';
448
+ char: string;
449
+ color: string | null;
450
+ sizePct: number | null;
451
+ sizePts?: number;
452
+ fontFamily: string | null;
453
+ } | {
454
+ type: 'autoNum';
455
+ numType: string;
456
+ startAt: number | null;
457
+ color: string | null;
458
+ };
459
+ interface TabStop$1 {
460
+ /** Position in EMU from the LEADING text-inset edge of the text area —
461
+ * logical, not physical (ECMA-376 §21.1.2.1): the left edge (after lIns)
462
+ * in an LTR paragraph, the right edge (before rIns) in an RTL
463
+ * (`<a:pPr rtl="1">`) paragraph. */
464
+ pos: number;
465
+ /** Alignment: "l" | "r" | "ctr" | "dec" */
466
+ algn: string;
467
+ }
468
+ interface Paragraph$1 {
469
+ /** Alignment: "l" | "ctr" | "r" | "just" */
470
+ alignment: string;
471
+ /** Left margin in EMU */
472
+ marL: number;
473
+ /** Right margin in EMU */
474
+ marR: number;
475
+ /** First-line indent in EMU (negative = hanging indent) */
476
+ indent: number;
477
+ spaceBefore: number | null;
478
+ spaceAfter: number | null;
479
+ spaceLine: SpaceLine | null;
480
+ /** List nesting level (0–8) */
481
+ lvl: number;
482
+ bullet: Bullet$1;
483
+ defFontSize: number | null;
484
+ defColor: string | null;
485
+ defBold: boolean | null;
486
+ defItalic: boolean | null;
487
+ defFontFamily: string | null;
488
+ /** Tab stops from pPr > tabLst */
489
+ tabStops: TabStop$1[];
243
490
  /**
244
- * `<c:marker><c:symbol val>` (ECMA-376 §21.2.2.32) — point marker shape.
245
- * One of "circle"|"square"|"diamond"|"triangle"|"x"|"plus"|"star"|
246
- * "dot"|"dash"|"picture"|"none". null = renderer default (circle when
247
- * showMarker is true).
491
+ * `<a:pPr rtl="1">` — right-to-left paragraph (ECMA-376 §21.1.2.2.7).
492
+ * When true and no explicit `algn`, the parser-side default flips from
493
+ * "l" to "r"; renderers can also use this flag to flow runs RTL.
248
494
  */
249
- markerSymbol?: string | null;
495
+ rtl?: boolean;
496
+ runs: TextRun[];
497
+ }
498
+ type TextRun = TextRunData | LineBreak | EquationRun;
499
+ /**
500
+ * An OMML equation embedded in a paragraph (ECMA-376 §22.1). Parsed into the
501
+ * shared math AST and rendered by `@silurus/ooxml-core`'s math engine.
502
+ * PowerPoint stores these as `a14:m` inside `mc:AlternateContent`.
503
+ */
504
+ interface EquationRun {
505
+ type: 'math';
506
+ /** Parsed OMML node list. */
507
+ nodes: MathNode[];
508
+ /** True for block (`m:oMathPara`) math, false for inline (`m:oMath`). */
509
+ display: boolean;
510
+ /** Paragraph default run size in pt, if declared; absent → renderer inherits. */
511
+ fontSize?: number | null;
512
+ /** Equation colour (hex, no '#') from the math run's rPr; absent → inherit. */
513
+ color?: string | null;
514
+ }
515
+ interface TextRunData {
516
+ type: 'text';
517
+ text: string;
518
+ /** null = not set, inherit from paragraph/body defaults */
519
+ bold: boolean | null;
520
+ /** null = not set, inherit from paragraph/body defaults */
521
+ italic: boolean | null;
522
+ underline: boolean;
250
523
  /**
251
- * `<c:marker><c:size val>` (ECMA-376 §21.2.2.34) marker side length in
252
- * points. null = renderer default (~5 pt).
524
+ * Specific underline style when not the default single line. Values come
525
+ * from ECMA-376 §21.1.2.3.16 (ST_TextUnderlineType): "dbl", "heavy",
526
+ * "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong",
527
+ * "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash",
528
+ * "dotDotDashHeavy", "wavy", "wavyHeavy", "wavyDbl". Absent means either
529
+ * no underline (when `underline` is false) or the default single line.
253
530
  */
254
- markerSize?: number | null;
255
- /** `<c:marker><c:spPr><a:solidFill>` resolved hex (no `#`). */
256
- markerFill?: string | null;
257
- /** `<c:marker><c:spPr><a:ln><a:solidFill>` resolved hex (no `#`). */
258
- markerLine?: string | null;
531
+ underlineStyle?: string;
259
532
  /**
260
- * Per-data-point overrides (ECMA-376 §21.2.2.39 `<c:dPt>`). Keyed by point
261
- * index. Any unset field falls back to the series-level value.
533
+ * Underline-only colour from rPr > uFill (ECMA-376 §21.1.2.3.20). Absent
534
+ * means the underline follows the text colour (uFillTx default).
262
535
  */
263
- dataPointOverrides?: ChartDataPointOverride[] | null;
536
+ underlineColor?: string;
537
+ /** True when rPr strike is sngStrike or dblStrike. */
538
+ strikethrough: boolean;
264
539
  /**
265
- * Per-data-point custom labels (ECMA-376 §21.2.2.45 `<c:dLbl idx>`).
266
- * `text` is the resolved plain string — `<a:fld type="CELLRANGE">`
267
- * placeholders are already substituted at parse time. An empty string
268
- * means the point's label was deleted with `<c:delete val="1"/>` and
269
- * the renderer should skip it.
540
+ * True only when rPr strike = "dblStrike". Lets the renderer draw two parallel
541
+ * lines instead of one. ECMA-376 §21.1.2.3.10 (ST_TextStrikeType).
270
542
  */
271
- dataLabelOverrides?: ChartDataLabelOverride[] | null;
543
+ strikeDouble?: boolean;
544
+ /** Font size in points */
545
+ fontSize: number | null;
546
+ color: string | null;
547
+ fontFamily: string | null;
272
548
  /**
273
- * Series-level `<c:dLbls>` block (showVal / showSerName / position).
274
- * Applied to every point lacking its own `<c:dLbl>` override.
549
+ * East Asian font family from rPr > a:ea (ECMA-376 §21.1.2.3.7),
550
+ * resolved through the theme. Renderer uses this for CJK glyphs when
551
+ * present; absent means CJK falls back to fontFamily.
275
552
  */
276
- seriesDataLabels?: ChartSeriesDataLabels | null;
553
+ fontFamilyEa?: string;
277
554
  /**
278
- * `<c:errBars>` per-series error bars (ECMA-376 §21.2.2.20). Up to two
279
- * (one per direction). Plus / minus deltas are absolute per-point values
280
- * regardless of `errValType`.
555
+ * Symbol font family from rPr > a:sym (ECMA-376 §21.1.2.3.10), resolved
556
+ * through the theme. PowerPoint stores symbol-font glyphs as Private-Use
557
+ * codepoints U+F020–U+F0FF; the renderer uses this font to resolve them.
558
+ * Absent means no symbol font was declared.
281
559
  */
282
- errBars?: ChartErrBars[] | null;
560
+ fontFamilySym?: string;
561
+ /** Baseline shift in thousandths of a point. Positive = superscript, negative = subscript. */
562
+ baseline?: number;
283
563
  /**
284
- * `<c:bubbleSize>` per-point sizes for bubble charts (ECMA-376 §21.2.2.4).
285
- * Drives marker radius renderer treats the values as areas (radius
286
- * scales by sqrt) so visual area is proportional to value, matching
287
- * Excel. null / empty array = uniform marker size. Ignored for non-bubble
288
- * series.
564
+ * Capitalisation transform ECMA-376 §21.1.2.3.13 (ST_TextCapsType).
565
+ * 'all' renders text in upper case; 'small' uses small caps (rendered as
566
+ * upper case at ~80% size when no smcp font feature is available).
567
+ * 'none' or omitted leaves the text unchanged.
289
568
  */
290
- bubbleSizes?: (number | null)[] | null;
569
+ caps?: 'none' | 'small' | 'all';
291
570
  /**
292
- * `<c:ser><c:smooth val>` (ECMA-376 §21.2.2.194) — line/area series flag
293
- * requesting a smoothed (spline) curve through the points instead of straight
294
- * segments. Only consulted for the line and area families (scatter carries its
295
- * smoothing in `ChartModel.scatterStyle`). null/undefined/false = straight
296
- * polyline (the default; byte-stable for series that never set it).
571
+ * Inter-character spacing in 100ths of a point — ECMA-376 §21.1.2.3.5
572
+ * (rPr @spc). Positive values add space, negative values tighten.
297
573
  */
298
- smooth?: boolean | null;
574
+ letterSpacing?: number;
575
+ /** Set for OOXML field runs (e.g. "slidenum"). When set, renderer replaces text with field value. */
576
+ fieldType?: string;
299
577
  /**
300
- * `<c:ser><c:trendline>` per-series trendlines (ECMA-376 §21.2.2.211,
301
- * `CT_Trendline`). A series can carry several (e.g. a linear fit + a moving
302
- * average). null/undefined/empty = no trendline (the default; byte-stable for
303
- * series that never declare one).
578
+ * Hyperlink target resolved from rPr > a:hlinkClick @r:id via the slide's _rels.
579
+ * For an external link this is the URL; for an internal slide jump it is the
580
+ * resolved internal part name (e.g. "../slides/slide3.xml"). Undefined for runs
581
+ * without a hyperlink. ECMA-376 §21.1.2.3.5 (CT_Hyperlink).
304
582
  */
305
- trendLines?: ChartTrendline[] | null;
583
+ hyperlink?: string;
306
584
  /**
307
- * `<c:ser><c:spPr><a:ln><a:noFill/>` (ECMA-376 §21.2.2.198 CT_ShapeProperties
308
- * DrawingML §20.1.2.2.24 CT_LineProperties). true when the series connecting
309
- * line is explicitly turned OFF. For a scatter/line series this OVERRIDES the
310
- * chart-group `<c:scatterStyle>` (§21.2.2.42) / line default — Excel and
311
- * PowerPoint draw markers only (no connecting line) even when the group style
312
- * is `lineMarker`. null/undefined = no explicit line-off, so the group default
313
- * governs (byte-stable for series that carry a paintable line).
585
+ * Raw `<a:hlinkClick @action>` string (e.g. "ppaction://hlinksldjump") when
586
+ * present its presence marks {@link hyperlink} as an INTERNAL PowerPoint
587
+ * action (slide jump / first / last …) rather than an external URL. Undefined
588
+ * when the hlinkClick has no @action. ECMA-376 §21.1.2.3.5. (IX1)
314
589
  */
315
- lineHidden?: boolean | null;
316
- }
317
- /**
318
- * `<c:ser><c:trendline>` (ECMA-376 §21.2.2.211). A regression/smoothing curve
319
- * fitted to the series' data points.
320
- */
321
- interface ChartTrendline {
590
+ hyperlinkAction?: string;
322
591
  /**
323
- * `<c:trendlineType val>` (§21.2.2.213, `ST_TrendlineType` §21.2.3.50):
324
- * "linear" | "exp" | "log" | "power" | "poly" | "movingAvg". The renderer
325
- * currently draws "linear" (least squares) and "movingAvg"; other types parse
326
- * but are not yet plotted (tracked as a follow-up).
592
+ * Run-level drop shadow on glyphs (`<a:rPr><a:effectLst><a:outerShdw>`),
593
+ * ECMA-376 §20.1.8.45. Independent of the shape-level shadow on `spPr`.
594
+ * Absent means no run-level shadow.
327
595
  */
328
- trendlineType: string;
329
- /** `<c:order val>` — polynomial order (`poly`, default 2). */
330
- order?: number | null;
331
- /** `<c:period val>` — moving-average window (`movingAvg`, default 2). */
332
- period?: number | null;
333
- /** `<c:forward val>` — units to extend the line past the last point. */
334
- forward?: number | null;
335
- /** `<c:backward val>` — units to extend the line before the first point. */
336
- backward?: number | null;
337
- /** `<c:intercept val>` — forced y-intercept (linear/exp). null = free fit. */
338
- intercept?: number | null;
339
- /** `<c:dispRSqr val="1">` — show the R² value (label; not yet rendered). */
340
- dispRSqr?: boolean | null;
341
- /** `<c:dispEq val="1">` — show the fit equation (label; not yet rendered). */
342
- dispEq?: boolean | null;
343
- /** `<c:spPr><a:ln><a:solidFill>` trendline color (hex without '#'). null =
344
- * inherit the series color. */
345
- lineColor?: string | null;
346
- /** `<c:spPr><a:ln w>` trendline width in EMU. */
347
- lineWidthEmu?: number | null;
348
- }
349
- interface ChartDataPointOverride {
350
- idx: number;
351
- /** Resolved fill hex (no `#`). */
352
- color?: string;
353
- markerSymbol?: string;
354
- markerSize?: number;
355
- markerFill?: string;
356
- markerLine?: string;
596
+ shadow?: Shadow;
357
597
  /**
358
- * `<c:dPt><c:explosion val>` (ECMA-376 §21.2.2.61) — the amount this
598
+ * Run-level glyph outline (`<a:rPr><a:ln w="..">`), ECMA-376 §20.1.2.2.24
599
+ * (CT_TextOutlineEffect). Renderer strokes each glyph with the given
600
+ * width / colour in addition to the normal fill. Absent means glyphs are
601
+ * fill-only.
602
+ */
603
+ outline?: TextOutline;
604
+ /**
605
+ * Run-level text highlight / marker colour (`<a:rPr><a:highlight>`),
606
+ * ECMA-376 §21.1.2.3.4. In DrawingML this is a full CT_Color (any
607
+ * srgbClr / schemeClr / sysClr / prstClr + transforms), unlike
608
+ * WordprocessingML's fixed 16-name highlight enum — so the parser already
609
+ * resolves it through the theme/clrMap. The value is a hex string without
610
+ * `#` (6-char opaque, or 8-char RRGGBBAA when an alpha transform applies);
611
+ * the renderer paints a background rectangle behind the run's glyphs.
612
+ * Absent means no highlight.
613
+ */
614
+ highlight?: string;
615
+ }
616
+ /** Run-level glyph outline. Width is in OOXML EMU (12700 EMU = 1 pt). */
617
+ interface TextOutline {
618
+ width: number;
619
+ /** Hex without '#'. Absent = inherit from text fill colour. */
620
+ color?: string;
621
+ }
622
+ interface LineBreak {
623
+ type: 'break';
624
+ }
625
+ interface RenderOptions {
626
+ width?: number;
627
+ defaultTextColor?: string | null;
628
+ dpr?: number;
629
+ majorFont?: string | null;
630
+ minorFont?: string | null;
631
+ /** Theme hyperlink colour (hex 6 chars). Used to colour hyperlink runs without an explicit colour. */
632
+ hlinkColor?: string | null;
633
+ /**
634
+ * Lazily resolve an archive-internal asset (by zip path) to a Blob. The
635
+ * renderer uses this to fetch posters and other large embedded assets on
636
+ * demand, keeping the parse output free of inlined base64.
637
+ */
638
+ fetchMedia?: (path: string) => Promise<Blob>;
639
+ /**
640
+ * Lazily resolve an embedded image (by zip path + MIME) to a Blob. Twin of
641
+ * {@link RenderOptions.fetchMedia} for pictures and blip fills: the renderer
642
+ * fetches raster/SVG bytes on demand and decodes them (`createImageBitmap` /
643
+ * path-keyed `<img>`), so the parse output carries only paths, never base64.
644
+ */
645
+ fetchImage?: (path: string, mimeType: string) => Promise<Blob>;
646
+ /**
647
+ * When true, renderMedia draws only the poster frame — play/pause badges
648
+ * and progress bars are left to the caller. Set by the pptx presentSlide
649
+ * API so its interactive handle can own all control chrome without
650
+ * the static renderer drawing a duplicate play badge.
651
+ */
652
+ skipMediaControls?: boolean;
653
+ }
654
+ //#endregion
655
+ //#region packages/core/src/types/chart.d.ts
656
+ interface ChartSeries {
657
+ name: string;
658
+ /** Hex without '#'. null = fall back to palette. */
659
+ color: string | null;
660
+ /** Numeric values; null = missing data point. */
661
+ values: (number | null)[];
662
+ /**
663
+ * Per-data-point colors (pie / doughnut). Hex without '#'. null inside the
664
+ * array = use palette for that slice. Omit entirely for non-pie series.
665
+ */
666
+ dataPointColors?: (string | null)[] | null;
667
+ /**
668
+ * Per-data-point data-label text colors. Used by chartEx (`<cx:dataLabel idx>`)
669
+ * to override label colour per bar — sample-2's waterfall paints negative
670
+ * △ values in red while positive values stay black. Null inside the array =
671
+ * fall back to the chart-level `dataLabelFontColor`.
672
+ */
673
+ dataLabelColors?: (string | null)[] | null;
674
+ /**
675
+ * Series-level data-label text colour (`<c:ser><c:dLbls><c:txPr>…solidFill`,
676
+ * ECMA-376 §21.2.2.216). Hex without '#'. Stacked-bar charts colour each
677
+ * segment's label independently (e.g. white on the dark segment, black on
678
+ * the light one), which a single chart-level `dataLabelFontColor` can't
679
+ * express. Takes precedence over `dataLabelFontColor`; null = no override.
680
+ */
681
+ labelColor?: string | null;
682
+ /**
683
+ * Mixed chart: per-series chart type override. Currently only "line" (XLSX
684
+ * and PPTX combo charts) is honoured; other values are treated as the
685
+ * chart's primary type.
686
+ */
687
+ seriesType?: string | null;
688
+ /**
689
+ * Combo chart: this series is plotted against the SECONDARY value axis
690
+ * (`ChartModel.secondaryValAxis`) — the `<c:valAx>` with `axPos="r"` /
691
+ * `<c:crosses val="max">`. When false/absent the series uses the primary
692
+ * (left) value-axis scale. PowerPoint's "Revenue vs. gross margin" combo
693
+ * (sample-14 slide-8) puts the margin line on a 0–100% secondary axis.
694
+ */
695
+ useSecondaryAxis?: boolean | null;
696
+ /**
697
+ * Scatter-only X values (as strings). When null the series uses
698
+ * `ChartModel.categories` as X.
699
+ */
700
+ categories?: string[] | null;
701
+ /**
702
+ * Resolved marker visibility for line/scatter series. ECMA-376 §21.2.2.32
703
+ * `<c:marker><c:symbol>` defaults to "none" for line charts unless the
704
+ * chart-level `<c:marker val="1"/>` or a per-series symbol opts in. When
705
+ * undefined/null the renderer uses its own default (visible) so callers
706
+ * that don't parse markers (e.g. pptx today) keep their existing behavior.
707
+ */
708
+ showMarker?: boolean | null;
709
+ /**
710
+ * Excel number-format code for this series' values (ECMA-376 §21.2.2.37,
711
+ * `<c:val>/<c:numRef>/<c:formatCode>`). Used to format data labels when the
712
+ * chart-level `<c:dLbls><c:numFmt>` is not set. null = no series-level code.
713
+ */
714
+ valFormatCode?: string | null;
715
+ /**
716
+ * `<c:marker><c:symbol val>` (ECMA-376 §21.2.2.32) — point marker shape.
717
+ * One of "circle"|"square"|"diamond"|"triangle"|"x"|"plus"|"star"|
718
+ * "dot"|"dash"|"picture"|"none". null = renderer default (circle when
719
+ * showMarker is true).
720
+ */
721
+ markerSymbol?: string | null;
722
+ /**
723
+ * `<c:marker><c:size val>` (ECMA-376 §21.2.2.34) — marker side length in
724
+ * points. null = renderer default (~5 pt).
725
+ */
726
+ markerSize?: number | null;
727
+ /** `<c:marker><c:spPr><a:solidFill>` resolved hex (no `#`). */
728
+ markerFill?: string | null;
729
+ /** `<c:marker><c:spPr><a:ln><a:solidFill>` resolved hex (no `#`). */
730
+ markerLine?: string | null;
731
+ /**
732
+ * Per-data-point overrides (ECMA-376 §21.2.2.39 `<c:dPt>`). Keyed by point
733
+ * index. Any unset field falls back to the series-level value.
734
+ */
735
+ dataPointOverrides?: ChartDataPointOverride[] | null;
736
+ /**
737
+ * Per-data-point custom labels (ECMA-376 §21.2.2.45 `<c:dLbl idx>`).
738
+ * `text` is the resolved plain string — `<a:fld type="CELLRANGE">`
739
+ * placeholders are already substituted at parse time. An empty string
740
+ * means the point's label was deleted with `<c:delete val="1"/>` and
741
+ * the renderer should skip it.
742
+ */
743
+ dataLabelOverrides?: ChartDataLabelOverride[] | null;
744
+ /**
745
+ * Series-level `<c:dLbls>` block (showVal / showSerName / position).
746
+ * Applied to every point lacking its own `<c:dLbl>` override.
747
+ */
748
+ seriesDataLabels?: ChartSeriesDataLabels | null;
749
+ /**
750
+ * `<c:errBars>` per-series error bars (ECMA-376 §21.2.2.20). Up to two
751
+ * (one per direction). Plus / minus deltas are absolute per-point values
752
+ * regardless of `errValType`.
753
+ */
754
+ errBars?: ChartErrBars[] | null;
755
+ /**
756
+ * `<c:bubbleSize>` per-point sizes for bubble charts (ECMA-376 §21.2.2.4).
757
+ * Drives marker radius — renderer treats the values as areas (radius
758
+ * scales by sqrt) so visual area is proportional to value, matching
759
+ * Excel. null / empty array = uniform marker size. Ignored for non-bubble
760
+ * series.
761
+ */
762
+ bubbleSizes?: (number | null)[] | null;
763
+ /**
764
+ * `<c:ser><c:smooth val>` (ECMA-376 §21.2.2.194) — line/area series flag
765
+ * requesting a smoothed (spline) curve through the points instead of straight
766
+ * segments. Only consulted for the line and area families (scatter carries its
767
+ * smoothing in `ChartModel.scatterStyle`). null/undefined/false = straight
768
+ * polyline (the default; byte-stable for series that never set it).
769
+ */
770
+ smooth?: boolean | null;
771
+ /**
772
+ * `<c:ser><c:trendline>` per-series trendlines (ECMA-376 §21.2.2.211,
773
+ * `CT_Trendline`). A series can carry several (e.g. a linear fit + a moving
774
+ * average). null/undefined/empty = no trendline (the default; byte-stable for
775
+ * series that never declare one).
776
+ */
777
+ trendLines?: ChartTrendline[] | null;
778
+ /**
779
+ * `<c:ser><c:spPr><a:ln><a:noFill/>` (ECMA-376 §21.2.2.198 CT_ShapeProperties
780
+ * → DrawingML §20.1.2.2.24 CT_LineProperties). true when the series connecting
781
+ * line is explicitly turned OFF. For a scatter/line series this OVERRIDES the
782
+ * chart-group `<c:scatterStyle>` (§21.2.2.42) / line default — Excel and
783
+ * PowerPoint draw markers only (no connecting line) even when the group style
784
+ * is `lineMarker`. null/undefined = no explicit line-off, so the group default
785
+ * governs (byte-stable for series that carry a paintable line).
786
+ */
787
+ lineHidden?: boolean | null;
788
+ }
789
+ /**
790
+ * `<c:ser><c:trendline>` (ECMA-376 §21.2.2.211). A regression/smoothing curve
791
+ * fitted to the series' data points.
792
+ */
793
+ interface ChartTrendline {
794
+ /**
795
+ * `<c:trendlineType val>` (§21.2.2.213, `ST_TrendlineType` §21.2.3.50):
796
+ * "linear" | "exp" | "log" | "power" | "poly" | "movingAvg". The renderer
797
+ * currently draws "linear" (least squares) and "movingAvg"; other types parse
798
+ * but are not yet plotted (tracked as a follow-up).
799
+ */
800
+ trendlineType: string;
801
+ /** `<c:order val>` — polynomial order (`poly`, default 2). */
802
+ order?: number | null;
803
+ /** `<c:period val>` — moving-average window (`movingAvg`, default 2). */
804
+ period?: number | null;
805
+ /** `<c:forward val>` — units to extend the line past the last point. */
806
+ forward?: number | null;
807
+ /** `<c:backward val>` — units to extend the line before the first point. */
808
+ backward?: number | null;
809
+ /** `<c:intercept val>` — forced y-intercept (linear/exp). null = free fit. */
810
+ intercept?: number | null;
811
+ /** `<c:dispRSqr val="1">` — show the R² value (label; not yet rendered). */
812
+ dispRSqr?: boolean | null;
813
+ /** `<c:dispEq val="1">` — show the fit equation (label; not yet rendered). */
814
+ dispEq?: boolean | null;
815
+ /** `<c:spPr><a:ln><a:solidFill>` trendline color (hex without '#'). null =
816
+ * inherit the series color. */
817
+ lineColor?: string | null;
818
+ /** `<c:spPr><a:ln w>` trendline width in EMU. */
819
+ lineWidthEmu?: number | null;
820
+ }
821
+ interface ChartDataPointOverride {
822
+ idx: number;
823
+ /** Resolved fill hex (no `#`). */
824
+ color?: string;
825
+ markerSymbol?: string;
826
+ markerSize?: number;
827
+ markerFill?: string;
828
+ markerLine?: string;
829
+ /**
830
+ * `<c:dPt><c:explosion val>` (ECMA-376 §21.2.2.61) — the amount this
359
831
  * pie/doughnut slice is moved out from the center. The schema type is
360
832
  * `CT_UnsignedInt` (unbounded `xsd:unsignedInt`); the spec text only says
361
833
  * "the amount the data point shall be moved from the center of the pie"
@@ -1314,7 +1786,7 @@ type HyperlinkTarget = {
1314
1786
  */
1315
1787
  declare function openExternalHyperlink(url: string, allowed?: readonly string[], win?: Pick<Window, 'open'> | undefined): boolean;
1316
1788
  //#endregion
1317
- //#region dist/.types-work/find-highlight-gMP3FAnW.d.ts
1789
+ //#region dist/.types-work/find-highlight-CnpZn3yb.d.ts
1318
1790
  //#region packages/core/src/autoResize.d.ts
1319
1791
  interface AutoResizeOptions {
1320
1792
  /**
@@ -1501,7 +1973,7 @@ interface FindHighlightColors {
1501
1973
  active?: string;
1502
1974
  }
1503
1975
  //#endregion
1504
- //#region dist/.types-work/renderer-CU7Ays06.d.ts
1976
+ //#region dist/.types-work/renderer-CNZ68tul.d.ts
1505
1977
  //#region packages/docx/src/types.d.ts
1506
1978
  interface DocxDocumentModel {
1507
1979
  section: SectionProps;
@@ -1889,7 +2361,7 @@ interface DocParagraph {
1889
2361
  spaceAfter: number;
1890
2362
  lineSpacing: LineSpacing | null;
1891
2363
  numbering: NumberingInfo | null;
1892
- tabStops: TabStop$1[];
2364
+ tabStops: TabStop[];
1893
2365
  runs: DocRun[];
1894
2366
  /**
1895
2367
  * ECMA-376 §17.13.6.2 `<w:bookmarkStart w:name>` — names of the bookmarks that
@@ -2032,7 +2504,7 @@ interface DocxRunBorder {
2032
2504
  /** pt spacing between the border and the run text (w:space) */
2033
2505
  space: number;
2034
2506
  }
2035
- interface TabStop$1 {
2507
+ interface TabStop {
2036
2508
  /** tab stop position in pt (from the left of paragraph content area) */
2037
2509
  pos: number;
2038
2510
  /** ECMA-376 ST_TabJc. `num` is the list tab between a numbering marker and
@@ -2193,7 +2665,7 @@ interface PTabRun {
2193
2665
  /** Resolved run font size (pt) — matches the surrounding text's leader/gap. */
2194
2666
  fontSize: number;
2195
2667
  }
2196
- type PathCmd$2 = {
2668
+ type PathCmd$1 = {
2197
2669
  cmd: 'moveTo';
2198
2670
  x: number;
2199
2671
  y: number;
@@ -2232,6 +2704,9 @@ interface AnchorHostMetrics {
2232
2704
  italic?: boolean;
2233
2705
  }
2234
2706
  interface ShapeRun {
2707
+ /** ECMA-376 §20.4.2.8 — true when the shape is hosted by `<wp:inline>` and
2708
+ * therefore advances the paragraph pen like an inline drawing object. */
2709
+ inline?: boolean;
2235
2710
  widthPt: number;
2236
2711
  heightPt: number;
2237
2712
  /** X offset in pt */
@@ -2277,7 +2752,7 @@ interface ShapeRun {
2277
2752
  zOrder: number;
2278
2753
  /** Normalized [0,1] custom-geometry sub-paths. Empty when `presetGeometry`
2279
2754
  * is set; the renderer chooses between buildCustomPath and buildShapePath. */
2280
- subpaths: PathCmd$2[][];
2755
+ subpaths: PathCmd$1[][];
2281
2756
  /** OOXML <a:prstGeom prst> name (e.g. "rect", "ellipse", "rtTriangle").
2282
2757
  * When set the renderer calls core's buildShapePath with `adjValues`. */
2283
2758
  presetGeometry?: string | null;
@@ -2443,7 +2918,7 @@ interface ShapeText$1 {
2443
2918
  * Absent/empty ⇒ only the automatic default-tab grid applies. The renderer
2444
2919
  * feeds these to the SAME line engine the body uses so a `\t` inside a text box
2445
2920
  * advances to its stop (the old shape wrapper dropped tabs entirely). */
2446
- tabStops?: TabStop$1[];
2921
+ tabStops?: TabStop[];
2447
2922
  /** ECMA-376 §17.3.1.6 `<w:bidi>` — right-to-left text-box paragraph, resolved
2448
2923
  * through the style chain like {@link DocParagraph.bidi}. `true` = RTL,
2449
2924
  * `false` = explicitly LTR, absent = unspecified. Consumed as the paragraph
@@ -2481,11 +2956,29 @@ type ShapeFill = {
2481
2956
  color: string;
2482
2957
  } | {
2483
2958
  fillType: 'gradient';
2484
- stops: GradientStop$1[];
2959
+ stops: GradientStop[];
2485
2960
  angle: number;
2486
2961
  gradType: string;
2962
+ } | {
2963
+ /** ECMA-376 §20.1.8.14 picture fill on a DrawingML shape. */
2964
+ fillType: 'image';
2965
+ imagePath: string;
2966
+ mimeType: string;
2967
+ /** Microsoft 2016 SVG original retained beside the raster fallback. */
2968
+ svgImagePath?: string;
2969
+ /** ECMA-376 §20.1.8.55 source-image crop. */
2970
+ srcRect?: {
2971
+ l: number;
2972
+ t: number;
2973
+ r: number;
2974
+ b: number;
2975
+ };
2976
+ fillRect?: FillRect;
2977
+ tile?: TileInfo;
2978
+ alpha?: number;
2979
+ duotone?: Duotone$1;
2487
2980
  };
2488
- interface GradientStop$1 {
2981
+ interface GradientStop {
2489
2982
  /** 0.0–1.0 */
2490
2983
  position: number;
2491
2984
  /** hex 6-char */
@@ -2624,7 +3117,8 @@ interface DocxTextRun {
2624
3117
  * width, not the gap between glyphs. Absent ⇒ 100%. */
2625
3118
  charScale?: number;
2626
3119
  /** ECMA-376 §17.3.2.24 `<w:position w:val>` — baseline raise (positive) /
2627
- * lower (negative) in POINTS, without changing the font size or line box.
3120
+ * lower (negative) in POINTS, without changing the font size. The shifted
3121
+ * ink still participates in the surrounding line's visible extent.
2628
3122
  * Absent ⇒ no shift. */
2629
3123
  position?: number;
2630
3124
  /** ECMA-376 §17.3.2.19 `<w:kern w:val>` — font-kerning threshold in POINTS
@@ -3032,7 +3526,7 @@ interface DocxTextRunInfo {
3032
3526
  eastAsianVert?: boolean;
3033
3527
  }
3034
3528
  //#endregion
3035
- //#region dist/.types-work/docx-VDKt2JLe.d.ts
3529
+ //#region dist/.types-work/docx-BI2B2aF9.d.ts
3036
3530
  //#region packages/docx/src/worker-protocol.d.ts
3037
3531
  /** Serializable subset of RenderPageOptions (callbacks cannot cross the wire). */
3038
3532
  type WireRenderPageOptions = Omit<RenderPageOptions, 'onTextRun'>;
@@ -3742,929 +4236,457 @@ declare class DocxScrollViewer implements ZoomableViewer {
3742
4236
  private _wheelListener;
3743
4237
  /** Gesture-only pointer anchor for the NEXT `setScale`, in scrollHost-viewport
3744
4238
  * px (`{ x, y }` from the wheel event, relative to the scroll host's top-left).
3745
- * Set by the Ctrl/⌘+wheel handler right before it calls `setScale` so the zoom
3746
- * pivots on the cursor ("zoom toward the pointer") in BOTH axes; consumed and
3747
- * cleared by `setScale`. `null` for every non-gesture source (the public
3748
- * `setScale`, the +/- steppers, `fitWidth`/`fitPage`, the resize re-fit), which
3749
- * keep the historical viewport-TOP re-anchor so their behaviour is unchanged. */
3750
- private _pendingZoomAnchor;
3751
- /** Observes the container so a width change re-fits the base scale. Disconnected
3752
- * in `destroy()`. */
3753
- private _resizeObserver;
3754
- /** The base fit scale at the last established/re-fit layout. `_onResize` divides
3755
- * `_scale` by this to recover the current zoom multiplier so a width change
3756
- * re-fits the base while preserving the user's zoom (design §11). */
3757
- private _prevBase;
3758
- /** The fit width (px) the base scale was last established at. Lets `_onResize`
3759
- * skip the re-fit when only the height changed (a ResizeObserver fires on ANY
3760
- * box change, but only a WIDTH change alters the fit-to-width base scale). */
3761
- private _lastFitWidth;
3762
- /** Resolved page-canvas `box-shadow` (design: the recipe drop shadow by
3763
- * default). Resolved ONCE with `??` — NOT `||` — so `pageShadow: false`
3764
- * survives as the "no shadow" sentinel (a `||` would treat `false` as absent
3765
- * and wrongly re-apply the default). Applied by `_applyPageShadow` at EVERY
3766
- * canvas-creation site (`_acquireSlot` and the double-buffer spare in
3767
- * `_settleSlot`) so a recycled/re-mounted slot and a settle-swapped spare all
3768
- * carry it. */
3769
- private readonly _pageShadow;
3770
- private readonly _find;
3771
- private _findActive;
3772
- constructor(container: HTMLElement, opts?: DocxScrollViewerOptions);
3773
- /**
3774
- * Load a DOCX from URL or ArrayBuffer and render the first window.
3775
- * UNSUPPORTED when an engine was injected via `opts.document` (throws) — the
3776
- * caller already owns the parsed engine.
3777
- */
3778
- load(source: string | ArrayBuffer): Promise<void>;
3779
- get pageCount(): number;
3780
- /** CSS px width of page `i` at the current scale. */
3781
- private _pageWidthPx;
3782
- /** CSS px height of page `i` at the current scale. */
3783
- private _pageHeightPx;
3784
- /** The fit width (px), deferring when the container is unlaid-out. An EXPLICIT
3785
- * `opts.width` is the page's CSS-width contract and is returned UNCHANGED (the
3786
- * gutters still apply around placement, not to the width). The container-derived
3787
- * default instead targets `containerWidth − padL − padR` so a page sits INSIDE
3788
- * the horizontal gutters at 100%. A non-positive result (gutters wider than the
3789
- * container) is treated as unlaid-out — the same deferral as a zero-width box. */
3790
- private _fitWidthPx;
3791
- /** Base scale: first page's width fit to the fit-width. Returns 0 when the
3792
- * container has no width yet (deferral). */
3793
- private _baseScale;
3794
- /**
3795
- * Recompute per-page heights + the spacer and re-mount the visible window.
3796
- *
3797
- * The viewer already calls this automatically after `load()`, an injected
3798
- * engine, a container resize, and a zoom, so most integrations never need it.
3799
- * It is public as a deliberate escape hatch: if the host mutates the layout in
3800
- * a way the `ResizeObserver` cannot observe (e.g. a CSS change on an ancestor
3801
- * that resizes the container without a box-size event, or a font that finishes
3802
- * loading after first paint), call `relayout()` to force a re-fit. Idempotent —
3803
- * safe to call repeatedly, and a no-op while the container has zero width (the
3804
- * fit is deferred until width appears, design §11).
3805
- */
3806
- relayout(): void;
3807
- private _recomputeHeights;
3808
- private _gap;
3809
- private _overscan;
3810
- /** Desk padding fed to `computeVisibleRange`: `paddingTop`/`paddingBottom`,
3811
- * each defaulting to `gap` (uniform rhythm). Resolved here (not stored) to
3812
- * mirror `_gap()`/`_overscan()`, and consumed at EVERY `computeVisibleRange`
3813
- * call site so the padded offsets are the single source of geometry. */
3814
- private _pad;
3815
- /** Horizontal desk gutters: `paddingLeft`/`paddingRight`, each defaulting to
3816
- * `gap` (uniform rhythm — the horizontal gutters match the vertical padding).
3817
- * Consumed by `_fitWidthPx` (to shrink the container-derived fit), by
3818
- * `_positionSlot` (the flush-left floor), and by `_syncSpacer` (the spacer
3819
- * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
3820
- private _padH;
3821
- /** Index of the page whose slot spans content-offset `y` (largest `i` with
3822
- * `offsets[i] <= y`), for the pointer-anchored zoom re-anchor. Mirrors the
3823
- * `topIndex` search `computeVisibleRange` runs for the scrollTop, but for an
3824
- * ARBITRARY content-y (the pointer, not the viewport top). Clamped into
3825
- * `[0, n-1]`; a `y` below the first page (inside the leading pad) yields 0. */
3826
- private _pageIndexAtOffset;
3827
- private _range;
3828
- private _syncSpacer;
3829
- /** Horizontal scroll extent: the widest page (docx pages can differ in width)
3830
- * plus both gutters. A spacer NARROWER than the container never creates a
3831
- * scrollbar (scrollWidth = max(clientWidth, content)), so it is always safe to
3832
- * set — it only matters when a zoomed-in page grows past the viewport, where it
3833
- * gives the gutters something to scroll to on either side. Max over per-page
3834
- * widths so the extent covers the widest page in the document. Called from
3835
- * `_syncSpacer` and after every scale change (zoom / resize re-fit) so the
3836
- * extent tracks the current page px width. */
3837
- private _syncSpacerWidth;
3838
- private _onScroll;
3839
- /** Mount/recycle slots for the current visible window. */
3840
- private _mountVisible;
3841
- /** Apply the resolved page-canvas shadow (design: recipe drop shadow by
3842
- * default, `false` ⇒ none). Single source so `_acquireSlot` and the
3843
- * double-buffer spare in `_settleSlot` stay in lock-step — a spare that missed
3844
- * this would lose the shadow on the settle swap. `box-shadow` never affects
3845
- * layout, so this is safe to (re)set on a live/pooled canvas without shifting
3846
- * any offset. */
3847
- private _applyPageShadow;
3848
- private _acquireSlot;
3849
- private _recycleSlot;
3850
- private _positionSlot;
3851
- /** Device-pixel ratio for a render (opts override → window → 1). */
3852
- private _dpr;
3853
- /**
3854
- * Render page `i` into `slot`. Routes strictly on the constructor-resolved
3855
- * `_mode` (design §11 — no probing, no silent mis-pathing): `main` ⇒ paint the
3856
- * slot's canvas directly via `renderPage`; `worker` ⇒ transfer an ImageBitmap
3857
- * from `renderPageToBitmap`.
3858
- *
3859
- * Slot-identity guard: a slot recycled to a DIFFERENT page while a previous
3860
- * render is in flight must not repaint the stale page. `slot.renderedPage`
3861
- * tracks the page this slot is committed to; we stamp it up-front and bail on
3862
- * resolution if it changed (the engine's own token guard is per-canvas; this is
3863
- * the viewer's per-slot page-identity check).
3864
- *
3865
- * Render epoch (main path): pixel staleness after a mid-flight `setScale` is
3866
- * already handled by the engine's per-canvas token (the newer renderPage on the
3867
- * same canvas wins) — `setScale` recycles + re-mounts, and the re-mount always
3868
- * re-dispatches `renderPage` (renderedPage reset to -1), so a fresh render is
3869
- * always issued. But the viewer-side side effects of a STALE resolution — the
3870
- * text-layer build (its run geometry is at the OLD scale) and the renderedPage
3871
- * bookkeeping — must NOT run, or a superseded render would rebuild the overlay
3872
- * with stale x/y/w/h (the pool reuses slot objects, so the identity check alone
3873
- * can pass for an old-epoch resolution). We gate them on the captured epoch.
3874
- */
3875
- private _renderSlot;
3876
- /**
3877
- * IX1/IX-nav — the click handler passed to the text-layer overlay. When the
3878
- * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
3879
- * is suppressed). Otherwise the built-in default is: an external link opens in
3880
- * a new tab through core `openExternalHyperlink` (URL sanitised against the
3881
- * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
3882
- * resolves its bookmark name to its destination page via
3883
- * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and scrolls there
3884
- * with {@link scrollToPage}. An anchor naming no known bookmark is a safe no-op
3885
- * rather than a scroll to a guessed page.
3886
- *
3887
- * IX1 — returns `undefined` when `enableHyperlinks` is `false`, the single gate
3888
- * that disables hyperlink interactivity: {@link buildDocxTextLayer} treats a
3889
- * missing handler as "render link runs like plain runs", so no hit region,
3890
- * cursor, tooltip, listener, or navigation is wired (a custom
3891
- * `onHyperlinkClick` is suppressed too).
3892
- */
3893
- private _hyperlinkHandler;
3894
- /** A width-measurer primed with a run's `font` — used ONLY to clamp a §17.3.2.10
3895
- * 縦中横 selection span to its drawn one-em cell (#836). Mirrors DocxViewer's
3896
- * `_measureForFont`. Returns a length-based fallback when canvas metrics are
3897
- * unavailable so the caller still gets a callable (the overlay then sees scale
3898
- * 1 and leaves the span un-clamped). */
3899
- private _measureForFont;
3900
- /** A canvas's intended CSS box in px (the % denominators the overlay builders
3901
- * expect). Reads the inline `style.width`/`height` set by the render path,
3902
- * falling back to the backing-store size when unset; tolerates the `px` suffix. */
3903
- private _canvasCssPx;
3904
- /** Route an async render failure to `onError`, or `console.error` when none is
3905
- * set (so failures are never fully silent), and never after teardown. */
3906
- private _reportRenderError;
3907
- /**
3908
- * Worker-mode slot render: dispatch `renderPageToBitmap`, transfer the result
3909
- * via a per-slot `bitmaprenderer` context, and manage the ImageBitmap lifecycle.
3910
- *
3911
- * Coalescing / drop-stale (design §11):
3912
- * - Skip if page `i` is already in flight (a scroll storm won't double-dispatch).
3913
- * - Skip if page `i` already left the mounted window before dispatch.
3914
- * - On resolution, if `slot` is no longer THIS page's live slot (it recycled to
3915
- * another page, or page `i` re-mounted onto a DIFFERENT slot while this render
3916
- * was in flight), close the orphan bitmap and skip the paint. In that
3917
- * re-mount case a live slot for `i` still awaits a render, so once we clear
3918
- * the in-flight guard we re-dispatch it — a page that recycled and re-mounted
3919
- * mid-flight must never stay blank.
3920
- * - RENDER EPOCH: the dispatch captures `this._renderEpoch`. `setScale` bumps
3921
- * the epoch, so a resolution whose captured epoch ≠ the live epoch is STALE
3922
- * even when the SAME slot object is still mounted for page `i` (the pool
3923
- * reuses slot objects, so the identity check alone can't catch a zoom that
3924
- * happened mid-flight). A moved epoch ⇒ close the orphan + re-dispatch the
3925
- * live slot at the new scale, never paint the old-scale bitmap.
3926
- */
3927
- private _renderSlotBitmap;
3928
- /**
3929
- * Set the absolute px-per-pt zoom scale, clamped inline to
3930
- * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
3931
- * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
3932
- * then re-anchor VERTICALLY so the page currently under the viewport top stays
3933
- * fixed. A no-op when the clamped scale is unchanged. Called BEFORE the doc is
3934
- * loaded / the base fit is established, the clamped factor is LATCHED (IX9 F1,
3935
- * family-unified with the single-canvas viewers) and applied by `relayout()`
3936
- * once the layout establishes — `onScaleChange` fires then.
3937
- *
3938
- * FLICKER-FREE (design §7): this does NOT re-render the visible pages inline.
3939
- * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
3940
- * overlays) and DEBOUNCES a full-resolution settle re-render for ZOOM_SETTLE_MS,
3941
- * so a wheel/pinch burst never blanks a page and coalesces into one crisp render.
3942
- *
3943
- * Re-anchor (written from scratch — XlsxViewer only re-anchors horizontally):
3944
- * capture `top = topIndex` and the intra-page fraction `intraFrac` from the
3945
- * CURRENT range BEFORE rescale; after recomputing heights at the new scale,
3946
- * `newScrollTop = offsets'[top] + intraFrac × heights'[top]`, clamped to
3947
- * `[0, totalHeight' − viewportHeight]`. Because a page's height scales linearly
3948
- * with `_scale`, the same fractional position maps exactly to the new geometry.
3949
- *
3950
- * CAVEAT — base fit below the floor: `relayout()` sets `_scale = base` WITHOUT
3951
- * clamping to `[zoomMin, zoomMax]`. If the base fit is below `zoomMin` (a wide
3952
- * page in a narrow container), the initial scale sits under the floor, but once
3953
- * the user zooms via `setScale` the clamp pins the minimum to `zoomMin`, so they
3954
- * can no longer return below the floor to the original base fit through this API.
3955
- */
3956
- setScale(scale: number): void;
3957
- /** IX9 {@link ZoomableViewer} — the current zoom factor, where `1` = 100% (a
3958
- * page at its natural pt→px width). This is the viewer's absolute `_scale`
3959
- * (`widthPt × PT_TO_PX × _scale` is the drawn width), so it reads `1` at true
3960
- * 100% and, after the initial fit-to-width, the base fit factor. Before the
3961
- * fit is established it reports a latched pre-load `setScale` (IX9 F1) if one
3962
- * is pending — matching what a single-canvas viewer would show — else `1`. */
3963
- getScale(): number;
3964
- /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
3965
- * ladder above the current factor (clamped to `zoomMax` by {@link setScale}). */
3966
- zoomIn(): void;
3967
- /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
3968
- zoomOut(): void;
3969
- /**
3970
- * IX9 {@link ZoomableViewer} — fit a page's WIDTH to the container (the classic
3971
- * continuous-scroll "fit width"). Sets the scale to the width-fit base for the
3972
- * current container, then re-anchors + re-renders via {@link setScale}. Defers
3973
- * (no-op) while the container is unlaid-out. Note the `zoomMin`/`zoomMax` clamp
3974
- * still applies, so a fit below `zoomMin` pins to `zoomMin`.
3975
- */
3976
- fitWidth(): void;
3977
- /**
3978
- * IX9 {@link ZoomableViewer} — fit a WHOLE page (width and height) inside the
3979
- * container so one page is visible without scrolling; takes the tighter of the
3980
- * width/height fit. Uses the FIRST page's size (the continuous viewer's fit
3981
- * reference, matching the base-fit convention). Defers while unlaid-out.
3982
- */
3983
- fitPage(): void;
3984
- /** Shared fit for {@link fitWidth}/{@link fitPage}: the width-fit factor is the
3985
- * established base (`_baseScale`); the page-fit additionally bounds by the
3986
- * container height against the first page's height. Applies via {@link setScale}
3987
- * so the flicker-free re-anchor / settle path and `onScaleChange` all run. */
3988
- private _fit;
3989
- /**
3990
- * CSS preview of the visible window at the current `_scale` (design §7
3991
- * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
3992
- * slots ENTERING the window mount fresh (rendered at the current scale directly,
3993
- * so they never need a preview); slots that STAY are repositioned and their
3994
- * canvas + text overlay are CSS-transformed to the new size (the device buffer
3995
- * is untouched — that is the whole point: no synchronous clear, no blank frame).
3996
- */
3997
- private _previewVisible;
3998
- /**
3999
- * CSS-preview a single already-mounted slot at the new geometry (design §7): the
4000
- * wrapper is repositioned + sized (via `_positionSlot`), the canvas bitmap is
4001
- * STRETCHED to the new CSS size (no `canvas.width` — the device buffer, and thus
4002
- * the drawn pixels, are left intact, just scaled by the browser), and the text
4003
- * overlay is scaled by `newScale / renderedScale` so it tracks the stretched
4004
- * page. `renderedScale <= 0` means the slot's first render hasn't resolved yet
4005
- * (nothing to stretch); the pending render captured the current scale, so it
4006
- * lands correct and no preview is needed.
4007
- */
4008
- private _previewSlot;
4009
- /** (Re)schedule the debounced settle re-render (design §7 mechanism 2). Resets
4010
- * the timer on every call so a burst of `setScale` dispatches ONE settle
4011
- * ZOOM_SETTLE_MS after the LAST call. Cleared in `destroy()`. */
4012
- private _scheduleSettle;
4013
- /** Full-resolution settle re-render of the visible window (design §7 mechanisms
4014
- * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
4015
- * swap (main) / same-canvas transfer (worker). Both modes rebuild the text
4016
- * overlay from the fresh render's run geometry (IX6 — worker mode collects the
4017
- * runs off-thread via `_renderSlotBitmap`) and clear the preview transform.
4018
- * Dispatched at the CURRENT epoch; the existing epoch gate discards it if a
4019
- * later `setScale` supersedes it mid-render. */
4020
- private _settleRender;
4021
- /**
4022
- * Settle-render one slot at the current scale (design §7 mechanism 3).
4023
- *
4024
- * WORKER: re-dispatch the bitmap render into the SAME canvas. The worker path
4025
- * sizes the device buffer and `transferFromImageBitmap`s it in ONE synchronous
4026
- * step (no await between `canvas.width = …` and the transfer), so the browser
4027
- * never composites an intermediate blank frame — no spare canvas is needed. The
4028
- * `renderedScale === _scale` gate in `_settleRender` plus the epoch gate inside
4029
- * `_renderSlotBitmap` keep this correct and idempotent.
4030
- *
4031
- * MAIN: `renderPage` (via renderDocumentToCanvas) synchronously sets
4032
- * `canvas.width = …` (which CLEARS the backing store to blank) BEFORE its first
4033
- * await and paints AFTER — so rendering into the on-screen canvas would flash it
4034
- * white. Render into a SPARE off-DOM canvas instead; only once it resolves at the
4035
- * current epoch do we swap it into the wrapper (replacing the old canvas, which is
4036
- * DISCARDED — the pooled unit is the slot, not the canvas). The old canvas keeps
4037
- * showing the stretched preview until the instant of the swap — blank-free.
4038
- */
4039
- private _settleSlot;
4040
- /**
4041
- * Scroll so page `index`'s top edge sits at the viewport top. Clamps `index` to
4042
- * `[0, pageCount-1]` (the pager convention) and the resulting scrollTop to
4043
- * `[0, totalHeight − viewportHeight]` so the last pages don't scroll past the
4044
- * end. A no-op when nothing is loaded or the document is empty.
4045
- *
4046
- * `opts.behavior` ('auto' | 'smooth', default 'auto') is honoured via
4047
- * `scrollHost.scrollTo({ top, behavior })` when the host supports it (a real
4048
- * browser); the stub-DOM has no `scrollTo`, so the fallback sets `scrollTop`
4049
- * directly (which is what the tests assert). We then call `_mountVisible` once.
4050
- *
4051
- * MOUNTING CAVEAT: synchronous mounting of the target page is guaranteed only on
4052
- * the DEFAULT/'auto' path — there `scrollTop` has already jumped to `top`, so the
4053
- * `_mountVisible` call reads the final scroll position and the target page's slots
4054
- * exist immediately. With `behavior: 'smooth'` the scroll animates ASYNCHRONOUSLY:
4055
- * `scrollTop` is still near the old position when `_mountVisible` runs, so the
4056
- * target page mounts lazily via the animation's subsequent `scroll` events, not
4057
- * from this call.
4058
- */
4059
- scrollToPage(index: number, opts?: {
4060
- behavior?: 'auto' | 'smooth';
4061
- }): void;
4062
- /** Search the complete document, including pages outside the virtualized
4063
- * mounted window. Matching is case-insensitive by default. */
4064
- findText(query: string, opts?: FindMatchesOptions): Promise<FindMatch<DocxMatchLocation>[]>;
4065
- /** Activate and reveal the next match, wrapping at the end. */
4066
- findNext(): Promise<FindMatch<DocxMatchLocation> | null>;
4067
- /** Activate and reveal the previous match, wrapping at the beginning. */
4068
- findPrev(): Promise<FindMatch<DocxMatchLocation> | null>;
4069
- /** Clear the current query and every mounted highlight. */
4070
- clearFind(): void;
4071
- private _activateMatch;
4072
- private _collectPageRuns;
4073
- private _redrawHighlights;
4074
- private _refreshFindRuns;
4075
- private _redrawSlotHighlights;
4076
- /**
4077
- * Re-fit the base scale on a container resize while PRESERVING the current zoom
4078
- * multiplier (design §11), then re-anchor + re-render. A `ResizeObserver` fires
4079
- * on any box change, but only a WIDTH change alters the fit-to-width base scale;
4080
- * a height-only change skips the re-fit yet STILL re-mounts the visible window
4081
- * (via `_mountVisible`), because a taller viewport reveals rows that were below
4082
- * the fold and would otherwise stay blank until the next scroll. Empty/unloaded
4083
- * ⇒ no-op; a still-zero width ⇒ defer.
4084
- *
4085
- * Zero-width recovery: a container that was 0-wide at construction never
4086
- * established a scale (`_scaleEstablished` is false), so the first non-zero
4087
- * resize establishes it here via `relayout()` — completing the T2 deferral.
4088
- *
4089
- * Re-fit math (zoom multiplier preserved):
4090
- * mult = _scale / _prevBase (the user's zoom over the old base)
4091
- * newScale = newBase × mult
4092
- * Routing through `setScale(newScale)` bumps `_renderEpoch` (resize IS an epoch
4093
- * event — T4 banner) and re-anchors + CSS-previews + debounces a settle re-render
4094
- * of every slot at the new geometry, exactly like a zoom (design §7 flicker-free
4095
- * path — a rapid ResizeObserver burst therefore also coalesces into one settle).
4096
- * `setScale`'s clamp/no-op guards apply: an unchanged newScale (identical width)
4097
- * is a no-op there — so we short-circuit BEFORE it when the fit-width is
4098
- * unchanged (mounting the revealed window without a needless re-render), and
4099
- * after it we call `_mountVisible` again to cover the case where the clamp made
4100
- * `setScale` no-op yet the viewport still grew.
4101
- */
4102
- private _onResize;
4103
- get topVisiblePage(): number;
4104
- /** Return the owning engine's latest content-free package-usage snapshot. */
4105
- getResourceMetrics(): Promise<OoxmlResourceMetrics>;
4106
- /**
4107
- * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
4108
- * engine) destroy the engine. An injected engine is left intact — the caller
4109
- * owns its lifecycle. Per-slot worker ImageBitmaps are closed on recycle.
4110
- */
4111
- destroy(): void;
4112
- }
4113
- //#endregion
4114
- //#region packages/docx/src/text-layer.d.ts
4115
- /**
4116
- * Build the transparent text-selection overlay for a rendered docx page: one
4117
- * absolutely-positioned, color-transparent `<span>` per {@link DocxTextRunInfo}
4118
- * (emitted by `renderPage`'s `onTextRun`), so the browser's native selection
4119
- * lands on the drawn glyphs. Extracted verbatim from `DocxViewer._buildTextLayer`
4120
- * so both the pager (DocxViewer) and the continuous-scroll viewer (DocxScrollViewer)
4121
- * share one implementation; also public API for integrators building their own
4122
- * overlay (design §10). IX6 — usable in BOTH render modes: worker mode collects
4123
- * the same `DocxTextRunInfo[]` off-thread and ships it back beside the bitmap, so
4124
- * the overlay is built from identical geometry regardless of thread.
4125
- *
4126
- * Every span is positioned as a PERCENTAGE of `cssWidth`/`cssHeight` (the page's
4127
- * intended CSS-px box), never literal px, and the container's own width/height are
4128
- * left untouched (the caller sizes it `width:100%;height:100%`). This lets the
4129
- * overlay track the canvas's ACTUAL rendered box even when a consumer scales the
4130
- * canvas down with external CSS (`width:100%!important; height:auto`): the
4131
- * `display:inline-block` wrapper shrinks with the canvas, the `100%` container
4132
- * follows, and every `%`-placed span scales with it, so nothing overflows the
4133
- * wrapper into an ancestor's scroll area.
4134
- *
4135
- * @param layer the overlay div (sized `width:100%;height:100%` by the caller).
4136
- * @param runs per-run geometry from `renderPage({ onTextRun })`.
4137
- * @param cssWidth the page's intended CSS width (px, number) — the %
4138
- * denominator for the x axis.
4139
- * @param cssHeight the page's intended CSS height (px, number) — the %
4140
- * denominator for the y axis.
4141
- * @param onHyperlinkClick IX1 — invoked when a run carrying a resolved
4142
- * {@link HyperlinkTarget} is clicked. A hyperlink run's
4143
- * span keeps its transparent glyphs (the visible link
4144
- * colour/underline is already drawn on the canvas) but
4145
- * gains `cursor:pointer`, a `title` tooltip (the URL or
4146
- * bookmark ref) and this click handler. A plain
4147
- * `<span>` — not an `<a href>` — is used deliberately so
4148
- * the browser's own navigation can never bypass the
4149
- * caller's URL sanitisation. When omitted, link runs are
4150
- * rendered exactly like plain runs (no click affordance).
4151
- * @param measureForFont optional width-measurer factory (primed with a run's
4152
- * `font`), used ONLY to clamp a §17.3.2.10 縦中横
4153
- * (eastAsianVert) span to its drawn one-em cell (#836):
4154
- * the span composes a `scaleX(run.w / naturalWidth)` so
4155
- * its selection extent matches the compressed glyphs
4156
- * instead of the run's natural ~2× width. When omitted,
4157
- * a 縦中横 span keeps the bare rotate (no regression for
4158
- * callers that do not thread a measurer).
4159
- */
4160
- declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], cssWidth: number, cssHeight: number, onHyperlinkClick?: (target: HyperlinkTarget) => void, measureForFont?: (font: string) => (s: string) => number): void;
4161
- //#endregion
4162
- //#region packages/docx/src/find-highlight-layer.d.ts
4163
- /** One page's highlight input: the run-slices a match covers, and whether that
4164
- * match is the active one (emphasis colour). */
4165
- interface DocxHighlightMatch {
4166
- slices: MatchRunSlice[];
4167
- active: boolean;
4168
- }
4169
- /** Format-specific compatibility alias for the shared colour contract. */
4170
- type DocxHighlightColors = FindHighlightColors;
4171
- /**
4172
- * Populate a highlight overlay layer with one box per matched run-slice.
4173
- *
4174
- * Every box is positioned as a PERCENTAGE of `cssWidth`/`cssHeight`, and the
4175
- * container's own size is left untouched (`width:100%;height:100%` from the
4176
- * caller), so the highlights track the canvas's ACTUAL rendered box even when a
4177
- * consumer scales the canvas down with external CSS — mirroring
4178
- * {@link buildDocxTextLayer}.
4179
- *
4180
- * @param layer the overlay div (cleared here; sized `100%` by the caller).
4181
- * @param runs the page's runs (same array the page was rendered/text-layered from).
4182
- * @param matches the page's matches (run-slices + active flag).
4183
- * @param cssWidth the page's intended CSS width (px, number) — the x-axis % denominator.
4184
- * @param cssHeight the page's intended CSS height (px, number) — the y-axis % denominator.
4185
- * @param measureForFont returns a width-measurer primed with a run's `font`
4186
- * (the viewer closes over a canvas 2d context). Kept as a
4187
- * factory so the font is set once per run, not per glyph.
4188
- * @param colors optional colour overrides.
4189
- */
4190
- declare function buildDocxHighlightLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], matches: DocxHighlightMatch[], cssWidth: number, cssHeight: number, measureForFont: (font: string) => (s: string) => number, colors?: DocxHighlightColors): void;
4191
- declare namespace docx_d_exports {
4192
- export { AnchorHostMetrics, AutoResizeOptions, BodyElement, BorderSpec, CellBorders, CellElement, ChartRun, ColSpec, ColumnsSpec, DocComment, DocNote, DocParagraph, DocRevision, DocRun, DocSettings, DocTable, DocTableCell, DocTableRow, DocxDocument, DocxDocumentModel, DocxHighlightColors, DocxHighlightMatch, DocxMatchLocation, DocxRunBorder, DocxScrollViewer, DocxScrollViewerOptions, DocxTextRun, DocxTextRunInfo, DocxViewer, DocxViewerOptions, EmbeddedFontRef, FieldRun, FindHighlightColors, FindMatch, FindMatchesOptions, FramePr, GradientStop$1 as GradientStop, HeaderFooter, HeadersFooters, HyperlinkTarget, ImageRun, LineEnd, LineNumbering, LineSpacing, LoadOptions$2 as LoadOptions, NoteRef, NumberingInfo, OoxmlDecodedImageLimitError, OoxmlDecodedImageLimitMetric, OoxmlError, OoxmlErrorCode, OoxmlErrorSource, OoxmlErrorStage, OoxmlFormat, OoxmlResourceLimit, OoxmlResourceLimitError, OoxmlResourceLimitErrorDetails, OoxmlResourceLimits, OoxmlResourceMetric, OoxmlResourceMetrics, OoxmlResourceMetricsCheckpoint, OoxmlResourceName, OoxmlResourcePolicySnapshot, OoxmlResourceUsageSnapshot, OoxmlResourceViolation, PTabRun, PageBorderEdge, PageBorders, PageNumType, ParaBorderEdge, ParagraphBorders, PathCmd$2 as PathCmd, RenderPageOptions, RenderPageToBitmapOptions, RubyAnnotation, RunRevision, SectionGeom, SectionProps, ShapeRun, ShapeText$1 as ShapeText, ShapeTextRun$1 as ShapeTextRun, TabStop$1 as TabStop, TableBorders, TblpPr, TextPath, WireRenderPageOptions, autoResize, buildDocxHighlightLayer, buildDocxTextLayer, isOoxmlDecodedImageLimitError, noteText, openExternalHyperlink };
4193
- }
4194
- //#endregion
4195
- //#region dist/.types-work/common-Bgczc_Eb.d.ts
4196
- //#region packages/core/src/types/common.d.ts
4197
- type PathCmd$1 = {
4198
- cmd: 'moveTo';
4199
- x: number;
4200
- y: number;
4201
- } | {
4202
- cmd: 'lineTo';
4203
- x: number;
4204
- y: number;
4205
- } | {
4206
- cmd: 'cubicBezTo';
4207
- x1: number;
4208
- y1: number;
4209
- x2: number;
4210
- y2: number;
4211
- x: number;
4212
- y: number;
4213
- } | {
4214
- cmd: 'arcTo';
4215
- wr: number;
4216
- hr: number;
4217
- stAng: number;
4218
- swAng: number;
4219
- } | {
4220
- cmd: 'close';
4221
- };
4222
- type Fill = SolidFill | NoFill | GradientFill | PatternFill | ImageFill;
4223
- interface SolidFill {
4224
- fillType: 'solid';
4225
- color: string;
4226
- }
4227
- interface NoFill {
4228
- fillType: 'none';
4229
- }
4230
- interface GradientStop {
4231
- position: number;
4232
- color: string;
4233
- }
4234
- interface GradientFill {
4235
- fillType: 'gradient';
4236
- stops: GradientStop[];
4237
- /** degrees: 0 = left→right, 90 = top→bottom */
4238
- angle: number;
4239
- /** 'linear' | 'radial' */
4240
- gradType: string;
4241
- }
4242
- /**
4243
- * Preset pattern fill — ECMA-376 §20.1.8.40 (CT_PatternFillProperties)
4244
- * with `preset` drawn from §20.1.10.59 (ST_PresetPatternVal).
4245
- */
4246
- interface PatternFill {
4247
- fillType: 'pattern';
4248
- /** Foreground hex colour — used for the "1" pixels of the preset bitmap. */
4249
- fg: string;
4250
- /** Background hex colour — used for the "0" pixels. */
4251
- bg: string;
4252
- /** Preset name, e.g. "pct25", "horz", "diagCross", "lgGrid". */
4253
- preset: string;
4254
- }
4255
- /**
4256
- * ECMA-376 §20.1.8.30 (CT_RelativeRect) — the destination rectangle a stretched
4257
- * blip is mapped into, as edge insets relative to the fill region. Values are
4258
- * fractions (ST_Percentage / 100000); **negative values let the image bleed
4259
- * past the box (overscan)**. Absent edges default to 0.
4260
- */
4261
- interface FillRect {
4262
- l?: number;
4263
- t?: number;
4264
- r?: number;
4265
- b?: number;
4266
- }
4267
- /**
4268
- * ECMA-376 §20.1.8.58 (CT_TileInfoProperties) — tiled blip-fill placement.
4269
- * The blip repeats at its native size (scaled by sx/sy) across the fill box.
4270
- * Mutually exclusive with {@link ImageFill.fillRect} (the `stretch` mode).
4271
- */
4272
- interface TileInfo {
4273
- /** Horizontal offset of the first tile, in EMU (`tx`). Default 0. */
4274
- tx: number;
4275
- /** Vertical offset of the first tile, in EMU (`ty`). Default 0. */
4276
- ty: number;
4277
- /** Horizontal tile scale as a fraction (`sx` / 100000). Default 1.0. */
4278
- sx: number;
4279
- /** Vertical tile scale as a fraction (`sy` / 100000). Default 1.0. */
4280
- sy: number;
4281
- /** Mirror mode: `'none' | 'x' | 'y' | 'xy'` (`flip`). Default `'none'`. */
4282
- flip: string;
4283
- /**
4284
- * Anchor corner the tile grid registers against:
4285
- * `tl|t|tr|l|ctr|r|bl|b|br` (`algn`). Default `'tl'`.
4286
- */
4287
- algn: string;
4288
- }
4289
- /**
4290
- * Image fill — ECMA-376 §20.1.8.14 (CT_BlipFillProperties). The embedded blip
4291
- * is carried as a zip path + MIME; the renderer fetches the bytes on demand via
4292
- * {@link RenderOptions.fetchImage} (no base64 inlined at parse time). Both
4293
- * fill-modes are modelled and mutually exclusive: `stretch` (§20.1.8.56) carries
4294
- * {@link ImageFill.fillRect}; `tile` (§20.1.8.58) carries {@link ImageFill.tile}.
4295
- */
4296
- interface ImageFill {
4297
- fillType: 'image';
4298
- /**
4299
- * Embedded zip path of the blip (e.g. "word/media/image1.png"), for the lazy
4300
- * byte-on-demand pipeline. The renderer fetches the bytes via a path-keyed
4301
- * loader ({@link RenderOptions.fetchImage}) instead of inlining base64.
4302
- */
4303
- imagePath: string;
4304
- /** MIME type of the blip at {@link ImageFill.imagePath} (e.g. `image/png`). */
4305
- mimeType: string;
4306
- /**
4307
- * `<a:stretch><a:fillRect>` insets. Absent → fills the whole box (or the
4308
- * fill is tiled — see {@link ImageFill.tile}).
4309
- */
4310
- fillRect?: FillRect;
4311
- /**
4312
- * `<a:tile>` descriptor. Present only when the blipFill is tiled; mutually
4313
- * exclusive with {@link ImageFill.fillRect}.
4314
- */
4315
- tile?: TileInfo;
4316
- /** `a:blip > a:alphaModFix@amt` as a fraction (0.0–1.0). Absent = opaque. */
4317
- alpha?: number;
4318
- /**
4319
- * ECMA-376 §20.1.8.23 `<a:duotone>` recolour, resolved to its two endpoint
4320
- * colours (through the slide theme). Absent ⇒ no duotone. When present the
4321
- * renderer maps the blip's luminance ramp between the two colours (core
4322
- * `applyDuotone`) — the same recolour a `<p:pic>` duotone applies, wired onto
4323
- * the picture-FILL path (§20.1.8.14) by issue #889.
4324
- */
4325
- duotone?: Duotone$1;
4326
- }
4327
- interface Shadow {
4328
- color: string;
4329
- alpha: number;
4330
- blur: number;
4331
- dist: number;
4332
- /** degrees clockwise from East */
4333
- dir: number;
4334
- }
4335
- /** ECMA-376 §20.1.8.17 (CT_GlowEffect) — coloured halo with blur radius. */
4336
- interface Glow {
4337
- color: string;
4338
- alpha: number;
4339
- /** Blur radius in EMU. */
4340
- radius: number;
4341
- }
4342
- /** ECMA-376 §20.1.8.31 (CT_SoftEdgesEffect) — feather radius in EMU. */
4343
- interface SoftEdge {
4344
- radius: number;
4345
- }
4346
- /** ECMA-376 §20.1.8.27 (CT_ReflectionEffect) — mirrored copy below the
4347
- * shape with a linear alpha gradient. Carries the spec attributes whose
4348
- * defaults the renderer needs to interpret correctly. */
4349
- interface Reflection {
4350
- blur: number;
4351
- dist: number;
4352
- /** Direction in degrees, clockwise from East. */
4353
- dir: number;
4354
- /** Start alpha (0–1). Default 1.0. */
4355
- stA: number;
4356
- /** Start position along the gradient (0–1). Default 0. */
4357
- stPos: number;
4358
- /** End alpha. Default 0. */
4359
- endA: number;
4360
- /** End position. Default 1.0. */
4361
- endPos: number;
4362
- /** Horizontal scale (1.0 = same width). */
4363
- sx: number;
4364
- /** Vertical scale (-1.0 = full mirror). */
4365
- sy: number;
4366
- }
4367
- interface ArrowEnd {
4368
- /** OOXML type: "none" | "triangle" | "stealth" | "diamond" | "oval" | "arrow" */
4369
- type: string;
4370
- /** Width multiplier: "sm" | "med" | "lg" */
4371
- w: string;
4372
- /** Length multiplier: "sm" | "med" | "lg" */
4373
- len: string;
4374
- }
4375
- interface Stroke {
4376
- color: string;
4377
- /** Width in EMU */
4378
- width: number;
4379
- /** Authored non-solid DrawingML line paint. Solid lines use `color`. */
4380
- fill?: Exclude<Fill, {
4381
- fillType: 'image';
4382
- } | {
4383
- fillType: 'none';
4384
- }>;
4385
- /** OOXML prstDash value: "dash", "dot", "dashDot", "lgDash", "lgDashDot", etc. */
4386
- dashStyle?: string;
4387
- /** Canvas line cap normalized from DrawingML/VML (`flat` → `butt`). */
4388
- lineCap?: CanvasLineCap;
4389
- /** Arrow head at the start of the line */
4390
- headEnd?: ArrowEnd;
4391
- /** Arrow head at the end of the line */
4392
- tailEnd?: ArrowEnd;
4393
- /**
4394
- * ECMA-376 §20.1.8.42 ST_CompoundLine. "sng" (default) | "dbl" |
4395
- * "thinThick" | "thickThin" | "tri". Absent means single line.
4396
- */
4397
- cmpd?: string;
4398
- }
4399
- interface TextBody$1 {
4400
- /** Vertical anchor: "t" | "ctr" | "b" */
4401
- verticalAnchor: string;
4402
- paragraphs: Paragraph$1[];
4403
- /** Default pt size from lstStyle (overrides renderer default when present) */
4404
- defaultFontSize: number | null;
4405
- /** Inherited bold from layout/master defRPr (null = not set, use false as final default) */
4406
- defaultBold: boolean | null;
4407
- /** Inherited italic from layout/master defRPr (null = not set, use false as final default) */
4408
- defaultItalic: boolean | null;
4409
- /** Text insets in EMU (defaults: lIns=rIns=91440, tIns=bIns=45720) */
4410
- lIns: number;
4411
- rIns: number;
4412
- tIns: number;
4413
- bIns: number;
4414
- /** "square" = wrap, "none" = no wrap */
4415
- wrap: string;
4416
- /** Text direction: "horz" | "vert" | "vert270" | "eaVert" etc. */
4417
- vert: string;
4418
- /** Auto-fit: "sp" = shape grows to fit text, "norm" = font shrinks, "none" = no fit */
4419
- autoFit: string;
4420
- /**
4421
- * `<a:normAutofit fontScale>` (ECMA-376 §21.1.2.1.3) — PowerPoint's stored,
4422
- * pre-computed font-shrink ratio for `autoFit === "norm"`, as a fraction
4423
- * (e.g. 0.625 for `fontScale="62500"`). Null/absent when PowerPoint stored no
4424
- * scale; the renderer then re-derives one. Applying the stored value matches
4425
- * PowerPoint exactly instead of guessing from our own text metrics.
4426
- */
4427
- fontScale?: number | null;
4428
- /** `<a:normAutofit lnSpcReduction>` — stored line-spacing reduction fraction
4429
- * (e.g. 0.20 for `lnSpcReduction="20000"`). Null/absent when not stored. */
4430
- lnSpcReduction?: number | null;
4431
- /**
4432
- * `<a:bodyPr numCol>` (ECMA-376 §20.1.10.34) — number of text columns inside
4433
- * the shape. Defaults to 1; values > 1 cause the renderer to flow paragraphs
4434
- * across N columns left-to-right, top-to-bottom.
4435
- */
4436
- numCol?: number;
4437
- /** `<a:bodyPr spcCol>` — gap between columns in EMU. Default 0. */
4438
- spcCol?: number;
4439
- }
4440
- type SpaceLine = {
4441
- type: 'pct';
4442
- val: number;
4443
- } | {
4444
- type: 'pts';
4445
- val: number;
4446
- };
4447
- /**
4448
- * A paragraph's bullet marker. For `char`, the marker size is EITHER `sizePct`
4449
- * (a percentage of the run size — ECMA-376 §21.1.2.4.9 `<a:buSzPct>`) OR `sizePts`
4450
- * (an absolute size in points — §21.1.2.4.10 `<a:buSzPts>`), never both: they are
4451
- * the one `EG_TextBulletSize` xsd:choice. `sizePts` is optional (absent when no
4452
- * `<a:buSzPts>` was declared); when present it takes precedence over `sizePct`.
4453
- */
4454
- type Bullet$1 = {
4455
- type: 'none';
4456
- } | {
4457
- type: 'inherit';
4458
- } | {
4459
- type: 'char';
4460
- char: string;
4461
- color: string | null;
4462
- sizePct: number | null;
4463
- sizePts?: number;
4464
- fontFamily: string | null;
4465
- } | {
4466
- type: 'autoNum';
4467
- numType: string;
4468
- startAt: number | null;
4469
- color: string | null;
4470
- };
4471
- interface TabStop {
4472
- /** Position in EMU from the LEADING text-inset edge of the text area —
4473
- * logical, not physical (ECMA-376 §21.1.2.1): the left edge (after lIns)
4474
- * in an LTR paragraph, the right edge (before rIns) in an RTL
4475
- * (`<a:pPr rtl="1">`) paragraph. */
4476
- pos: number;
4477
- /** Alignment: "l" | "r" | "ctr" | "dec" */
4478
- algn: string;
4479
- }
4480
- interface Paragraph$1 {
4481
- /** Alignment: "l" | "ctr" | "r" | "just" */
4482
- alignment: string;
4483
- /** Left margin in EMU */
4484
- marL: number;
4485
- /** Right margin in EMU */
4486
- marR: number;
4487
- /** First-line indent in EMU (negative = hanging indent) */
4488
- indent: number;
4489
- spaceBefore: number | null;
4490
- spaceAfter: number | null;
4491
- spaceLine: SpaceLine | null;
4492
- /** List nesting level (0–8) */
4493
- lvl: number;
4494
- bullet: Bullet$1;
4495
- defFontSize: number | null;
4496
- defColor: string | null;
4497
- defBold: boolean | null;
4498
- defItalic: boolean | null;
4499
- defFontFamily: string | null;
4500
- /** Tab stops from pPr > tabLst */
4501
- tabStops: TabStop[];
4502
- /**
4503
- * `<a:pPr rtl="1">` — right-to-left paragraph (ECMA-376 §21.1.2.2.7).
4504
- * When true and no explicit `algn`, the parser-side default flips from
4505
- * "l" to "r"; renderers can also use this flag to flow runs RTL.
4506
- */
4507
- rtl?: boolean;
4508
- runs: TextRun[];
4509
- }
4510
- type TextRun = TextRunData | LineBreak | EquationRun;
4511
- /**
4512
- * An OMML equation embedded in a paragraph (ECMA-376 §22.1). Parsed into the
4513
- * shared math AST and rendered by `@silurus/ooxml-core`'s math engine.
4514
- * PowerPoint stores these as `a14:m` inside `mc:AlternateContent`.
4515
- */
4516
- interface EquationRun {
4517
- type: 'math';
4518
- /** Parsed OMML node list. */
4519
- nodes: MathNode[];
4520
- /** True for block (`m:oMathPara`) math, false for inline (`m:oMath`). */
4521
- display: boolean;
4522
- /** Paragraph default run size in pt, if declared; absent → renderer inherits. */
4523
- fontSize?: number | null;
4524
- /** Equation colour (hex, no '#') from the math run's rPr; absent → inherit. */
4525
- color?: string | null;
4526
- }
4527
- interface TextRunData {
4528
- type: 'text';
4529
- text: string;
4530
- /** null = not set, inherit from paragraph/body defaults */
4531
- bold: boolean | null;
4532
- /** null = not set, inherit from paragraph/body defaults */
4533
- italic: boolean | null;
4534
- underline: boolean;
4535
- /**
4536
- * Specific underline style when not the default single line. Values come
4537
- * from ECMA-376 §21.1.2.3.16 (ST_TextUnderlineType): "dbl", "heavy",
4538
- * "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong",
4539
- * "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash",
4540
- * "dotDotDashHeavy", "wavy", "wavyHeavy", "wavyDbl". Absent means either
4541
- * no underline (when `underline` is false) or the default single line.
4542
- */
4543
- underlineStyle?: string;
4239
+ * Set by the Ctrl/⌘+wheel handler right before it calls `setScale` so the zoom
4240
+ * pivots on the cursor ("zoom toward the pointer") in BOTH axes; consumed and
4241
+ * cleared by `setScale`. `null` for every non-gesture source (the public
4242
+ * `setScale`, the +/- steppers, `fitWidth`/`fitPage`, the resize re-fit), which
4243
+ * keep the historical viewport-TOP re-anchor so their behaviour is unchanged. */
4244
+ private _pendingZoomAnchor;
4245
+ /** Observes the container so a width change re-fits the base scale. Disconnected
4246
+ * in `destroy()`. */
4247
+ private _resizeObserver;
4248
+ /** The base fit scale at the last established/re-fit layout. `_onResize` divides
4249
+ * `_scale` by this to recover the current zoom multiplier so a width change
4250
+ * re-fits the base while preserving the user's zoom (design §11). */
4251
+ private _prevBase;
4252
+ /** The fit width (px) the base scale was last established at. Lets `_onResize`
4253
+ * skip the re-fit when only the height changed (a ResizeObserver fires on ANY
4254
+ * box change, but only a WIDTH change alters the fit-to-width base scale). */
4255
+ private _lastFitWidth;
4256
+ /** Resolved page-canvas `box-shadow` (design: the recipe drop shadow by
4257
+ * default). Resolved ONCE with `??` — NOT `||` — so `pageShadow: false`
4258
+ * survives as the "no shadow" sentinel (a `||` would treat `false` as absent
4259
+ * and wrongly re-apply the default). Applied by `_applyPageShadow` at EVERY
4260
+ * canvas-creation site (`_acquireSlot` and the double-buffer spare in
4261
+ * `_settleSlot`) so a recycled/re-mounted slot and a settle-swapped spare all
4262
+ * carry it. */
4263
+ private readonly _pageShadow;
4264
+ private readonly _find;
4265
+ private _findActive;
4266
+ constructor(container: HTMLElement, opts?: DocxScrollViewerOptions);
4544
4267
  /**
4545
- * Underline-only colour from rPr > uFill (ECMA-376 §21.1.2.3.20). Absent
4546
- * means the underline follows the text colour (uFillTx default).
4268
+ * Load a DOCX from URL or ArrayBuffer and render the first window.
4269
+ * UNSUPPORTED when an engine was injected via `opts.document` (throws) — the
4270
+ * caller already owns the parsed engine.
4547
4271
  */
4548
- underlineColor?: string;
4549
- /** True when rPr strike is sngStrike or dblStrike. */
4550
- strikethrough: boolean;
4272
+ load(source: string | ArrayBuffer): Promise<void>;
4273
+ get pageCount(): number;
4274
+ /** CSS px width of page `i` at the current scale. */
4275
+ private _pageWidthPx;
4276
+ /** CSS px height of page `i` at the current scale. */
4277
+ private _pageHeightPx;
4278
+ /** The fit width (px), deferring when the container is unlaid-out. An EXPLICIT
4279
+ * `opts.width` is the page's CSS-width contract and is returned UNCHANGED (the
4280
+ * gutters still apply around placement, not to the width). The container-derived
4281
+ * default instead targets `containerWidth − padL − padR` so a page sits INSIDE
4282
+ * the horizontal gutters at 100%. A non-positive result (gutters wider than the
4283
+ * container) is treated as unlaid-out — the same deferral as a zero-width box. */
4284
+ private _fitWidthPx;
4285
+ /** Base scale: first page's width fit to the fit-width. Returns 0 when the
4286
+ * container has no width yet (deferral). */
4287
+ private _baseScale;
4551
4288
  /**
4552
- * True only when rPr strike = "dblStrike". Lets the renderer draw two parallel
4553
- * lines instead of one. ECMA-376 §21.1.2.3.10 (ST_TextStrikeType).
4289
+ * Recompute per-page heights + the spacer and re-mount the visible window.
4290
+ *
4291
+ * The viewer already calls this automatically after `load()`, an injected
4292
+ * engine, a container resize, and a zoom, so most integrations never need it.
4293
+ * It is public as a deliberate escape hatch: if the host mutates the layout in
4294
+ * a way the `ResizeObserver` cannot observe (e.g. a CSS change on an ancestor
4295
+ * that resizes the container without a box-size event, or a font that finishes
4296
+ * loading after first paint), call `relayout()` to force a re-fit. Idempotent —
4297
+ * safe to call repeatedly, and a no-op while the container has zero width (the
4298
+ * fit is deferred until width appears, design §11).
4554
4299
  */
4555
- strikeDouble?: boolean;
4556
- /** Font size in points */
4557
- fontSize: number | null;
4558
- color: string | null;
4559
- fontFamily: string | null;
4300
+ relayout(): void;
4301
+ private _recomputeHeights;
4302
+ private _gap;
4303
+ private _overscan;
4304
+ /** Desk padding fed to `computeVisibleRange`: `paddingTop`/`paddingBottom`,
4305
+ * each defaulting to `gap` (uniform rhythm). Resolved here (not stored) to
4306
+ * mirror `_gap()`/`_overscan()`, and consumed at EVERY `computeVisibleRange`
4307
+ * call site so the padded offsets are the single source of geometry. */
4308
+ private _pad;
4309
+ /** Horizontal desk gutters: `paddingLeft`/`paddingRight`, each defaulting to
4310
+ * `gap` (uniform rhythm — the horizontal gutters match the vertical padding).
4311
+ * Consumed by `_fitWidthPx` (to shrink the container-derived fit), by
4312
+ * `_positionSlot` (the flush-left floor), and by `_syncSpacer` (the spacer
4313
+ * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
4314
+ private _padH;
4315
+ /** Index of the page whose slot spans content-offset `y` (largest `i` with
4316
+ * `offsets[i] <= y`), for the pointer-anchored zoom re-anchor. Mirrors the
4317
+ * `topIndex` search `computeVisibleRange` runs for the scrollTop, but for an
4318
+ * ARBITRARY content-y (the pointer, not the viewport top). Clamped into
4319
+ * `[0, n-1]`; a `y` below the first page (inside the leading pad) yields 0. */
4320
+ private _pageIndexAtOffset;
4321
+ private _range;
4322
+ private _syncSpacer;
4323
+ /** Horizontal scroll extent: the widest page (docx pages can differ in width)
4324
+ * plus both gutters. A spacer NARROWER than the container never creates a
4325
+ * scrollbar (scrollWidth = max(clientWidth, content)), so it is always safe to
4326
+ * set — it only matters when a zoomed-in page grows past the viewport, where it
4327
+ * gives the gutters something to scroll to on either side. Max over per-page
4328
+ * widths so the extent covers the widest page in the document. Called from
4329
+ * `_syncSpacer` and after every scale change (zoom / resize re-fit) so the
4330
+ * extent tracks the current page px width. */
4331
+ private _syncSpacerWidth;
4332
+ private _onScroll;
4333
+ /** Mount/recycle slots for the current visible window. */
4334
+ private _mountVisible;
4335
+ /** Apply the resolved page-canvas shadow (design: recipe drop shadow by
4336
+ * default, `false` ⇒ none). Single source so `_acquireSlot` and the
4337
+ * double-buffer spare in `_settleSlot` stay in lock-step — a spare that missed
4338
+ * this would lose the shadow on the settle swap. `box-shadow` never affects
4339
+ * layout, so this is safe to (re)set on a live/pooled canvas without shifting
4340
+ * any offset. */
4341
+ private _applyPageShadow;
4342
+ private _acquireSlot;
4343
+ private _recycleSlot;
4344
+ private _positionSlot;
4345
+ /** Device-pixel ratio for a render (opts override → window → 1). */
4346
+ private _dpr;
4560
4347
  /**
4561
- * East Asian font family from rPr > a:ea (ECMA-376 §21.1.2.3.7),
4562
- * resolved through the theme. Renderer uses this for CJK glyphs when
4563
- * present; absent means CJK falls back to fontFamily.
4348
+ * Render page `i` into `slot`. Routes strictly on the constructor-resolved
4349
+ * `_mode` (design §11 no probing, no silent mis-pathing): `main` ⇒ paint the
4350
+ * slot's canvas directly via `renderPage`; `worker` transfer an ImageBitmap
4351
+ * from `renderPageToBitmap`.
4352
+ *
4353
+ * Slot-identity guard: a slot recycled to a DIFFERENT page while a previous
4354
+ * render is in flight must not repaint the stale page. `slot.renderedPage`
4355
+ * tracks the page this slot is committed to; we stamp it up-front and bail on
4356
+ * resolution if it changed (the engine's own token guard is per-canvas; this is
4357
+ * the viewer's per-slot page-identity check).
4358
+ *
4359
+ * Render epoch (main path): pixel staleness after a mid-flight `setScale` is
4360
+ * already handled by the engine's per-canvas token (the newer renderPage on the
4361
+ * same canvas wins) — `setScale` recycles + re-mounts, and the re-mount always
4362
+ * re-dispatches `renderPage` (renderedPage reset to -1), so a fresh render is
4363
+ * always issued. But the viewer-side side effects of a STALE resolution — the
4364
+ * text-layer build (its run geometry is at the OLD scale) and the renderedPage
4365
+ * bookkeeping — must NOT run, or a superseded render would rebuild the overlay
4366
+ * with stale x/y/w/h (the pool reuses slot objects, so the identity check alone
4367
+ * can pass for an old-epoch resolution). We gate them on the captured epoch.
4564
4368
  */
4565
- fontFamilyEa?: string;
4369
+ private _renderSlot;
4566
4370
  /**
4567
- * Symbol font family from rPr > a:sym (ECMA-376 §21.1.2.3.10), resolved
4568
- * through the theme. PowerPoint stores symbol-font glyphs as Private-Use
4569
- * codepoints U+F020–U+F0FF; the renderer uses this font to resolve them.
4570
- * Absent means no symbol font was declared.
4371
+ * IX1/IX-nav the click handler passed to the text-layer overlay. When the
4372
+ * caller supplied `onHyperlinkClick`, it fully owns the behaviour (the default
4373
+ * is suppressed). Otherwise the built-in default is: an external link opens in
4374
+ * a new tab through core `openExternalHyperlink` (URL sanitised against the
4375
+ * safe scheme allowlist, `noopener,noreferrer`); an internal `<w:anchor>` link
4376
+ * resolves its bookmark name to its destination page via
4377
+ * {@link DocxDocument.getBookmarkPage} (ECMA-376 §17.16.23) and scrolls there
4378
+ * with {@link scrollToPage}. An anchor naming no known bookmark is a safe no-op
4379
+ * rather than a scroll to a guessed page.
4380
+ *
4381
+ * IX1 — returns `undefined` when `enableHyperlinks` is `false`, the single gate
4382
+ * that disables hyperlink interactivity: {@link buildDocxTextLayer} treats a
4383
+ * missing handler as "render link runs like plain runs", so no hit region,
4384
+ * cursor, tooltip, listener, or navigation is wired (a custom
4385
+ * `onHyperlinkClick` is suppressed too).
4571
4386
  */
4572
- fontFamilySym?: string;
4573
- /** Baseline shift in thousandths of a point. Positive = superscript, negative = subscript. */
4574
- baseline?: number;
4387
+ private _hyperlinkHandler;
4388
+ /** A width-measurer primed with a run's `font` used ONLY to clamp a §17.3.2.10
4389
+ * 縦中横 selection span to its drawn one-em cell (#836). Mirrors DocxViewer's
4390
+ * `_measureForFont`. Returns a length-based fallback when canvas metrics are
4391
+ * unavailable so the caller still gets a callable (the overlay then sees scale
4392
+ * 1 and leaves the span un-clamped). */
4393
+ private _measureForFont;
4394
+ /** A canvas's intended CSS box in px (the % denominators the overlay builders
4395
+ * expect). Reads the inline `style.width`/`height` set by the render path,
4396
+ * falling back to the backing-store size when unset; tolerates the `px` suffix. */
4397
+ private _canvasCssPx;
4398
+ /** Route an async render failure to `onError`, or `console.error` when none is
4399
+ * set (so failures are never fully silent), and never after teardown. */
4400
+ private _reportRenderError;
4575
4401
  /**
4576
- * Capitalisation transform ECMA-376 §21.1.2.3.13 (ST_TextCapsType).
4577
- * 'all' renders text in upper case; 'small' uses small caps (rendered as
4578
- * upper case at ~80% size when no smcp font feature is available).
4579
- * 'none' or omitted leaves the text unchanged.
4402
+ * Worker-mode slot render: dispatch `renderPageToBitmap`, transfer the result
4403
+ * via a per-slot `bitmaprenderer` context, and manage the ImageBitmap lifecycle.
4404
+ *
4405
+ * Coalescing / drop-stale (design §11):
4406
+ * - Skip if page `i` is already in flight (a scroll storm won't double-dispatch).
4407
+ * - Skip if page `i` already left the mounted window before dispatch.
4408
+ * - On resolution, if `slot` is no longer THIS page's live slot (it recycled to
4409
+ * another page, or page `i` re-mounted onto a DIFFERENT slot while this render
4410
+ * was in flight), close the orphan bitmap and skip the paint. In that
4411
+ * re-mount case a live slot for `i` still awaits a render, so once we clear
4412
+ * the in-flight guard we re-dispatch it — a page that recycled and re-mounted
4413
+ * mid-flight must never stay blank.
4414
+ * - RENDER EPOCH: the dispatch captures `this._renderEpoch`. `setScale` bumps
4415
+ * the epoch, so a resolution whose captured epoch ≠ the live epoch is STALE
4416
+ * even when the SAME slot object is still mounted for page `i` (the pool
4417
+ * reuses slot objects, so the identity check alone can't catch a zoom that
4418
+ * happened mid-flight). A moved epoch ⇒ close the orphan + re-dispatch the
4419
+ * live slot at the new scale, never paint the old-scale bitmap.
4580
4420
  */
4581
- caps?: 'none' | 'small' | 'all';
4421
+ private _renderSlotBitmap;
4582
4422
  /**
4583
- * Inter-character spacing in 100ths of a point ECMA-376 §21.1.2.3.5
4584
- * (rPr @spc). Positive values add space, negative values tighten.
4423
+ * Set the absolute px-per-pt zoom scale, clamped inline to
4424
+ * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
4425
+ * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
4426
+ * then re-anchor VERTICALLY so the page currently under the viewport top stays
4427
+ * fixed. A no-op when the clamped scale is unchanged. Called BEFORE the doc is
4428
+ * loaded / the base fit is established, the clamped factor is LATCHED (IX9 F1,
4429
+ * family-unified with the single-canvas viewers) and applied by `relayout()`
4430
+ * once the layout establishes — `onScaleChange` fires then.
4431
+ *
4432
+ * FLICKER-FREE (design §7): this does NOT re-render the visible pages inline.
4433
+ * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
4434
+ * overlays) and DEBOUNCES a full-resolution settle re-render for ZOOM_SETTLE_MS,
4435
+ * so a wheel/pinch burst never blanks a page and coalesces into one crisp render.
4436
+ *
4437
+ * Re-anchor (written from scratch — XlsxViewer only re-anchors horizontally):
4438
+ * capture `top = topIndex` and the intra-page fraction `intraFrac` from the
4439
+ * CURRENT range BEFORE rescale; after recomputing heights at the new scale,
4440
+ * `newScrollTop = offsets'[top] + intraFrac × heights'[top]`, clamped to
4441
+ * `[0, totalHeight' − viewportHeight]`. Because a page's height scales linearly
4442
+ * with `_scale`, the same fractional position maps exactly to the new geometry.
4443
+ *
4444
+ * CAVEAT — base fit below the floor: `relayout()` sets `_scale = base` WITHOUT
4445
+ * clamping to `[zoomMin, zoomMax]`. If the base fit is below `zoomMin` (a wide
4446
+ * page in a narrow container), the initial scale sits under the floor, but once
4447
+ * the user zooms via `setScale` the clamp pins the minimum to `zoomMin`, so they
4448
+ * can no longer return below the floor to the original base fit through this API.
4585
4449
  */
4586
- letterSpacing?: number;
4587
- /** Set for OOXML field runs (e.g. "slidenum"). When set, renderer replaces text with field value. */
4588
- fieldType?: string;
4450
+ setScale(scale: number): void;
4451
+ /** IX9 {@link ZoomableViewer} the current zoom factor, where `1` = 100% (a
4452
+ * page at its natural pt→px width). This is the viewer's absolute `_scale`
4453
+ * (`widthPt × PT_TO_PX × _scale` is the drawn width), so it reads `1` at true
4454
+ * 100% and, after the initial fit-to-width, the base fit factor. Before the
4455
+ * fit is established it reports a latched pre-load `setScale` (IX9 F1) if one
4456
+ * is pending — matching what a single-canvas viewer would show — else `1`. */
4457
+ getScale(): number;
4458
+ /** IX9 {@link ZoomableViewer} — step up to the next rung of the shared zoom
4459
+ * ladder above the current factor (clamped to `zoomMax` by {@link setScale}). */
4460
+ zoomIn(): void;
4461
+ /** IX9 {@link ZoomableViewer} — step down to the next lower ladder rung. */
4462
+ zoomOut(): void;
4589
4463
  /**
4590
- * Hyperlink target resolved from rPr > a:hlinkClick @r:id via the slide's _rels.
4591
- * For an external link this is the URL; for an internal slide jump it is the
4592
- * resolved internal part name (e.g. "../slides/slide3.xml"). Undefined for runs
4593
- * without a hyperlink. ECMA-376 §21.1.2.3.5 (CT_Hyperlink).
4464
+ * IX9 {@link ZoomableViewer} fit a page's WIDTH to the container (the classic
4465
+ * continuous-scroll "fit width"). Sets the scale to the width-fit base for the
4466
+ * current container, then re-anchors + re-renders via {@link setScale}. Defers
4467
+ * (no-op) while the container is unlaid-out. Note the `zoomMin`/`zoomMax` clamp
4468
+ * still applies, so a fit below `zoomMin` pins to `zoomMin`.
4594
4469
  */
4595
- hyperlink?: string;
4470
+ fitWidth(): void;
4596
4471
  /**
4597
- * Raw `<a:hlinkClick @action>` string (e.g. "ppaction://hlinksldjump") when
4598
- * present its presence marks {@link hyperlink} as an INTERNAL PowerPoint
4599
- * action (slide jump / first / last …) rather than an external URL. Undefined
4600
- * when the hlinkClick has no @action. ECMA-376 §21.1.2.3.5. (IX1)
4472
+ * IX9 {@link ZoomableViewer} — fit a WHOLE page (width and height) inside the
4473
+ * container so one page is visible without scrolling; takes the tighter of the
4474
+ * width/height fit. Uses the FIRST page's size (the continuous viewer's fit
4475
+ * reference, matching the base-fit convention). Defers while unlaid-out.
4601
4476
  */
4602
- hyperlinkAction?: string;
4477
+ fitPage(): void;
4478
+ /** Shared fit for {@link fitWidth}/{@link fitPage}: the width-fit factor is the
4479
+ * established base (`_baseScale`); the page-fit additionally bounds by the
4480
+ * container height against the first page's height. Applies via {@link setScale}
4481
+ * so the flicker-free re-anchor / settle path and `onScaleChange` all run. */
4482
+ private _fit;
4603
4483
  /**
4604
- * Run-level drop shadow on glyphs (`<a:rPr><a:effectLst><a:outerShdw>`),
4605
- * ECMA-376 §20.1.8.45. Independent of the shape-level shadow on `spPr`.
4606
- * Absent means no run-level shadow.
4484
+ * CSS preview of the visible window at the current `_scale` (design §7
4485
+ * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
4486
+ * slots ENTERING the window mount fresh (rendered at the current scale directly,
4487
+ * so they never need a preview); slots that STAY are repositioned and their
4488
+ * canvas + text overlay are CSS-transformed to the new size (the device buffer
4489
+ * is untouched — that is the whole point: no synchronous clear, no blank frame).
4607
4490
  */
4608
- shadow?: Shadow;
4491
+ private _previewVisible;
4609
4492
  /**
4610
- * Run-level glyph outline (`<a:rPr><a:ln w="..">`), ECMA-376 §20.1.2.2.24
4611
- * (CT_TextOutlineEffect). Renderer strokes each glyph with the given
4612
- * width / colour in addition to the normal fill. Absent means glyphs are
4613
- * fill-only.
4493
+ * CSS-preview a single already-mounted slot at the new geometry (design §7): the
4494
+ * wrapper is repositioned + sized (via `_positionSlot`), the canvas bitmap is
4495
+ * STRETCHED to the new CSS size (no `canvas.width` the device buffer, and thus
4496
+ * the drawn pixels, are left intact, just scaled by the browser), and the text
4497
+ * overlay is scaled by `newScale / renderedScale` so it tracks the stretched
4498
+ * page. `renderedScale <= 0` means the slot's first render hasn't resolved yet
4499
+ * (nothing to stretch); the pending render captured the current scale, so it
4500
+ * lands correct and no preview is needed.
4614
4501
  */
4615
- outline?: TextOutline;
4502
+ private _previewSlot;
4503
+ /** (Re)schedule the debounced settle re-render (design §7 mechanism 2). Resets
4504
+ * the timer on every call so a burst of `setScale` dispatches ONE settle
4505
+ * ZOOM_SETTLE_MS after the LAST call. Cleared in `destroy()`. */
4506
+ private _scheduleSettle;
4507
+ /** Full-resolution settle re-render of the visible window (design §7 mechanisms
4508
+ * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
4509
+ * swap (main) / same-canvas transfer (worker). Both modes rebuild the text
4510
+ * overlay from the fresh render's run geometry (IX6 — worker mode collects the
4511
+ * runs off-thread via `_renderSlotBitmap`) and clear the preview transform.
4512
+ * Dispatched at the CURRENT epoch; the existing epoch gate discards it if a
4513
+ * later `setScale` supersedes it mid-render. */
4514
+ private _settleRender;
4616
4515
  /**
4617
- * Run-level text highlight / marker colour (`<a:rPr><a:highlight>`),
4618
- * ECMA-376 §21.1.2.3.4. In DrawingML this is a full CT_Color (any
4619
- * srgbClr / schemeClr / sysClr / prstClr + transforms), unlike
4620
- * WordprocessingML's fixed 16-name highlight enum so the parser already
4621
- * resolves it through the theme/clrMap. The value is a hex string without
4622
- * `#` (6-char opaque, or 8-char RRGGBBAA when an alpha transform applies);
4623
- * the renderer paints a background rectangle behind the run's glyphs.
4624
- * Absent means no highlight.
4516
+ * Settle-render one slot at the current scale (design §7 mechanism 3).
4517
+ *
4518
+ * WORKER: re-dispatch the bitmap render into the SAME canvas. The worker path
4519
+ * sizes the device buffer and `transferFromImageBitmap`s it in ONE synchronous
4520
+ * step (no await between `canvas.width = …` and the transfer), so the browser
4521
+ * never composites an intermediate blank frame no spare canvas is needed. The
4522
+ * `renderedScale === _scale` gate in `_settleRender` plus the epoch gate inside
4523
+ * `_renderSlotBitmap` keep this correct and idempotent.
4524
+ *
4525
+ * MAIN: `renderPage` (via renderDocumentToCanvas) synchronously sets
4526
+ * `canvas.width = …` (which CLEARS the backing store to blank) BEFORE its first
4527
+ * await and paints AFTER — so rendering into the on-screen canvas would flash it
4528
+ * white. Render into a SPARE off-DOM canvas instead; only once it resolves at the
4529
+ * current epoch do we swap it into the wrapper (replacing the old canvas, which is
4530
+ * DISCARDED — the pooled unit is the slot, not the canvas). The old canvas keeps
4531
+ * showing the stretched preview until the instant of the swap — blank-free.
4625
4532
  */
4626
- highlight?: string;
4627
- }
4628
- /** Run-level glyph outline. Width is in OOXML EMU (12700 EMU = 1 pt). */
4629
- interface TextOutline {
4630
- width: number;
4631
- /** Hex without '#'. Absent = inherit from text fill colour. */
4632
- color?: string;
4633
- }
4634
- interface LineBreak {
4635
- type: 'break';
4636
- }
4637
- interface RenderOptions {
4638
- width?: number;
4639
- defaultTextColor?: string | null;
4640
- dpr?: number;
4641
- majorFont?: string | null;
4642
- minorFont?: string | null;
4643
- /** Theme hyperlink colour (hex 6 chars). Used to colour hyperlink runs without an explicit colour. */
4644
- hlinkColor?: string | null;
4533
+ private _settleSlot;
4645
4534
  /**
4646
- * Lazily resolve an archive-internal asset (by zip path) to a Blob. The
4647
- * renderer uses this to fetch posters and other large embedded assets on
4648
- * demand, keeping the parse output free of inlined base64.
4535
+ * Scroll so page `index`'s top edge sits at the viewport top. Clamps `index` to
4536
+ * `[0, pageCount-1]` (the pager convention) and the resulting scrollTop to
4537
+ * `[0, totalHeight − viewportHeight]` so the last pages don't scroll past the
4538
+ * end. A no-op when nothing is loaded or the document is empty.
4539
+ *
4540
+ * `opts.behavior` ('auto' | 'smooth', default 'auto') is honoured via
4541
+ * `scrollHost.scrollTo({ top, behavior })` when the host supports it (a real
4542
+ * browser); the stub-DOM has no `scrollTo`, so the fallback sets `scrollTop`
4543
+ * directly (which is what the tests assert). We then call `_mountVisible` once.
4544
+ *
4545
+ * MOUNTING CAVEAT: synchronous mounting of the target page is guaranteed only on
4546
+ * the DEFAULT/'auto' path — there `scrollTop` has already jumped to `top`, so the
4547
+ * `_mountVisible` call reads the final scroll position and the target page's slots
4548
+ * exist immediately. With `behavior: 'smooth'` the scroll animates ASYNCHRONOUSLY:
4549
+ * `scrollTop` is still near the old position when `_mountVisible` runs, so the
4550
+ * target page mounts lazily via the animation's subsequent `scroll` events, not
4551
+ * from this call.
4649
4552
  */
4650
- fetchMedia?: (path: string) => Promise<Blob>;
4553
+ scrollToPage(index: number, opts?: {
4554
+ behavior?: 'auto' | 'smooth';
4555
+ }): void;
4556
+ /** Search the complete document, including pages outside the virtualized
4557
+ * mounted window. Matching is case-insensitive by default. */
4558
+ findText(query: string, opts?: FindMatchesOptions): Promise<FindMatch<DocxMatchLocation>[]>;
4559
+ /** Activate and reveal the next match, wrapping at the end. */
4560
+ findNext(): Promise<FindMatch<DocxMatchLocation> | null>;
4561
+ /** Activate and reveal the previous match, wrapping at the beginning. */
4562
+ findPrev(): Promise<FindMatch<DocxMatchLocation> | null>;
4563
+ /** Clear the current query and every mounted highlight. */
4564
+ clearFind(): void;
4565
+ private _activateMatch;
4566
+ private _collectPageRuns;
4567
+ private _redrawHighlights;
4568
+ private _refreshFindRuns;
4569
+ private _redrawSlotHighlights;
4651
4570
  /**
4652
- * Lazily resolve an embedded image (by zip path + MIME) to a Blob. Twin of
4653
- * {@link RenderOptions.fetchMedia} for pictures and blip fills: the renderer
4654
- * fetches raster/SVG bytes on demand and decodes them (`createImageBitmap` /
4655
- * path-keyed `<img>`), so the parse output carries only paths, never base64.
4571
+ * Re-fit the base scale on a container resize while PRESERVING the current zoom
4572
+ * multiplier (design §11), then re-anchor + re-render. A `ResizeObserver` fires
4573
+ * on any box change, but only a WIDTH change alters the fit-to-width base scale;
4574
+ * a height-only change skips the re-fit yet STILL re-mounts the visible window
4575
+ * (via `_mountVisible`), because a taller viewport reveals rows that were below
4576
+ * the fold and would otherwise stay blank until the next scroll. Empty/unloaded
4577
+ * ⇒ no-op; a still-zero width ⇒ defer.
4578
+ *
4579
+ * Zero-width recovery: a container that was 0-wide at construction never
4580
+ * established a scale (`_scaleEstablished` is false), so the first non-zero
4581
+ * resize establishes it here via `relayout()` — completing the T2 deferral.
4582
+ *
4583
+ * Re-fit math (zoom multiplier preserved):
4584
+ * mult = _scale / _prevBase (the user's zoom over the old base)
4585
+ * newScale = newBase × mult
4586
+ * Routing through `setScale(newScale)` bumps `_renderEpoch` (resize IS an epoch
4587
+ * event — T4 banner) and re-anchors + CSS-previews + debounces a settle re-render
4588
+ * of every slot at the new geometry, exactly like a zoom (design §7 flicker-free
4589
+ * path — a rapid ResizeObserver burst therefore also coalesces into one settle).
4590
+ * `setScale`'s clamp/no-op guards apply: an unchanged newScale (identical width)
4591
+ * is a no-op there — so we short-circuit BEFORE it when the fit-width is
4592
+ * unchanged (mounting the revealed window without a needless re-render), and
4593
+ * after it we call `_mountVisible` again to cover the case where the clamp made
4594
+ * `setScale` no-op yet the viewport still grew.
4656
4595
  */
4657
- fetchImage?: (path: string, mimeType: string) => Promise<Blob>;
4596
+ private _onResize;
4597
+ get topVisiblePage(): number;
4598
+ /** Return the owning engine's latest content-free package-usage snapshot. */
4599
+ getResourceMetrics(): Promise<OoxmlResourceMetrics>;
4658
4600
  /**
4659
- * When true, renderMedia draws only the poster frame play/pause badges
4660
- * and progress bars are left to the caller. Set by the pptx presentSlide
4661
- * API so its interactive handle can own all control chrome without
4662
- * the static renderer drawing a duplicate play badge.
4601
+ * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
4602
+ * engine) destroy the engine. An injected engine is left intact the caller
4603
+ * owns its lifecycle. Per-slot worker ImageBitmaps are closed on recycle.
4663
4604
  */
4664
- skipMediaControls?: boolean;
4605
+ destroy(): void;
4606
+ }
4607
+ //#endregion
4608
+ //#region packages/docx/src/text-layer.d.ts
4609
+ /**
4610
+ * Build the transparent text-selection overlay for a rendered docx page: one
4611
+ * absolutely-positioned, color-transparent `<span>` per {@link DocxTextRunInfo}
4612
+ * (emitted by `renderPage`'s `onTextRun`), so the browser's native selection
4613
+ * lands on the drawn glyphs. Extracted verbatim from `DocxViewer._buildTextLayer`
4614
+ * so both the pager (DocxViewer) and the continuous-scroll viewer (DocxScrollViewer)
4615
+ * share one implementation; also public API for integrators building their own
4616
+ * overlay (design §10). IX6 — usable in BOTH render modes: worker mode collects
4617
+ * the same `DocxTextRunInfo[]` off-thread and ships it back beside the bitmap, so
4618
+ * the overlay is built from identical geometry regardless of thread.
4619
+ *
4620
+ * Every span is positioned as a PERCENTAGE of `cssWidth`/`cssHeight` (the page's
4621
+ * intended CSS-px box), never literal px, and the container's own width/height are
4622
+ * left untouched (the caller sizes it `width:100%;height:100%`). This lets the
4623
+ * overlay track the canvas's ACTUAL rendered box even when a consumer scales the
4624
+ * canvas down with external CSS (`width:100%!important; height:auto`): the
4625
+ * `display:inline-block` wrapper shrinks with the canvas, the `100%` container
4626
+ * follows, and every `%`-placed span scales with it, so nothing overflows the
4627
+ * wrapper into an ancestor's scroll area.
4628
+ *
4629
+ * @param layer the overlay div (sized `width:100%;height:100%` by the caller).
4630
+ * @param runs per-run geometry from `renderPage({ onTextRun })`.
4631
+ * @param cssWidth the page's intended CSS width (px, number) — the %
4632
+ * denominator for the x axis.
4633
+ * @param cssHeight the page's intended CSS height (px, number) — the %
4634
+ * denominator for the y axis.
4635
+ * @param onHyperlinkClick IX1 — invoked when a run carrying a resolved
4636
+ * {@link HyperlinkTarget} is clicked. A hyperlink run's
4637
+ * span keeps its transparent glyphs (the visible link
4638
+ * colour/underline is already drawn on the canvas) but
4639
+ * gains `cursor:pointer`, a `title` tooltip (the URL or
4640
+ * bookmark ref) and this click handler. A plain
4641
+ * `<span>` — not an `<a href>` — is used deliberately so
4642
+ * the browser's own navigation can never bypass the
4643
+ * caller's URL sanitisation. When omitted, link runs are
4644
+ * rendered exactly like plain runs (no click affordance).
4645
+ * @param measureForFont optional width-measurer factory (primed with a run's
4646
+ * `font`), used ONLY to clamp a §17.3.2.10 縦中横
4647
+ * (eastAsianVert) span to its drawn one-em cell (#836):
4648
+ * the span composes a `scaleX(run.w / naturalWidth)` so
4649
+ * its selection extent matches the compressed glyphs
4650
+ * instead of the run's natural ~2× width. When omitted,
4651
+ * a 縦中横 span keeps the bare rotate (no regression for
4652
+ * callers that do not thread a measurer).
4653
+ */
4654
+ declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], cssWidth: number, cssHeight: number, onHyperlinkClick?: (target: HyperlinkTarget) => void, measureForFont?: (font: string) => (s: string) => number): void;
4655
+ //#endregion
4656
+ //#region packages/docx/src/find-highlight-layer.d.ts
4657
+ /** One page's highlight input: the run-slices a match covers, and whether that
4658
+ * match is the active one (emphasis colour). */
4659
+ interface DocxHighlightMatch {
4660
+ slices: MatchRunSlice[];
4661
+ active: boolean;
4662
+ }
4663
+ /** Format-specific compatibility alias for the shared colour contract. */
4664
+ type DocxHighlightColors = FindHighlightColors;
4665
+ /**
4666
+ * Populate a highlight overlay layer with one box per matched run-slice.
4667
+ *
4668
+ * Every box is positioned as a PERCENTAGE of `cssWidth`/`cssHeight`, and the
4669
+ * container's own size is left untouched (`width:100%;height:100%` from the
4670
+ * caller), so the highlights track the canvas's ACTUAL rendered box even when a
4671
+ * consumer scales the canvas down with external CSS — mirroring
4672
+ * {@link buildDocxTextLayer}.
4673
+ *
4674
+ * @param layer the overlay div (cleared here; sized `100%` by the caller).
4675
+ * @param runs the page's runs (same array the page was rendered/text-layered from).
4676
+ * @param matches the page's matches (run-slices + active flag).
4677
+ * @param cssWidth the page's intended CSS width (px, number) — the x-axis % denominator.
4678
+ * @param cssHeight the page's intended CSS height (px, number) — the y-axis % denominator.
4679
+ * @param measureForFont returns a width-measurer primed with a run's `font`
4680
+ * (the viewer closes over a canvas 2d context). Kept as a
4681
+ * factory so the font is set once per run, not per glyph.
4682
+ * @param colors optional colour overrides.
4683
+ */
4684
+ declare function buildDocxHighlightLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], matches: DocxHighlightMatch[], cssWidth: number, cssHeight: number, measureForFont: (font: string) => (s: string) => number, colors?: DocxHighlightColors): void;
4685
+ declare namespace docx_d_exports {
4686
+ export { AnchorHostMetrics, AutoResizeOptions, BodyElement, BorderSpec, CellBorders, CellElement, ChartRun, ColSpec, ColumnsSpec, DocComment, DocNote, DocParagraph, DocRevision, DocRun, DocSettings, DocTable, DocTableCell, DocTableRow, DocxDocument, DocxDocumentModel, DocxHighlightColors, DocxHighlightMatch, DocxMatchLocation, DocxRunBorder, DocxScrollViewer, DocxScrollViewerOptions, DocxTextRun, DocxTextRunInfo, DocxViewer, DocxViewerOptions, EmbeddedFontRef, FieldRun, FindHighlightColors, FindMatch, FindMatchesOptions, FramePr, GradientStop, HeaderFooter, HeadersFooters, HyperlinkTarget, ImageRun, LineEnd, LineNumbering, LineSpacing, LoadOptions$2 as LoadOptions, NoteRef, NumberingInfo, OoxmlDecodedImageLimitError, OoxmlDecodedImageLimitMetric, OoxmlError, OoxmlErrorCode, OoxmlErrorSource, OoxmlErrorStage, OoxmlFormat, OoxmlResourceLimit, OoxmlResourceLimitError, OoxmlResourceLimitErrorDetails, OoxmlResourceLimits, OoxmlResourceMetric, OoxmlResourceMetrics, OoxmlResourceMetricsCheckpoint, OoxmlResourceName, OoxmlResourcePolicySnapshot, OoxmlResourceUsageSnapshot, OoxmlResourceViolation, PTabRun, PageBorderEdge, PageBorders, PageNumType, ParaBorderEdge, ParagraphBorders, PathCmd$1 as PathCmd, RenderPageOptions, RenderPageToBitmapOptions, RubyAnnotation, RunRevision, SectionGeom, SectionProps, ShapeRun, ShapeText$1 as ShapeText, ShapeTextRun$1 as ShapeTextRun, TabStop, TableBorders, TblpPr, TextPath, WireRenderPageOptions, autoResize, buildDocxHighlightLayer, buildDocxTextLayer, isOoxmlDecodedImageLimitError, noteText, openExternalHyperlink };
4665
4687
  }
4666
4688
  //#endregion
4667
- //#region dist/.types-work/pptx-B9ExAlcU.d.ts
4689
+ //#region dist/.types-work/pptx-BVpCWU0A.d.ts
4668
4690
  //#region packages/pptx/src/types.d.ts
4669
4691
  /**
4670
4692
  * Picture bullet — ECMA-376 §21.1.2.4.2 `<a:buBlip><a:blip r:embed>`. The
@@ -4892,7 +4914,7 @@ interface ShapeElement {
4892
4914
  defaultTextColor: string | null;
4893
4915
  /** Custom geometry sub-paths (set only when geometry === "custGeom").
4894
4916
  * Outer array: one entry per <a:path>; inner: path commands with coords in [0,1]. */
4895
- custGeom: PathCmd$1[][] | null;
4917
+ custGeom: PathCmd$2[][] | null;
4896
4918
  /** First adjustment value from prstGeom avLst (e.g. trapezoid inset). Range 0–100000. */
4897
4919
  adj: number | null;
4898
4920
  /** Second adjustment value from prstGeom avLst (e.g. arrow head width). Range 0–100000. */
@@ -5192,7 +5214,7 @@ interface PictureElement {
5192
5214
  * Path2D and `ctx.clip()` before drawing the bitmap so the image is
5193
5215
  * trimmed to the laptop / device silhouette declared in the file.
5194
5216
  */
5195
- custGeom?: PathCmd$1[][] | null;
5217
+ custGeom?: PathCmd$2[][] | null;
5196
5218
  /**
5197
5219
  * Drop shadow from `spPr > effectLst > outerShdw`. A `p:pic`'s `spPr` is
5198
5220
  * `CT_ShapeProperties` (ECMA-376 §19.3.1.37), so the same effects shapes
@@ -6596,10 +6618,10 @@ type PptxHighlightColors = FindHighlightColors;
6596
6618
  */
6597
6619
  declare function buildPptxHighlightLayer(layer: HTMLDivElement, runs: PptxTextRunInfo[], matches: PptxHighlightMatch[], cssWidth: number, cssHeight: number, measureForFont: (font: string) => (s: string) => number, colors?: PptxHighlightColors): void;
6598
6620
  declare namespace pptx_d_exports {
6599
- export { AutoResizeOptions, Bevel3d, BlipBullet, Bullet, Camera3d, ChartElement, ChartModel, ChartSeries, DimOptions, Fill, FillRect, FindHighlightColors, FindMatch, FindMatchesOptions, Glow, GradientFill, GradientStop, HiddenSlideMode, HyperlinkTarget, ImageFill, LightRig, LineBreak, LoadOptions$1 as LoadOptions, MediaElement, NoFill, OoxmlDecodedImageLimitError, OoxmlDecodedImageLimitMetric, OoxmlError, OoxmlErrorCode, OoxmlErrorSource, OoxmlErrorStage, OoxmlFormat, OoxmlResourceLimit, OoxmlResourceLimitError, OoxmlResourceLimitErrorDetails, OoxmlResourceLimits, OoxmlResourceMetric, OoxmlResourceMetrics, OoxmlResourceMetricsCheckpoint, OoxmlResourceName, OoxmlResourcePolicySnapshot, OoxmlResourceUsageSnapshot, OoxmlResourceViolation, Paragraph, PathCmd$1 as PathCmd, PictureElement, PptxComment, PptxHighlightColors, PptxHighlightMatch, PptxMatchLocation, PptxPresentation, PptxScrollViewer, PptxScrollViewerOptions, PptxTextRunInfo, PptxViewer, PptxViewerOptions, Presentation, PresentationHandle, Reflection, RenderOptions, RenderSlideOptions, RenderSlideToBitmapOptions, Rot3d, Scene3d, Shadow, ShapeElement, Slide, SlideElement, SoftEdge, SolidFill, Sp3d, SpaceLine, Stroke, TabStop, TableCell, TableElement, TableRow, TextBody, TextRect, TextRun, TextRunCallback, TextRunData, TileInfo, autoResize, buildPptxHighlightLayer, buildPptxTextLayer, isOoxmlDecodedImageLimitError, openExternalHyperlink, renderSlide };
6621
+ export { AutoResizeOptions, Bevel3d, BlipBullet, Bullet, Camera3d, ChartElement, ChartModel, ChartSeries, DimOptions, Fill, FillRect, FindHighlightColors, FindMatch, FindMatchesOptions, Glow, GradientFill, GradientStop$1 as GradientStop, HiddenSlideMode, HyperlinkTarget, ImageFill, LightRig, LineBreak, LoadOptions$1 as LoadOptions, MediaElement, NoFill, OoxmlDecodedImageLimitError, OoxmlDecodedImageLimitMetric, OoxmlError, OoxmlErrorCode, OoxmlErrorSource, OoxmlErrorStage, OoxmlFormat, OoxmlResourceLimit, OoxmlResourceLimitError, OoxmlResourceLimitErrorDetails, OoxmlResourceLimits, OoxmlResourceMetric, OoxmlResourceMetrics, OoxmlResourceMetricsCheckpoint, OoxmlResourceName, OoxmlResourcePolicySnapshot, OoxmlResourceUsageSnapshot, OoxmlResourceViolation, Paragraph, PathCmd$2 as PathCmd, PictureElement, PptxComment, PptxHighlightColors, PptxHighlightMatch, PptxMatchLocation, PptxPresentation, PptxScrollViewer, PptxScrollViewerOptions, PptxTextRunInfo, PptxViewer, PptxViewerOptions, Presentation, PresentationHandle, Reflection, RenderOptions, RenderSlideOptions, RenderSlideToBitmapOptions, Rot3d, Scene3d, Shadow, ShapeElement, Slide, SlideElement, SoftEdge, SolidFill, Sp3d, SpaceLine, Stroke, TabStop$1 as TabStop, TableCell, TableElement, TableRow, TextBody, TextRect, TextRun, TextRunCallback, TextRunData, TileInfo, autoResize, buildPptxHighlightLayer, buildPptxTextLayer, isOoxmlDecodedImageLimitError, openExternalHyperlink, renderSlide };
6600
6622
  }
6601
6623
  //#endregion
6602
- //#region dist/.types-work/xlsx-DJYn-XbI.d.ts
6624
+ //#region dist/.types-work/xlsx-Dyp8TxbZ.d.ts
6603
6625
  //#region packages/xlsx/src/types.d.ts
6604
6626
  interface Workbook {
6605
6627
  sheets: SheetMeta[];