@docx-editor.dev/core 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +9 -15
  2. package/dist/automation.cjs +1 -1
  3. package/dist/automation.d.ts +11 -9
  4. package/dist/automation.js +1 -1
  5. package/dist/binding.cjs +1 -1
  6. package/dist/binding.d.ts +12 -16
  7. package/dist/binding.js +1 -1
  8. package/dist/{chunk-Q4AB7E4O.cjs → chunk-4MIFIUEV.cjs} +1 -1
  9. package/dist/chunk-66DTF4TD.cjs +2 -0
  10. package/dist/chunk-67U2OZS7.cjs +2 -0
  11. package/dist/chunk-6EGMDUL7.js +21 -0
  12. package/dist/chunk-ABXCIZVW.js +2 -0
  13. package/dist/chunk-AEIAYDCG.js +1 -0
  14. package/dist/{chunk-YL4Q5LZP.cjs → chunk-BPSGGVP5.cjs} +1 -1
  15. package/dist/chunk-CMV2MTDE.js +5 -0
  16. package/dist/chunk-DYPT6YDY.js +1 -0
  17. package/dist/chunk-EWV5JMJY.cjs +21 -0
  18. package/dist/chunk-FAD7DH6Q.cjs +1 -0
  19. package/dist/chunk-GHNTGVKG.cjs +11 -0
  20. package/dist/chunk-GQVKCK33.js +2 -0
  21. package/dist/chunk-I4ENGOOZ.js +1 -0
  22. package/dist/chunk-IZHDCQUC.js +20 -0
  23. package/dist/chunk-KEXMDQ2Z.js +1 -0
  24. package/dist/chunk-KQEUFAAG.cjs +20 -0
  25. package/dist/chunk-QVJO5FUR.js +11 -0
  26. package/dist/chunk-SIUUTE5M.js +2 -0
  27. package/dist/chunk-TDHR2QLH.cjs +1 -0
  28. package/dist/{chunk-PXKZUTHX.cjs → chunk-WGHVIB6B.cjs} +2 -2
  29. package/dist/chunk-XKQTITOJ.cjs +5 -0
  30. package/dist/{chunk-KXPVOPR5.js → chunk-YKDRBDI2.js} +1 -1
  31. package/dist/chunk-YRTRPSSA.cjs +1 -0
  32. package/dist/collaboration/replication.cjs +1 -1
  33. package/dist/collaboration/replication.d.ts +4 -4
  34. package/dist/collaboration/replication.js +1 -1
  35. package/dist/collaboration.d.ts +4 -4
  36. package/dist/contracts/editor.d.ts +9 -7
  37. package/dist/contracts/interaction.d.ts +73 -10
  38. package/dist/contracts/modules.d.ts +8 -8
  39. package/dist/{custom-node-writes-C9ArIlJh.d.ts → custom-node-writes-4WOL8JB_.d.ts} +2 -2
  40. package/dist/{editor-sRxrFJd8.d.ts → editor-PDx0fqSo.d.ts} +26 -125
  41. package/dist/editor-scope-DZJT4ukT.d.ts +53 -0
  42. package/dist/editor.cjs +1 -1
  43. package/dist/editor.css +1 -1
  44. package/dist/editor.d.ts +32 -26
  45. package/dist/editor.js +1 -1
  46. package/dist/export.cjs +2 -2
  47. package/dist/export.d.ts +24 -12
  48. package/dist/export.js +2 -2
  49. package/dist/{font-resolver-BYEHQGTw.d.ts → font-resolver-CdqOugwZ.d.ts} +2 -2
  50. package/dist/{headless-document-view-DELC4E5v.d.ts → headless-document-view-C9ONlGtf.d.ts} +2 -2
  51. package/dist/{hf-references-DTrHkktY.d.ts → hf-references-BZVHctVD.d.ts} +1 -1
  52. package/dist/{index-C3Gvq4jN.d.ts → index-BVb4a6O3.d.ts} +74 -28
  53. package/dist/index.cjs +1 -1
  54. package/dist/index.d.ts +20 -18
  55. package/dist/index.js +1 -1
  56. package/dist/{layout-shaping-DoGR5cf8.d.ts → layout-shaping-D_U5z-Qh.d.ts} +1 -1
  57. package/dist/layout.cjs +1 -1
  58. package/dist/layout.d.ts +270 -459
  59. package/dist/layout.js +1 -1
  60. package/dist/{note-properties-DSe8igrz.d.ts → note-properties-BosjbkGC.d.ts} +1 -1
  61. package/dist/{omml-equation-BpRmfDAf.d.ts → omml-equation-oLlbPpVy.d.ts} +1 -1
  62. package/dist/output.cjs +1 -1
  63. package/dist/output.d.ts +6 -6
  64. package/dist/output.js +1 -1
  65. package/dist/{protocol-DZzKxH5r.d.ts → protocol-CUsqt13U.d.ts} +28 -6
  66. package/dist/{review-items-DYRx187i.d.ts → review-items-kC-YI70C.d.ts} +1 -1
  67. package/dist/{review-reads-LbGUdNzn.d.ts → review-reads-DdvADsmp.d.ts} +8 -7
  68. package/dist/{revision-projection-DHRJH48u.d.ts → revision-projection-CN_DiOl_.d.ts} +1 -1
  69. package/dist/{semantic-cell-selection-BXVWCBYm.d.ts → semantic-cell-selection-BMrXDniV.d.ts} +4 -4
  70. package/dist/{semantic-interaction-DZsDmPg5.d.ts → semantic-interaction-NGeYzYCN.d.ts} +1 -1
  71. package/dist/{semantic-paint-f7SqN4oW.d.ts → semantic-paint-CM8F-8pH.d.ts} +2 -2
  72. package/dist/{semantic-records-EIdwId-a.d.ts → semantic-records-BreR44Ki.d.ts} +1231 -947
  73. package/dist/store.cjs +1 -1
  74. package/dist/store.d.ts +80 -20
  75. package/dist/store.js +1 -1
  76. package/dist/{tree-op-types-egdarvH_.d.ts → tree-op-types-C5rZzoAz.d.ts} +131 -53
  77. package/dist/{tree-package-fragment-DjkH4ESI.d.ts → tree-package-fragment-DDQHk8sv.d.ts} +3 -3
  78. package/dist/{tree-session-contract-CGOOgPjW.d.ts → tree-session-contract-dKgbKH9A.d.ts} +18 -8
  79. package/dist/{tree-store-DKJB0IcA.d.ts → tree-store-DU192chS.d.ts} +1 -1
  80. package/package.json +2 -2
  81. package/dist/chunk-24APKZAN.cjs +0 -11
  82. package/dist/chunk-2YH4MSHX.cjs +0 -2
  83. package/dist/chunk-5Y7AADI6.js +0 -1
  84. package/dist/chunk-AREFLNUP.js +0 -11
  85. package/dist/chunk-AS7YZNQE.cjs +0 -2
  86. package/dist/chunk-AVGCKH6A.cjs +0 -20
  87. package/dist/chunk-CDYVDQRI.cjs +0 -5
  88. package/dist/chunk-HOEWFH5U.cjs +0 -1
  89. package/dist/chunk-KFELX7CN.js +0 -2
  90. package/dist/chunk-LGFKIF4M.cjs +0 -1
  91. package/dist/chunk-M52QXFKI.js +0 -1
  92. package/dist/chunk-MNUKSXRO.js +0 -1
  93. package/dist/chunk-PBOIGBDO.js +0 -2
  94. package/dist/chunk-PMHGONET.cjs +0 -1
  95. package/dist/chunk-QJYH6AYD.js +0 -1
  96. package/dist/chunk-QRC37MWG.cjs +0 -20
  97. package/dist/chunk-RJSB6RJ7.js +0 -20
  98. package/dist/chunk-SB54HLFJ.js +0 -20
  99. package/dist/chunk-XCESLTJL.js +0 -2
  100. package/dist/chunk-Z4527AXR.js +0 -5
@@ -1,264 +1,174 @@
1
- import { b as RevisionAttribution, R as RevisionDisplayMode } from './revision-projection-DHRJH48u.js';
2
- import { a as OoxmlNode, q as OoxmlProperty, c as OoxmlElement, U as TableBorderStyle, O as OoxmlPart, x as ImageResourceState, W as DrawingAccessibility, S as SourceCrop, X as DrawingTransform, Y as VectorShapeProjection, z as DrawingHorizontalReferenceFrame, A as DrawingVerticalReferenceFrame, u as ImageWrapTarget, Z as DrawingProjection, _ as OoxmlDrawingNode } from './tree-op-types-egdarvH_.js';
3
- import { O as OmmlEquationProjection } from './omml-equation-BpRmfDAf.js';
1
+ import { b as RevisionAttribution, R as RevisionDisplayMode, a as RevisionAuthorFilter } from './revision-projection-CN_DiOl_.js';
2
+ import { q as OoxmlProperty, c as OoxmlElement, a as OoxmlNode, O as OoxmlPart, U as TableBorderStyle, x as ImageResourceState, W as DrawingAccessibility, X as DrawingImageEffects, S as SourceCrop, Y as DrawingTransform, Z as VectorShapeProjection, z as DrawingHorizontalReferenceFrame, A as DrawingVerticalReferenceFrame, u as ImageWrapTarget, _ as DrawingProjection, $ as OoxmlDrawingNode } from './tree-op-types-C5rZzoAz.js';
3
+ import { O as OmmlEquationProjection } from './omml-equation-oLlbPpVy.js';
4
4
 
5
+ /** Soft ceiling on abstractNum / num entries read from one part. */
6
+ declare const MAX_NUMBERING_DEFINITIONS = 512;
7
+ /** Soft ceiling on override entries per `w:num`. */
8
+ declare const MAX_LVL_OVERRIDES = 9;
9
+ /** `w:suff` — what separates a list marker from the text after it. */
10
+ type ListSuffix = 'tab' | 'space' | 'nothing';
11
+ /** `w:lvlJc` — how a list marker aligns within its own indent. */
12
+ type ListMarkerAlign = 'left' | 'center' | 'right';
5
13
  /**
6
- * Whether a paragraph must start a new page (`w:pageBreakBefore`).
7
- *
8
- * LAST WINS, like every other toggle read off the cascade (`paragraphKeeps` does the same
9
- * for `w:keepNext`). An any-wins `.some()` cannot be switched off: a Chapter or Heading style
10
- * carries `w:pageBreakBefore`, and Word writes `w:val="0"` on the one instance whose author
11
- * unchecked the box — that paragraph still broke, so the document grew a blank page and every
12
- * page number after it was wrong.
14
+ * One level's indent, plus which parts the level actually AUTHORED.
13
15
  *
14
- * An absent `w:val` is on (§17.17.4), and the off vocabulary is the whole of it — `off` is a
15
- * spelling too, which {@link isOn} beside this already accepts.
16
- */
17
- declare function paragraphBreaksBefore(props: readonly OoxmlProperty[]): boolean;
18
- /**
19
- * Soft ceiling matching the spike's resolved-style limit (31_680 twips ≈ 22"). Beyond
20
- * that an attacker-authored spacing would push pagination into pathological page counts.
16
+ * The provenance matters: `w:ind` cascades per-attribute, so a level that authored only `left`
17
+ * must not overwrite an inherited `hanging` with a synthesized zero.
21
18
  */
22
- declare const MAX_PARAGRAPH_SPACING_PT: number;
23
- /** Soft ceiling on border width (96 eighths = 12pt). Word's UI tops out well below this. */
24
- declare const MAX_BORDER_WIDTH_PT = 12;
25
- /** Soft ceiling on border-to-text gap (`w:space`, already in points). */
26
- declare const MAX_BORDER_SPACE_PT = 3168;
19
+ interface NumberingLevelIndent {
20
+ readonly left: number;
21
+ readonly right: number;
22
+ readonly hanging: number;
23
+ readonly firstLine: number;
24
+ /**
25
+ * Which of these the LEVEL actually authored.
26
+ *
27
+ * A level's `w:pPr/w:ind` sits between the paragraph style and direct formatting, so the
28
+ * merge has to tell "the level says left = 0" from "the level says nothing about left and
29
+ * the style's value stands". Absent (the default) reads as "says nothing", which is what a
30
+ * hand-built level in a test means.
31
+ */
32
+ readonly stated?: {
33
+ readonly left: boolean;
34
+ readonly right: boolean;
35
+ /** `w:hanging`/`w:firstLine` are one mutually exclusive slot (§17.3.1.10, §17.3.1.12). */
36
+ readonly firstLineOffset: boolean;
37
+ };
38
+ }
39
+ /** One `w:lvl`: how this depth numbers, what its marker looks like, and how it indents. */
40
+ interface NumberingLevel {
41
+ readonly ilvl: number;
42
+ readonly start: number;
43
+ readonly numFmt: string;
44
+ readonly lvlText: string;
45
+ readonly lvlJc: ListMarkerAlign;
46
+ readonly suff: ListSuffix;
47
+ readonly indent: NumberingLevelIndent;
48
+ /**
49
+ * `w:lvlRestart` one-based trigger level, or `0` when the level never restarts.
50
+ * Omitted in XML → restart when the previous level (or any earlier level) is used.
51
+ */
52
+ readonly lvlRestart?: number;
53
+ /**
54
+ * `w:isLgl` (§17.9.9): render EVERY level referenced by this level's `w:lvlText` in
55
+ * decimal, whatever number format those levels declare for themselves.
56
+ */
57
+ readonly isLgl: boolean;
58
+ /** Level `w:rPr` as flat properties (for marker face / vanish). */
59
+ readonly runProperties: readonly OoxmlProperty[];
60
+ /** True when level run props request vanish — marker must not paint. */
61
+ readonly vanish: boolean;
62
+ }
27
63
  /**
28
- * A paragraph's resolved space before and after, in points.
64
+ * One `w:lvlOverride` on a `w:num`: a restart value, a replacement level, or both.
29
65
  *
30
- * Already collapsed against `w:contextualSpacing`, so adjacent same-style paragraphs that suppress
31
- * their gap arrive here with it removed rather than leaving that to whoever stacks them.
66
+ * How two lists share an abstract definition while numbering independently.
32
67
  */
33
- interface ParagraphSpacing {
34
- /** `w:spacing/@before`, in points. */
35
- readonly before: number;
36
- /** `w:spacing/@after`, in points. */
37
- readonly after: number;
68
+ interface LevelOverride {
69
+ readonly startOverride?: number;
70
+ /** Full level replacement when `w:lvl` is present under the override. */
71
+ readonly level?: NumberingLevel;
38
72
  }
39
73
  /**
40
- * The gap Word substitutes when `w:beforeAutospacing` / `w:afterAutospacing` is on
41
- * (ECMA-376 §17.3.1.33, the `w:spacing` clause both attributes belong to).
74
+ * One `w:abstractNum` — the shape of a list, without being a list.
42
75
  *
43
- * The attribute means "the consumer decides", and the authored `@before` / `@after` beside it
44
- * is IGNORED rather than used as the value. Word's answer is HTML's default `<p>` margin,
45
- * 14pt, which is what a document round-tripped through Word's HTML filter carries — and this
46
- * one is everywhere, because Word writes `w:before="100" w:beforeAutospacing="1"` for it. Reading
47
- * only the literal 100 twips lays every such paragraph out 9pt tight, which moves page breaks.
76
+ * Never referenced by a paragraph directly. Paragraphs name a {@link NumDefinition}, which names
77
+ * one of these, so several lists can share a definition and still count separately.
48
78
  */
49
- declare const AUTO_PARAGRAPH_SPACING_PT = 14;
79
+ interface AbstractNumDefinition {
80
+ readonly abstractNumId: string;
81
+ readonly levels: ReadonlyMap<number, NumberingLevel>;
82
+ /**
83
+ * `w:numStyleLink` (§17.9.21): this definition carries no levels of its own — the numbering
84
+ * lives on the named style. Word follows the link; resolve it with
85
+ * {@link resolveNumberingStyleLinks} before reading levels, or every paragraph on this
86
+ * abstract renders with NO marker at all.
87
+ */
88
+ readonly numStyleLink?: string;
89
+ /**
90
+ * `w:styleLink` (§17.9.23): the definition side of the same pair — this abstract IS the
91
+ * numbering of the named style. Kept so a link can be verified rather than assumed.
92
+ */
93
+ readonly styleLink?: string;
94
+ }
50
95
  /**
51
- * Where a paragraph sits, for the two contexts in which Word's auto spacing resolves to 0
52
- * instead of {@link AUTO_PARAGRAPH_SPACING_PT}.
96
+ * One `w:num` — the thing a paragraph's `w:numId` actually names.
53
97
  *
54
- * Both come from the HTML model the attribute emulates: a `<li>` and a `<td>` collapse the
55
- * paragraph margin, a bare `<p>` does not. A caller that says nothing gets the body answer.
98
+ * Points at an {@link AbstractNumDefinition} and may override any of its levels.
56
99
  */
57
- interface ParagraphAutoSpacingContext {
58
- /** The paragraph participates in numbering (`w:numPr`), i.e. it is a list item. */
59
- readonly inList?: boolean;
60
- /** The paragraph lives in a table cell. */
61
- readonly inTableCell?: boolean;
100
+ interface NumDefinition {
101
+ readonly numId: string;
102
+ readonly abstractNumId: string;
103
+ readonly overrides: ReadonlyMap<number, LevelOverride>;
62
104
  }
63
105
  /**
64
- * Resolved line spacing (`w:spacing/@line` + `@lineRule`, ECMA-376 17.3.1.33).
65
- *
66
- * `auto` is the interesting one: `@line` is 240ths of a line, so 240 is single, 360 is
67
- * one-and-a-half, 480 is double — and Word's own Normal style since 2013 is 259, i.e.
68
- * 1.08. A document laid out at a flat single spacing is ~8% tight on EVERY line, which
69
- * moves every page break, so this is not a cosmetic detail.
106
+ * The bounded projection of `numbering.xml` that list layout resolves against.
70
107
  *
71
- * `exact` fixes the line box at `@line` twips and lets tall glyphs clip, the way Word
72
- * does. `atLeast` uses it as a floor.
108
+ * Projection ONLY — never a mutation or serialization authority. Hostile values are dropped or
109
+ * clamped, and a missing definition resolves to "no list" rather than a guess.
73
110
  */
74
- type LineSpacingRule = 'auto' | 'exact' | 'atLeast';
111
+ interface NumberingIndex {
112
+ readonly abstractNums: ReadonlyMap<string, AbstractNumDefinition>;
113
+ readonly nums: ReadonlyMap<string, NumDefinition>;
114
+ }
75
115
  /**
76
- * Resolved line spacing: the rule, and the value it applies.
116
+ * Build a numbering index from the root of a numbering part (`w:numbering`).
77
117
  *
78
- * `value` means different things per rule — 240ths of a line under `auto`, points under `exact`
79
- * and `atLeast` — which is why the two travel together and neither is useful alone.
118
+ * Empty / missing roots yield an empty index. Duplicate ids keep the first definition.
80
119
  */
81
- interface ParagraphLineSpacing {
82
- readonly rule: LineSpacingRule;
83
- /** `auto`: the 240ths-of-a-line multiplier numerator. Otherwise points. */
84
- readonly value: number;
85
- }
86
- /** Single spacing: what a paragraph that says nothing gets. */
87
- declare const SINGLE_LINE_SPACING: ParagraphLineSpacing;
88
120
  /**
89
- * One resolved `w:pBdr` edge: its style, colour, thickness and gap.
121
+ * Project `numbering.xml` into the bounded index.
90
122
  *
91
- * `widthPt` and `spacePt` are already converted and CLAMPED — both come from a file, and an
92
- * unbounded border width becomes a layout dimension.
123
+ * Every ceiling here exists because the input is file-derived: definition counts, override
124
+ * counts, indent magnitudes and style-link hop depth are all capped, and nothing from the file
125
+ * becomes a loop bound or an allocation size.
93
126
  */
94
- interface ParagraphBorderEdge {
95
- /** Authored `ST_Border` value (`single`, `dashed`, …). */
96
- readonly val: string;
97
- /** RRGGBB, or null when auto/missing (paint defaults to black). */
98
- readonly color: string | null;
99
- /** Border thickness in points (`w:sz` is eighths of a point). */
100
- readonly widthPt: number;
101
- /** Gap from text to the rule, in points (`w:space`). */
102
- readonly spacePt: number;
103
- /**
104
- * `w:shadow` — Word offsets a drop shadow behind the rule.
105
- *
106
- * Present only when authored true, so an edge that says nothing keeps the shape earlier
107
- * fixtures assert. Resolved and carried; drawing it is deferred.
108
- */
109
- readonly shadow?: true;
110
- }
111
- /** The six `CT_PBdr` children, in schema order (ECMA-376 §17.3.1.24). */
112
- declare const PARAGRAPH_BORDER_SIDES: readonly ["top", "left", "bottom", "right", "between", "bar"];
127
+ declare function buildNumberingIndex(root: OoxmlElement | null | undefined): NumberingIndex;
128
+ /** Resolve the effective level for a `numId` + `ilvl`, applying overrides. */
113
129
  /**
114
- * Which of the six `CT_PBdr` edges.
130
+ * The effective level for one paragraph's numbering reference, after overrides and style links.
115
131
  *
116
- * Four are physical box edges; `between` and `bar` are group-relative, drawn only where
117
- * consecutive paragraphs share a border definition.
132
+ * Answers null for a reference the index cannot resolve — a paragraph naming a definition the
133
+ * file never declared is an unnumbered paragraph, not an error.
118
134
  */
119
- type ParagraphBorderSide = (typeof PARAGRAPH_BORDER_SIDES)[number];
135
+ declare function resolveNumberingLevel(index: NumberingIndex, numId: string, ilvl: number): {
136
+ readonly abstractNumId: string;
137
+ readonly level: NumberingLevel;
138
+ readonly startOverride?: number;
139
+ } | null;
140
+ /** Empty index for tests / documents without numbering. */
141
+ declare const EMPTY_NUMBERING_INDEX: NumberingIndex;
142
+
143
+ /** Soft ceiling matching Word's practical custom-tab UI limit. */
144
+ declare const MAX_TAB_STOPS = 64;
120
145
  /**
121
- * A paragraph's resolved `w:pBdr` (ECMA-376 §17.3.1.24).
122
- *
123
- * `top`/`left`/`bottom`/`right` are the four physical edges of the box. The other two are
124
- * group-relative: `between` draws at a boundary INSIDE a run of consecutive paragraphs whose
125
- * border settings are identical, and `bar` is the vertical change-bar rule beside the
126
- * paragraph, drawn whether or not the paragraph groups with its neighbours.
146
+ * Soft ceiling on a tab position (31_680 twips ≈ 22"), matching paragraph-spacing bounds so
147
+ * a hostile stop cannot shove layout into pathological widths.
127
148
  */
128
- interface ParagraphBorders {
129
- readonly top?: ParagraphBorderEdge;
130
- readonly left?: ParagraphBorderEdge;
131
- readonly bottom?: ParagraphBorderEdge;
132
- readonly right?: ParagraphBorderEdge;
133
- readonly between?: ParagraphBorderEdge;
134
- readonly bar?: ParagraphBorderEdge;
135
- }
149
+ declare const MAX_TAB_POSITION_TWIPS = 31680;
150
+ /** OOXML / Word default when `w:settings/w:defaultTabStop` is absent: 720 twips = 0.5". */
151
+ declare const DEFAULT_TAB_INTERVAL_TWIPS = 720;
152
+ /** The default tab interval in points — {@link DEFAULT_TAB_INTERVAL_TWIPS} converted. */
153
+ declare const DEFAULT_TAB_INTERVAL_PT: number;
136
154
  /**
137
- * Resolve `w:spacing` before/after from flat paragraph properties.
138
- *
139
- * Line spacing (`w:line` / `w:lineRule`) is a separate concern — it changes measured line
140
- * height, not the gap between paragraphs — and is not resolved here.
155
+ * How a tab stop positions the text that follows it.
141
156
  *
142
- * `w:beforeAutospacing` / `w:afterAutospacing` REPLACE the authored measurement on their own
143
- * side rather than adding to it; see {@link AUTO_PARAGRAPH_SPACING_PT}.
157
+ * Only `left` is a plain cursor jump. The other three size the tab glyph from the MEASURED
158
+ * following segment, so its end, centre or decimal point lands on the stop.
144
159
  */
145
- declare function paragraphSpacing(props: readonly OoxmlProperty[], context?: ParagraphAutoSpacingContext): ParagraphSpacing;
160
+ type TabAlignment = 'left' | 'center' | 'right' | 'decimal';
146
161
  /**
147
- * Resolve `w:line` / `w:lineRule` from flat paragraph properties.
162
+ * `w:tab/@w:leader` (ECMA-376 §17.3.1.38, ST_TabTlc): the character repeated across the
163
+ * space a tab reserves. `none` is the default and is represented by an absent leader.
148
164
  *
149
- * Merged per attribute for the same reason as before/after: `w:spacing` is one element
150
- * carrying independent attributes, and a style that states only `@line` must not reset the
151
- * rule an earlier entry in the cascade set.
165
+ * This is the difference between a Word table of contents and a column of headings floating
166
+ * next to a column of page numbers, so it is carried through layout to paint rather than
167
+ * dropped as a geometry-irrelevant attribute.
152
168
  */
153
- declare function paragraphLineSpacing(props: readonly OoxmlProperty[]): ParagraphLineSpacing;
169
+ type TabLeader = 'dot' | 'hyphen' | 'underscore' | 'heavy' | 'middleDot';
154
170
  /**
155
- * Apply resolved line spacing to a line's natural (glyph-derived) box.
156
- *
157
- * Word places `auto` / `atLeast` extras BELOW the line (the last line's multiple spacing
158
- * still separates it from the next paragraph). Putting that delta above inverted cover-page
159
- * rhythm: `w:line="460"` on "between" opened a large gap above the word and almost none
160
- * before "MERIDIAN". `exact` taller than the glyphs centers the text (ECMA-376 17.3.1.33).
161
- * An `exact` box smaller than the glyphs keeps the baseline inside so clipped text still
162
- * sits on it.
163
- */
164
- declare function applyLineSpacing(spacing: ParagraphLineSpacing, naturalHeight: number, naturalBaseline: number): {
165
- height: number;
166
- baseline: number;
167
- };
168
- /**
169
- * `w:contextualSpacing` (17.3.1.9): drop before/after between paragraphs of the SAME
170
- * style. Word's built-in `ListParagraph` sets it, so every list authored in Word gets a
171
- * paragraph gap between items without this.
172
- */
173
- declare function paragraphContextualSpacing(props: readonly OoxmlProperty[]): boolean;
174
- /**
175
- * Resolve `w:pBdr` from the paragraph-properties node.
176
- *
177
- * Nested — every edge is a child of `pBdr`, not an attribute — so this reads the typed tree
178
- * rather than the flattened `OoxmlProperty[]` bag `propertiesOf` builds for leaf props.
179
- */
180
- declare function paragraphBorders(pPr: OoxmlNode | undefined): ParagraphBorders;
181
- /**
182
- * `w:pBdr` after the style cascade: a later `w:pBdr` replaces an earlier one WHOLESALE.
183
- *
184
- * Word does not merge edges across the cascade. A style that states only `w:bottom` discards
185
- * the box its `basedOn` ancestor declared, so folding edge by edge would leave a lone
186
- * underline surrounded by a box no one authored. Absence inherits; `nil`/`none` clear.
187
- */
188
- declare function cascadedParagraphBorders(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorders;
189
- /**
190
- * Visual stroke thickness layout publishes for one edge (points).
191
- *
192
- * Compound `ST_Border` values (`double`, …) use the shared inflated band so a thin
193
- * `w:sz="3"` double still occupies a visible double-line box — matching table borders.
194
- */
195
- declare function paragraphBorderStrokeWidthPt(edge: ParagraphBorderEdge): number;
196
- /**
197
- * Extent one border edge occupies away from the text it decorates: gap plus rule, in points.
198
- *
199
- * Vertically that is flow height — a top rule pushes the first line down, a bottom rule holds
200
- * the page open below the last one — so pagination has to see it. Horizontally it is
201
- * publish-only: Word draws left/right paragraph rules OUTSIDE the text column and never
202
- * re-breaks the lines, which is why adding a box to a paragraph in Word does not reflow it.
203
- */
204
- declare function paragraphBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
205
- /** Vertical extent a bottom border adds below the last line (gap + rule). */
206
- declare function bottomBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
207
- /**
208
- * Identity of a paragraph's border set, for the `w:between` group rule.
209
- *
210
- * Word treats consecutive paragraphs whose border settings are IDENTICAL as ONE bordered
211
- * block: the top rule draws above the first, the bottom rule below the last, and each
212
- * interior boundary gets `w:between` or nothing (§17.3.1.24). That is why applying a box to
213
- * three selected paragraphs in Word draws one box and not three.
214
- *
215
- * Empty string means "no borders", which never groups with anything.
216
- */
217
- declare function paragraphBordersFingerprint(borders: ParagraphBorders): string;
218
- /**
219
- * Gap to insert before a paragraph once the previous paragraph's `after` is already in the
220
- * flow cursor — Word takes the larger of the two rather than summing them.
221
- */
222
- declare function collapsedSpaceBefore(before: number, previousAfter: number): number;
223
- /**
224
- * Applied before-spacing for placement (Word 2013+ / compat mode 15).
225
- *
226
- * Adjacent before/after still collapse to the larger gap, but before is dropped entirely when
227
- * the paragraph begins at the top of a page mid-section. The first paragraph of a document or
228
- * section retains before. Callers publish this applied value on the fragment so shading, borders,
229
- * selection, and paint share one geometry.
230
- */
231
- declare function appliedSpaceBefore(before: number, previousAfter: number, atTopOfPage: boolean, firstParagraphOfSection: boolean): number;
232
-
233
- /** Soft ceiling matching Word's practical custom-tab UI limit. */
234
- declare const MAX_TAB_STOPS = 64;
235
- /**
236
- * Soft ceiling on a tab position (31_680 twips ≈ 22"), matching paragraph-spacing bounds so
237
- * a hostile stop cannot shove layout into pathological widths.
238
- */
239
- declare const MAX_TAB_POSITION_TWIPS = 31680;
240
- /** OOXML / Word default when `w:settings/w:defaultTabStop` is absent: 720 twips = 0.5". */
241
- declare const DEFAULT_TAB_INTERVAL_TWIPS = 720;
242
- /** The default tab interval in points — {@link DEFAULT_TAB_INTERVAL_TWIPS} converted. */
243
- declare const DEFAULT_TAB_INTERVAL_PT: number;
244
- /**
245
- * How a tab stop positions the text that follows it.
246
- *
247
- * Only `left` is a plain cursor jump. The other three size the tab glyph from the MEASURED
248
- * following segment, so its end, centre or decimal point lands on the stop.
249
- */
250
- type TabAlignment = 'left' | 'center' | 'right' | 'decimal';
251
- /**
252
- * `w:tab/@w:leader` (ECMA-376 §17.3.1.38, ST_TabTlc): the character repeated across the
253
- * space a tab reserves. `none` is the default and is represented by an absent leader.
254
- *
255
- * This is the difference between a Word table of contents and a column of headings floating
256
- * next to a column of page numbers, so it is carried through layout to paint rather than
257
- * dropped as a geometry-irrelevant attribute.
258
- */
259
- type TabLeader = 'dot' | 'hyphen' | 'underscore' | 'heavy' | 'middleDot';
260
- /**
261
- * The character each leader repeats (§17.3.1.38, ST_TabTlc).
171
+ * The character each leader repeats (§17.3.1.38, ST_TabTlc).
262
172
  *
263
173
  * Lives with the type rather than with the painter because LAYOUT has to measure it: a
264
174
  * leader is the same character typed over and over, and the only way to space it the way
@@ -339,6 +249,125 @@ declare function tabAdvanceWidth(alignment: TabAlignment, currentX: number, dest
339
249
  */
340
250
  declare function tabStopsFingerprint(tabs: ResolvedTabStops): string;
341
251
 
252
+ /** `w:vertAlign` — script position, which also scales the run's effective size. */
253
+ type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
254
+ /** A resolved underline: its variant, and its colour when it does not follow the text. */
255
+ interface ResolvedUnderline {
256
+ /** The authored `ST_Underline` variant. */
257
+ readonly variant: string;
258
+ /** RRGGBB, or null when the underline follows the text colour. */
259
+ readonly color: string | null;
260
+ }
261
+ /**
262
+ * A run's character properties after the full cascade, in the units layout works in.
263
+ *
264
+ * Points rather than half-points, RRGGBB rather than theme references — everything already
265
+ * resolved, so measurement and paint never re-run the cascade per glyph.
266
+ */
267
+ interface ResolvedRunStyle {
268
+ readonly fontFamily: string | null;
269
+ /**
270
+ * The `eastAsia` slot's typeface (`w:rFonts w:eastAsia`/`w:eastAsiaTheme`), or null when
271
+ * no level authors one.
272
+ *
273
+ * OOXML picks a run's face per SCRIPT: `fontFamily` covers the ascii/hAnsi slots, and
274
+ * ideographic/kana/hangul text resolves through this one instead. Kept beside — not
275
+ * folded into — `fontFamily`, because a mixed run needs both at once and the split into
276
+ * slot-homogeneous pieces happens downstream (`piecesOfParagraph`).
277
+ */
278
+ readonly fontFamilyEastAsia: string | null;
279
+ /** Points. `w:sz` is half-points, so 22 becomes 11. */
280
+ readonly fontSizePt: number;
281
+ /** RRGGBB, or null for the inherited/automatic colour. */
282
+ readonly color: string | null;
283
+ readonly bold: boolean;
284
+ /** Opaque solid `w14:textOutline`, in points; paint only, never a font-weight change. */
285
+ readonly textOutline?: {
286
+ readonly widthPt: number;
287
+ readonly color: string;
288
+ };
289
+ readonly italic: boolean;
290
+ readonly underline: ResolvedUnderline | null;
291
+ readonly strike: boolean;
292
+ readonly doubleStrike: boolean;
293
+ /** An `ST_HighlightColor` name, or null. */
294
+ readonly highlight: string | null;
295
+ /**
296
+ * Character shading fill (`w:rPr/w:shd`), validated RRGGBB, or null.
297
+ *
298
+ * Paint applies this as the glyph-box background; a recognised highlight overrides it.
299
+ * Measurement ignores shading.
300
+ */
301
+ readonly shading: string | null;
302
+ readonly verticalAlign: VerticalAlign;
303
+ /** `w:position`, in points. Positive raises the baseline. */
304
+ readonly baselineShiftPt: number;
305
+ readonly caps: boolean;
306
+ readonly smallCaps: boolean;
307
+ /** `w:spacing`, in points. Added to every advance. */
308
+ readonly characterSpacingPt: number;
309
+ /** `w:w`, as a percentage. 100 is unscaled. */
310
+ readonly horizontalScalePercent: number;
311
+ /** `w:kern`, in points: the size at or above which kerning applies. 0 disables it. */
312
+ readonly kerningMinPt: number;
313
+ /**
314
+ * `w:vanish` (ECMA-376 §17.3.2.45): the run is hidden text.
315
+ *
316
+ * Word does not draw it AND does not paginate it — hidden index or comment text takes no
317
+ * space at all. So this cannot be a paint-time opacity: a hidden run that is still measured
318
+ * pushes every following line, and every following page break, to the wrong place. Layout
319
+ * drops the content instead (see `piecesOfParagraph`).
320
+ *
321
+ * `w:specVanish` (§17.3.2.36) is a different property — an always-hidden paragraph mark on
322
+ * a heading — and never sets this.
323
+ */
324
+ readonly hidden: boolean;
325
+ }
326
+ /** The style a run inherits when it authors nothing. */
327
+ declare const DEFAULT_RUN_STYLE: ResolvedRunStyle;
328
+ /**
329
+ * The theme part's typefaces, for resolving `w:rFonts` theme attributes.
330
+ *
331
+ * Structurally identical to the binding lane's `DocumentThemeFonts` and assignable from it.
332
+ * Declared here so this lane reads validated strings rather than the theme tree.
333
+ */
334
+ interface ThemeFonts {
335
+ /** `a:majorFont` latin typeface — headings. */
336
+ readonly major: string | null;
337
+ /** `a:minorFont` latin typeface — body text. */
338
+ readonly minor: string | null;
339
+ /** `a:majorFont` east asian typeface (`a:ea`) — headings. Optional for back-compat. */
340
+ readonly majorEastAsia?: string | null;
341
+ /** `a:minorFont` east asian typeface (`a:ea`) — body text. Optional for back-compat. */
342
+ readonly minorEastAsia?: string | null;
343
+ }
344
+ /**
345
+ * Resolve one run's direct formatting.
346
+ *
347
+ * Unrecognised values are DROPPED rather than guessed: a `w:sz` of `"large"` leaves the
348
+ * default size rather than inventing one, because a wrong measurement moves every glyph
349
+ * after it and a missing one is visible immediately.
350
+ *
351
+ * `themeFonts` resolves `w:rFonts` theme references. Absent, a theme-only `rFonts` leaves
352
+ * the family inherited — which is what every run of a theme-fonted document does, so the
353
+ * whole document falls back to the surface default face.
354
+ */
355
+ declare function resolveRunStyle(props: readonly OoxmlProperty[], themeFonts?: ThemeFonts): ResolvedRunStyle;
356
+ /** The text as it is DRAWN, after case transforms. Measurement must use this, not the source. */
357
+ declare function displayText(text: string, style: ResolvedRunStyle): string;
358
+ /** Measure run text the way layout breaks lines and paints glyphs (caps/small-caps aware). */
359
+ declare function measureDisplayText(text: string, style: ResolvedRunStyle, measurer: TextMeasurer): number;
360
+ /** Whether two resolved styles are identical, for span merging and cache keys. */
361
+ declare function runStylesEqual(a: ResolvedRunStyle, b: ResolvedRunStyle): boolean;
362
+ /**
363
+ * How far a run's glyphs are lifted off the line's baseline, in points. Positive is up.
364
+ *
365
+ * Super and subscript move the GLYPHS without moving the run's box, so the box keeps tiling
366
+ * the line and the selection band stays continuous. Anything drawing at the glyphs — the
367
+ * painter, and the caret — has to apply this itself, and from one place, or the two drift.
368
+ */
369
+ declare function baselineShiftPtOf(style: ResolvedRunStyle): number;
370
+
342
371
  /**
343
372
  * UAX #9 embedding levels, one per UTF-16 code unit, plus the paragraph ranges they were resolved
344
373
  * within.
@@ -806,146 +835,32 @@ declare const createShapedRun: (input: ShapedRun, environmentInput: ShapingEnvir
806
835
  */
807
836
  declare const shapedRunComparatorInputs: (input: ShapedRun, environment: ShapingEnvironmentInput) => ShapedRunComparatorInputs;
808
837
 
809
- /** `w:vertAlign` — script position, which also scales the run's effective size. */
810
- type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
811
- /** A resolved underline: its variant, and its colour when it does not follow the text. */
812
- interface ResolvedUnderline {
813
- /** The authored `ST_Underline` variant. */
814
- readonly variant: string;
815
- /** RRGGBB, or null when the underline follows the text colour. */
816
- readonly color: string | null;
838
+ /**
839
+ * Which `w:rFonts` slot a character resolves its face through.
840
+ *
841
+ * OOXML gives a run up to four faces and picks between them by SCRIPT, so one run of mixed Latin
842
+ * and CJK text uses two different fonts without saying so anywhere in its properties.
843
+ */
844
+ type FontSlot = 'ascii' | 'hAnsi' | 'eastAsia' | 'cs';
845
+ /**
846
+ * A run of text sharing one script, direction and font slot — the unit handed to the shaper.
847
+ *
848
+ * Shaping cannot span a script change: Arabic and Latin in one call would produce wrong joining
849
+ * behaviour, so a run is itemized into these first.
850
+ */
851
+ interface ScriptItem {
852
+ readonly from: number;
853
+ readonly to: number;
854
+ readonly direction: TextDirection;
855
+ readonly bidiLevel: number;
856
+ readonly script: 'Zyyy' | 'Latn' | 'Grek' | 'Cyrl' | 'Hani' | 'Hebr' | 'Arab' | 'Deva' | 'Beng' | 'Thai' | 'Khmr';
857
+ readonly slot: FontSlot;
817
858
  }
818
859
  /**
819
- * A run's character properties after the full cascade, in the units layout works in.
860
+ * A code point whose script this engine does not itemize.
820
861
  *
821
- * Points rather than half-points, RRGGBB rather than theme references — everything already
822
- * resolved, so measurement and paint never re-run the cascade per glyph.
823
- */
824
- interface ResolvedRunStyle {
825
- readonly fontFamily: string | null;
826
- /**
827
- * The `eastAsia` slot's typeface (`w:rFonts w:eastAsia`/`w:eastAsiaTheme`), or null when
828
- * no level authors one.
829
- *
830
- * OOXML picks a run's face per SCRIPT: `fontFamily` covers the ascii/hAnsi slots, and
831
- * ideographic/kana/hangul text resolves through this one instead. Kept beside — not
832
- * folded into — `fontFamily`, because a mixed run needs both at once and the split into
833
- * slot-homogeneous pieces happens downstream (`piecesOfParagraph`).
834
- */
835
- readonly fontFamilyEastAsia: string | null;
836
- /** Points. `w:sz` is half-points, so 22 becomes 11. */
837
- readonly fontSizePt: number;
838
- /** RRGGBB, or null for the inherited/automatic colour. */
839
- readonly color: string | null;
840
- readonly bold: boolean;
841
- readonly italic: boolean;
842
- readonly underline: ResolvedUnderline | null;
843
- readonly strike: boolean;
844
- readonly doubleStrike: boolean;
845
- /** An `ST_HighlightColor` name, or null. */
846
- readonly highlight: string | null;
847
- /**
848
- * Character shading fill (`w:rPr/w:shd`), validated RRGGBB, or null.
849
- *
850
- * Paint applies this as the glyph-box background; a recognised highlight overrides it.
851
- * Measurement ignores shading.
852
- */
853
- readonly shading: string | null;
854
- readonly verticalAlign: VerticalAlign;
855
- /** `w:position`, in points. Positive raises the baseline. */
856
- readonly baselineShiftPt: number;
857
- readonly caps: boolean;
858
- readonly smallCaps: boolean;
859
- /** `w:spacing`, in points. Added to every advance. */
860
- readonly characterSpacingPt: number;
861
- /** `w:w`, as a percentage. 100 is unscaled. */
862
- readonly horizontalScalePercent: number;
863
- /** `w:kern`, in points: the size at or above which kerning applies. 0 disables it. */
864
- readonly kerningMinPt: number;
865
- /**
866
- * `w:vanish` (ECMA-376 §17.3.2.45): the run is hidden text.
867
- *
868
- * Word does not draw it AND does not paginate it — hidden index or comment text takes no
869
- * space at all. So this cannot be a paint-time opacity: a hidden run that is still measured
870
- * pushes every following line, and every following page break, to the wrong place. Layout
871
- * drops the content instead (see `piecesOfParagraph`).
872
- *
873
- * `w:specVanish` (§17.3.2.36) is a different property — an always-hidden paragraph mark on
874
- * a heading — and never sets this.
875
- */
876
- readonly hidden: boolean;
877
- }
878
- /** The style a run inherits when it authors nothing. */
879
- declare const DEFAULT_RUN_STYLE: ResolvedRunStyle;
880
- /**
881
- * The theme part's typefaces, for resolving `w:rFonts` theme attributes.
882
- *
883
- * Structurally identical to the binding lane's `DocumentThemeFonts` and assignable from it.
884
- * Declared here so this lane reads validated strings rather than the theme tree.
885
- */
886
- interface ThemeFonts {
887
- /** `a:majorFont` latin typeface — headings. */
888
- readonly major: string | null;
889
- /** `a:minorFont` latin typeface — body text. */
890
- readonly minor: string | null;
891
- /** `a:majorFont` east asian typeface (`a:ea`) — headings. Optional for back-compat. */
892
- readonly majorEastAsia?: string | null;
893
- /** `a:minorFont` east asian typeface (`a:ea`) — body text. Optional for back-compat. */
894
- readonly minorEastAsia?: string | null;
895
- }
896
- /**
897
- * Resolve one run's direct formatting.
898
- *
899
- * Unrecognised values are DROPPED rather than guessed: a `w:sz` of `"large"` leaves the
900
- * default size rather than inventing one, because a wrong measurement moves every glyph
901
- * after it and a missing one is visible immediately.
902
- *
903
- * `themeFonts` resolves `w:rFonts` theme references. Absent, a theme-only `rFonts` leaves
904
- * the family inherited — which is what every run of a theme-fonted document does, so the
905
- * whole document falls back to the surface default face.
906
- */
907
- declare function resolveRunStyle(props: readonly OoxmlProperty[], themeFonts?: ThemeFonts): ResolvedRunStyle;
908
- /** The text as it is DRAWN, after case transforms. Measurement must use this, not the source. */
909
- declare function displayText(text: string, style: ResolvedRunStyle): string;
910
- /** Measure run text the way layout breaks lines and paints glyphs (caps/small-caps aware). */
911
- declare function measureDisplayText(text: string, style: ResolvedRunStyle, measurer: TextMeasurer): number;
912
- /** Whether two resolved styles are identical, for span merging and cache keys. */
913
- declare function runStylesEqual(a: ResolvedRunStyle, b: ResolvedRunStyle): boolean;
914
- /**
915
- * How far a run's glyphs are lifted off the line's baseline, in points. Positive is up.
916
- *
917
- * Super and subscript move the GLYPHS without moving the run's box, so the box keeps tiling
918
- * the line and the selection band stays continuous. Anything drawing at the glyphs — the
919
- * painter, and the caret — has to apply this itself, and from one place, or the two drift.
920
- */
921
- declare function baselineShiftPtOf(style: ResolvedRunStyle): number;
922
-
923
- /**
924
- * Which `w:rFonts` slot a character resolves its face through.
925
- *
926
- * OOXML gives a run up to four faces and picks between them by SCRIPT, so one run of mixed Latin
927
- * and CJK text uses two different fonts without saying so anywhere in its properties.
928
- */
929
- type FontSlot = 'ascii' | 'hAnsi' | 'eastAsia' | 'cs';
930
- /**
931
- * A run of text sharing one script, direction and font slot — the unit handed to the shaper.
932
- *
933
- * Shaping cannot span a script change: Arabic and Latin in one call would produce wrong joining
934
- * behaviour, so a run is itemized into these first.
935
- */
936
- interface ScriptItem {
937
- readonly from: number;
938
- readonly to: number;
939
- readonly direction: TextDirection;
940
- readonly bidiLevel: number;
941
- readonly script: 'Zyyy' | 'Latn' | 'Grek' | 'Cyrl' | 'Hani' | 'Hebr' | 'Arab' | 'Deva' | 'Beng' | 'Thai' | 'Khmr';
942
- readonly slot: FontSlot;
943
- }
944
- /**
945
- * A code point whose script this engine does not itemize.
946
- *
947
- * Carries the offending `codePoint` so the caller can report which character stopped it, rather
948
- * than failing anonymously somewhere in the middle of a paragraph.
862
+ * Carries the offending `codePoint` so the caller can report which character stopped it, rather
863
+ * than failing anonymously somewhere in the middle of a paragraph.
949
864
  */
950
865
  declare class UnsupportedScriptError extends Error {
951
866
  readonly name = "UnsupportedScriptError";
@@ -972,19 +887,19 @@ declare function itemizeScriptFontSlots(text: string, paragraphOffset: number, e
972
887
  */
973
888
  declare function styleForFontSlot(style: ResolvedRunStyle, slot: FontSlot | undefined): ResolvedRunStyle;
974
889
 
975
- type AllowlistedPageField = 'PAGE' | 'NUMPAGES' | 'SECTIONPAGES';
976
890
  /**
977
- * Which allowlisted page fields a header/footer story actually contains.
891
+ * One parsed `HYPERLINK` instruction: the raw, unsanitized pieces.
978
892
  *
979
- * Drives layout reuse: no fields → one baseline; NUMPAGES only → one layout per page count;
980
- * SECTIONPAGES only → one layout per section page count; PAGE (alone or combined) → per
981
- * distinct evaluated values with a bounded cache. Counts both complex markers and
982
- * `w:fldSimple` (including an allowlisted page field nested inside a non-page simple field).
893
+ * `target` and `anchor` are verbatim from the instruction — the projector at the trust
894
+ * boundary decides what, if anything, they become in a DOM sink.
983
895
  */
984
- interface StoryPageFieldNeeds {
985
- readonly hasPage: boolean;
986
- readonly hasNumPages: boolean;
987
- readonly hasSectionPages: boolean;
896
+ interface HyperlinkFieldSpec {
897
+ /** The target as authored (first quoted or bare non-switch token), or null. */
898
+ readonly target: string | null;
899
+ /** `\l` — a bookmark name in this document, or null. */
900
+ readonly anchor: string | null;
901
+ /** `\o` — the hover tooltip, or null. */
902
+ readonly tooltip: string | null;
988
903
  }
989
904
 
990
905
  /** One recognized REF instruction: the target name and the supported switches, nothing else. */
@@ -1007,380 +922,434 @@ interface RefFieldSpec {
1007
922
  */
1008
923
  declare function parseRefInstruction(raw: string): RefFieldSpec | null;
1009
924
 
1010
- /** Soft ceiling on abstractNum / num entries read from one part. */
1011
- declare const MAX_NUMBERING_DEFINITIONS = 512;
1012
- /** Soft ceiling on override entries per `w:num`. */
1013
- declare const MAX_LVL_OVERRIDES = 9;
1014
- /** `w:suff` — what separates a list marker from the text after it. */
1015
- type ListSuffix = 'tab' | 'space' | 'nothing';
1016
- /** `w:lvlJc` — how a list marker aligns within its own indent. */
1017
- type ListMarkerAlign = 'left' | 'center' | 'right';
1018
925
  /**
1019
- * One level's indent, plus which parts the level actually AUTHORED.
926
+ * The story's resolved REF inputs for one layout pass.
1020
927
  *
1021
- * The provenance matters: `w:ind` cascades per-attribute, so a level that authored only `left`
1022
- * must not overwrite an inherited `hanging` with a synthesized zero.
928
+ * Threaded as a runtime rider on the layout options (see `layoutSemanticDocument`) rather
929
+ * than a `SemanticLayoutOptions` member, so the public options surface stays put. Absent
930
+ * means every REF field paints its cached result — the pre-existing degradation, and the one
931
+ * header/footer and text-box stories still take. Note stories receive the body's context
932
+ * through `NotesLayoutInput.refFields`.
1023
933
  */
1024
- interface NumberingLevelIndent {
1025
- readonly left: number;
1026
- readonly right: number;
1027
- readonly hanging: number;
1028
- readonly firstLine: number;
934
+ interface RefFieldContext {
1029
935
  /**
1030
- * Which of these the LEVEL actually authored.
1031
- *
1032
- * A level's `w:pPr/w:ind` sits between the paragraph style and direct formatting, so the
1033
- * merge has to tell "the level says left = 0" from "the level says nothing about left and
1034
- * the style's value stands". Absent (the default) reads as "says nothing", which is what a
1035
- * hand-built level in a test means.
936
+ * Content token over each paragraph ID and its PAINTED REF outputs in the story, for the
937
+ * section prepass memo. IDs keep structural edits from moving an identical sequence of
938
+ * outputs between paragraphs while the aggregate falsely reports unchanged dependencies.
939
+ * A renumbering edit can move a REF value in a section whose own blocks and list map are
940
+ * identity-unchanged, and this token is the only validator that sees it. A field that
941
+ * failed calibration contributes its (session-constant) cached text, so the token still
942
+ * moves when painted output or its owning paragraph changes.
1036
943
  */
1037
- readonly stated?: {
1038
- readonly left: boolean;
1039
- readonly right: boolean;
1040
- /** `w:hanging`/`w:firstLine` are one mutually exclusive slot (§17.3.1.10, §17.3.1.12). */
1041
- readonly firstLineOffset: boolean;
1042
- };
1043
- }
1044
- /** One `w:lvl`: how this depth numbers, what its marker looks like, and how it indents. */
1045
- interface NumberingLevel {
1046
- readonly ilvl: number;
1047
- readonly start: number;
1048
- readonly numFmt: string;
1049
- readonly lvlText: string;
1050
- readonly lvlJc: ListMarkerAlign;
1051
- readonly suff: ListSuffix;
1052
- readonly indent: NumberingLevelIndent;
944
+ readonly valuesToken: string;
945
+ /** The paragraph's REF outputs folded for its block cache key; `''` when it holds none. */
946
+ tokenForParagraph(paragraphId: string): string;
1053
947
  /**
1054
- * `w:lvlRestart` one-based trigger level, or `0` when the level never restarts.
1055
- * Omitted in XML → restart when the previous level (or any earlier level) is used.
948
+ * The live value ONE field paints, keyed by its begin / `w:fldSimple` node id, or null to
949
+ * keep the cached result (failed calibration, unresolvable, or an anchor this scan never
950
+ * saw). Anchor-keyed so the projection's paint and this context's token fold read the SAME
951
+ * calibration verdict, however each walk collected the field's cached text.
1056
952
  */
1057
- readonly lvlRestart?: number;
953
+ liveValueOf(anchorId: string, spec: RefFieldSpec): string | null;
1058
954
  /**
1059
- * `w:isLgl` (§17.9.9): render EVERY level referenced by this level's `w:lvlText` in
1060
- * decimal, whatever number format those levels declare for themselves.
955
+ * The synthesized display of ONE AUTONUM-family field, keyed by its begin / `w:fldSimple`
956
+ * node id, or null to paint nothing (unsupported switches, or an anchor this scan never
957
+ * saw — both the field's historical rendering). These fields carry no cached result at
958
+ * all, so there is no calibration: the sequential value is the only display they have.
959
+ * Optional so hand-built contexts predating it stay valid.
1061
960
  */
1062
- readonly isLgl: boolean;
1063
- /** Level `w:rPr` as flat properties (for marker face / vanish). */
1064
- readonly runProperties: readonly OoxmlProperty[];
1065
- /** True when level run props request vanish — marker must not paint. */
1066
- readonly vanish: boolean;
961
+ autonumValueOf?(anchorId: string): string | null;
962
+ /**
963
+ * The deferred projection of ONE `PAGEREF` field, or null to keep the cached result
964
+ * (missing bookmark, unsupported switches, or an anchor this scan never saw).
965
+ *
966
+ * A `PAGEREF` value is a property of pagination, so the scan cannot compute it — it hands
967
+ * the projection the resolved TARGET and the calibration inputs instead, and document
968
+ * finalize (`finalizePageFieldProjection`) substitutes the page number the target's first
969
+ * fragment lands on. Optional so hand-built contexts predating it stay valid.
970
+ */
971
+ pageRefProjectionOf?(anchorId: string, spec: RefFieldSpec): PageRefFieldProjection | null;
1067
972
  }
1068
973
  /**
1069
- * One `w:lvlOverride` on a `w:num`: a restart value, a replacement level, or both.
974
+ * Sticky calibration identity for one `PAGEREF` field.
1070
975
  *
1071
- * How two lists share an abstract definition while numbering independently.
976
+ * An opaque frozen object rather than a mutable verdict holder because span markers are
977
+ * serialized into fragment signatures — a verdict written into the marker would move a
978
+ * signature that no painted output moved. The verdict itself lives beside the finalize pass
979
+ * (`field-page-furniture.ts`), keyed weakly on this object; the registry below carries the
980
+ * object across passes the same way REF verdicts are carried.
1072
981
  */
1073
- interface LevelOverride {
1074
- readonly startOverride?: number;
1075
- /** Full level replacement when `w:lvl` is present under the override. */
1076
- readonly level?: NumberingLevel;
982
+ type PageRefCalibrationCell = Readonly<Record<never, never>>;
983
+ /**
984
+ * What a `PAGEREF` span carries to document finalize: the resolved target, the normalized
985
+ * authored cache (the calibration oracle), and the sticky calibration identity.
986
+ */
987
+ interface PageRefFieldProjection {
988
+ /** Canonical id of the paragraph the bookmark names — first declaration wins. */
989
+ readonly targetParagraphId: string;
990
+ /** The authored cached result, whitespace-collapsed — what the computed number must reproduce. */
991
+ readonly cached: string;
992
+ readonly calibration: PageRefCalibrationCell;
1077
993
  }
1078
994
  /**
1079
- * One `w:abstractNum` — the shape of a list, without being a list.
1080
- *
1081
- * Never referenced by a paragraph directly. Paragraphs name a {@link NumDefinition}, which names
1082
- * one of these, so several lists can share a definition and still count separately.
995
+ * Note parts whose stories join the context: their REF fields resolve against the body's
996
+ * bookmarks and numbering (a footnote citing "Section 1.2(c)" targets a body paragraph),
997
+ * and their bookmark declarations become plain-REF targets. Number switches aimed AT a
998
+ * note paragraph still fall back to the cached result — the list map is the body's.
1083
999
  */
1084
- interface AbstractNumDefinition {
1085
- readonly abstractNumId: string;
1086
- readonly levels: ReadonlyMap<number, NumberingLevel>;
1087
- /**
1088
- * `w:numStyleLink` (§17.9.21): this definition carries no levels of its own — the numbering
1089
- * lives on the named style. Word follows the link; resolve it with
1090
- * {@link resolveNumberingStyleLinks} before reading levels, or every paragraph on this
1091
- * abstract renders with NO marker at all.
1092
- */
1093
- readonly numStyleLink?: string;
1094
- /**
1095
- * `w:styleLink` (§17.9.23): the definition side of the same pair — this abstract IS the
1096
- * numbering of the named style. Kept so a link can be verified rather than assumed.
1097
- */
1098
- readonly styleLink?: string;
1000
+ interface RefNoteParts {
1001
+ readonly footnotesPart: OoxmlPart | null;
1002
+ readonly endnotesPart: OoxmlPart | null;
1099
1003
  }
1100
1004
  /**
1101
- * One `w:num` — the thing a paragraph's `w:numId` actually names.
1005
+ * Resolve the document's REF fields for one layout pass, or null when it has none.
1102
1006
  *
1103
- * Points at an {@link AbstractNumDefinition} and may override any of its levels.
1007
+ * Bookmarks and REF fields resolve across the body story and (when `notes` is given) the
1008
+ * footnote/endnote stories, against ONE shared target index — a footnote REF finds the
1009
+ * body bookmark it cites. Header/footer and text-box stories are not given a context and
1010
+ * keep painting cached results.
1104
1011
  */
1105
- interface NumDefinition {
1106
- readonly numId: string;
1107
- readonly abstractNumId: string;
1108
- readonly overrides: ReadonlyMap<number, LevelOverride>;
1109
- }
1012
+ declare function resolveStoryRefFields(blocks: readonly OoxmlElement[], listItems: ReadonlyMap<string, ResolvedListItem> | undefined, notes?: RefNoteParts): RefFieldContext | null;
1013
+
1014
+ type AllowlistedPageField = 'PAGE' | 'NUMPAGES' | 'SECTIONPAGES';
1110
1015
  /**
1111
- * The bounded projection of `numbering.xml` that list layout resolves against.
1016
+ * Which allowlisted page fields a header/footer story actually contains.
1112
1017
  *
1113
- * Projection ONLY — never a mutation or serialization authority. Hostile values are dropped or
1114
- * clamped, and a missing definition resolves to "no list" rather than a guess.
1018
+ * Drives layout reuse: no fields → one baseline; NUMPAGES only → one layout per page count;
1019
+ * SECTIONPAGES only → one layout per section page count; PAGE (alone or combined) → per
1020
+ * distinct evaluated values with a bounded cache. Counts both complex markers and
1021
+ * `w:fldSimple` (including an allowlisted page field nested inside a non-page simple field).
1115
1022
  */
1116
- interface NumberingIndex {
1117
- readonly abstractNums: ReadonlyMap<string, AbstractNumDefinition>;
1118
- readonly nums: ReadonlyMap<string, NumDefinition>;
1023
+ interface StoryPageFieldNeeds {
1024
+ readonly hasPage: boolean;
1025
+ readonly hasNumPages: boolean;
1026
+ readonly hasSectionPages: boolean;
1119
1027
  }
1028
+
1120
1029
  /**
1121
- * Build a numbering index from the root of a numbering part (`w:numbering`).
1030
+ * What a piece says about the FIELD result it came from, for Word's shading.
1122
1031
  *
1123
- * Empty / missing roots yield an empty index. Duplicate ids keep the first definition.
1032
+ * Carried from layout rather than decided at paint time because only the walk knows an atom was
1033
+ * a field at all — by paint the result is just text. Whether the shading is actually drawn is a
1034
+ * view decision made downstream, so this states the fact and nothing about the appearance.
1124
1035
  */
1036
+ interface FieldAtomMarker {
1037
+ /**
1038
+ * A legacy form field: `w:fldChar/w:ffData` (FORMTEXT, FORMCHECKBOX, FORMDROPDOWN).
1039
+ *
1040
+ * Word shades these on a different rule from ordinary fields — always, unless the document
1041
+ * turns it off — because they mark the blanks somebody is meant to fill in.
1042
+ */
1043
+ readonly formField: boolean;
1044
+ /**
1045
+ * A BODY PAGE / NUMPAGES / SECTIONPAGES atom whose value depends on pagination.
1046
+ *
1047
+ * The paragraph walk cannot know which page the field lands on — layout runs before the page
1048
+ * count — so it paints a placeholder and records the field's kind here. Document finalize
1049
+ * (`substituteBodyPageFields`) reads this marker and substitutes the real value per page.
1050
+ * Absent in headers/footers, which evaluate live through their own per-page projector.
1051
+ */
1052
+ readonly pageField?: {
1053
+ readonly kind: AllowlistedPageField;
1054
+ /** The field's `\#` numeric picture, applied when finalize substitutes the value. */
1055
+ readonly picture?: string;
1056
+ };
1057
+ /**
1058
+ * A BODY `PAGEREF` atom whose value is the page number its bookmark target lands on.
1059
+ *
1060
+ * Deferred exactly like {@link pageField}: the paragraph walk paints the field's cached
1061
+ * result (or the placeholder digit when the file cached none) and records the resolved
1062
+ * target here; document finalize substitutes the number of the page hosting the target's
1063
+ * first fragment, gated per field on the calibration verdict.
1064
+ */
1065
+ readonly pageRef?: PageRefFieldProjection;
1066
+ }
1067
+ /** A half-open model-offset range, in the paragraph's own UTF-16 offset space. */
1068
+ interface ModelRange {
1069
+ readonly start: number;
1070
+ readonly end: number;
1071
+ }
1125
1072
  /**
1126
- * Project `numbering.xml` into the bounded index.
1073
+ * How layout turns a typed `w:hyperlink` node into the sanitized record spans carry.
1127
1074
  *
1128
- * Every ceiling here exists because the input is file-derived: definition counts, override
1129
- * counts, indent magnitudes and style-link hop depth are all capped, and nothing from the file
1130
- * becomes a loop bound or an allocation size.
1075
+ * Injected rather than computed here because resolving `r:id` needs the PACKAGE's
1076
+ * relationships and this module only ever sees one part's tree. `null` means the caller
1077
+ * declined to project — the runs still measure and paint, they simply carry no link, which is
1078
+ * the right degradation: text is never lost for want of a target.
1131
1079
  */
1132
- declare function buildNumberingIndex(root: OoxmlElement | null | undefined): NumberingIndex;
1133
- /** Resolve the effective level for a `numId` + `ilvl`, applying overrides. */
1080
+ type HyperlinkProjector = (link: OoxmlNode) => SpanLinkRecord | null;
1134
1081
  /**
1135
- * The effective level for one paragraph's numbering reference, after overrides and style links.
1082
+ * How layout turns a parsed HYPERLINK field instruction into the sanitized record spans carry.
1136
1083
  *
1137
- * Answers null for a reference the index cannot resolve — a paragraph naming a definition the
1138
- * file never declared is an unnumbered paragraph, not an error.
1084
+ * Injected for the same reason as {@link HyperlinkProjector}: the spec's raw target must cross
1085
+ * the surface's ONE href trust boundary, and layout owns no sanitization policy. `null` means
1086
+ * no link — the cached result still paints as plain text, which is the right degradation.
1139
1087
  */
1140
- declare function resolveNumberingLevel(index: NumberingIndex, numId: string, ilvl: number): {
1141
- readonly abstractNumId: string;
1142
- readonly level: NumberingLevel;
1143
- readonly startOverride?: number;
1144
- } | null;
1145
- /** Empty index for tests / documents without numbering. */
1146
- declare const EMPTY_NUMBERING_INDEX: NumberingIndex;
1088
+ type FieldLinkProjector = (spec: HyperlinkFieldSpec) => SpanLinkRecord | null;
1147
1089
 
1148
- interface DrawingPoint {
1149
- readonly x: number;
1150
- readonly y: number;
1151
- }
1152
- interface DrawingInsets {
1153
- readonly top: number;
1154
- readonly right: number;
1155
- readonly bottom: number;
1156
- readonly left: number;
1090
+ /** Document-wide East Asian line-break and whitespace policy. @public */
1091
+ interface CjkTypographySettings {
1092
+ /** Apply the strict Japanese small-kana and prolonged-sound-mark restrictions. */
1093
+ readonly strict: boolean;
1094
+ /** Whitespace compression selected by `w:characterSpacingControl`. */
1095
+ readonly compression: 'doNotCompress' | 'compressPunctuation' | 'compressPunctuationAndJapaneseKana';
1096
+ /** Custom no-line-start characters, keyed by normalized language tag. */
1097
+ readonly before: Readonly<Record<string, string>>;
1098
+ /** Custom no-line-end characters, keyed by normalized language tag. */
1099
+ readonly after: Readonly<Record<string, string>>;
1157
1100
  }
1158
- type DrawingClipFallback = 'none' | 'unsupported-preset';
1159
- interface DrawingGeometry {
1160
- readonly contentBounds: LayoutBox;
1161
- readonly paintBounds: LayoutBox;
1162
- readonly hitBounds: LayoutBox;
1163
- readonly transformedCorners: readonly DrawingPoint[];
1164
- readonly clipPolygon: readonly DrawingPoint[] | null;
1165
- readonly clipFallback: DrawingClipFallback;
1166
- readonly effectInsets: DrawingInsets;
1167
- }
1168
-
1169
- /** Horizontal alignment of a paragraph (`w:jc`, ECMA-376 §17.3.1.13). */
1170
- type Alignment = 'left' | 'center' | 'right' | 'both';
1171
1101
 
1172
- interface StyleDefinition {
1173
- readonly styleId: string;
1174
- readonly type: string;
1175
- readonly basedOn: string | null;
1176
- /** `w:next` — the authoring style for a following paragraph. */
1177
- readonly next: string | null;
1178
- readonly outlineLevel: number | null;
1179
- readonly paragraphProperties: readonly OoxmlProperty[];
1180
- readonly runProperties: readonly OoxmlProperty[];
1181
- readonly paragraphPropertiesNode: OoxmlElement | undefined;
1182
- readonly tablePropertiesNode: OoxmlElement | undefined;
1183
- readonly tableRowPropertiesNode: OoxmlElement | undefined;
1184
- readonly conditionalTableFormats: ReadonlyMap<string, OoxmlElement>;
1102
+ /**
1103
+ * Whether a paragraph must start a new page (`w:pageBreakBefore`).
1104
+ *
1105
+ * LAST WINS, like every other toggle read off the cascade (`paragraphKeeps` does the same
1106
+ * for `w:keepNext`). An any-wins `.some()` cannot be switched off: a Chapter or Heading style
1107
+ * carries `w:pageBreakBefore`, and Word writes `w:val="0"` on the one instance whose author
1108
+ * unchecked the box — that paragraph still broke, so the document grew a blank page and every
1109
+ * page number after it was wrong.
1110
+ *
1111
+ * An absent `w:val` is on (§17.17.4), and the off vocabulary is the whole of it — `off` is a
1112
+ * spelling too, which {@link isOn} beside this already accepts.
1113
+ */
1114
+ declare function paragraphBreaksBefore(props: readonly OoxmlProperty[]): boolean;
1115
+ /**
1116
+ * Soft ceiling matching the spike's resolved-style limit (31_680 twips ≈ 22"). Beyond
1117
+ * that an attacker-authored spacing would push pagination into pathological page counts.
1118
+ */
1119
+ declare const MAX_PARAGRAPH_SPACING_PT: number;
1120
+ /** Soft ceiling on border width (96 eighths = 12pt). Word's UI tops out well below this. */
1121
+ declare const MAX_BORDER_WIDTH_PT = 12;
1122
+ /** Soft ceiling on border-to-text gap (`w:space`, already in points). */
1123
+ declare const MAX_BORDER_SPACE_PT = 3168;
1124
+ /**
1125
+ * A paragraph's resolved space before and after, in points.
1126
+ *
1127
+ * Already collapsed against `w:contextualSpacing`, so adjacent same-style paragraphs that suppress
1128
+ * their gap arrive here with it removed rather than leaving that to whoever stacks them.
1129
+ */
1130
+ interface ParagraphSpacing {
1131
+ /** `w:spacing/@before`, in points. */
1132
+ readonly before: number;
1133
+ /** `w:spacing/@after`, in points. */
1134
+ readonly after: number;
1185
1135
  }
1186
- declare function isValidStyleId(raw: string | undefined): raw is string;
1187
-
1188
- /** Soft ceiling on `basedOn` chain length — enough for real templates, refuses hostile graphs. */
1189
- declare const MAX_STYLE_BASED_ON_DEPTH = 32;
1190
- /** Soft ceiling on style definitions read from one styles part. */
1191
- declare const MAX_STYLE_DEFINITIONS = 4096;
1192
1136
  /**
1193
- * The whole styles part, indexed and ready to resolve against.
1137
+ * The gap Word substitutes when `w:beforeAutospacing` / `w:afterAutospacing` is on
1138
+ * (ECMA-376 §17.3.1.33, the `w:spacing` clause both attributes belong to).
1194
1139
  *
1195
- * `cacheToken` is load-bearing: it folds into layout cache producers so breaks measured under one
1196
- * styles part are never reused under another.
1140
+ * The attribute means "the consumer decides", and the authored `@before` / `@after` beside it
1141
+ * is IGNORED rather than used as the value. Word's answer is HTML's default `<p>` margin,
1142
+ * 14pt, which is what a document round-tripped through Word's HTML filter carries — and this
1143
+ * one is everywhere, because Word writes `w:before="100" w:beforeAutospacing="1"` for it. Reading
1144
+ * only the literal 100 twips lays every such paragraph out 9pt tight, which moves page breaks.
1197
1145
  */
1198
- interface StyleCascadeTable {
1199
- /**
1200
- * Bounded fingerprint folded into layout cache producers so a different styles part cannot
1201
- * reuse breaks measured under another cascade. Computed once per table (FNV-1a hex).
1202
- */
1203
- readonly cacheToken: string;
1204
- readonly docDefaultsRun: readonly OoxmlProperty[];
1205
- readonly docDefaultsParagraph: readonly OoxmlProperty[];
1206
- readonly docDefaultsParagraphNode: OoxmlElement | undefined;
1207
- /** `w:style[@w:default='1'][@w:type='paragraph']` — last wins among defaults of that type. */
1208
- readonly defaultParagraphStyleId: string | null;
1209
- /** `w:style[@w:default='1'][@w:type='character']` — last wins among defaults of that type. */
1210
- readonly defaultCharacterStyleId: string | null;
1211
- /**
1212
- * `w:style[@w:default='1'][@w:type='table']` — last wins among defaults of that type.
1213
- *
1214
- * A `w:tbl` with no `w:tblStyle` still resolves against this one. Word's own
1215
- * `TableNormal` is where the 0/108/0/108 twip cell margins live, so skipping it gives
1216
- * every unstyled table padding the document says it does not have.
1217
- */
1218
- readonly defaultTableStyleId: string | null;
1219
- /**
1220
- * The theme part's typefaces, for `w:rFonts` theme references.
1221
- *
1222
- * Lives on the cascade because it is document-level style material with the same
1223
- * lifetime, and because every site that resolves run properties already holds this table.
1224
- */
1225
- readonly themeFonts: ThemeFonts;
1226
- readonly styles: ReadonlyMap<string, StyleDefinition>;
1146
+ declare const AUTO_PARAGRAPH_SPACING_PT = 14;
1147
+ /**
1148
+ * Where a paragraph sits, for the two contexts in which Word's auto spacing resolves to 0
1149
+ * instead of {@link AUTO_PARAGRAPH_SPACING_PT}.
1150
+ *
1151
+ * Both come from the HTML model the attribute emulates: a `<li>` and a `<td>` collapse the
1152
+ * paragraph margin, a bare `<p>` does not. A caller that says nothing gets the body answer.
1153
+ */
1154
+ interface ParagraphAutoSpacingContext {
1155
+ /** The paragraph participates in numbering (`w:numPr`), i.e. it is a list item. */
1156
+ readonly inList?: boolean;
1157
+ /** The paragraph lives in a table cell. */
1158
+ readonly inTableCell?: boolean;
1227
1159
  }
1228
1160
  /**
1229
- * A paragraph's properties after the cascade, plus the same list WITHOUT its own `w:pPr`.
1161
+ * Resolved line spacing (`w:spacing/@line` + `@lineRule`, ECMA-376 17.3.1.33).
1230
1162
  *
1231
- * Both, because a writer needs to know what a paragraph INHERITS to decide whether setting a
1232
- * value is a change or a no-op — and writing back an inherited value freezes it into the
1233
- * paragraph as though the author had chosen it.
1163
+ * `auto` is the interesting one: `@line` is 240ths of a line, so 240 is single, 360 is
1164
+ * one-and-a-half, 480 is double — and Word's own Normal style since 2013 is 259, i.e.
1165
+ * 1.08. A document laid out at a flat single spacing is ~8% tight on EVERY line, which
1166
+ * moves every page break, so this is not a cosmetic detail.
1167
+ *
1168
+ * `exact` fixes the line box at `@line` twips and lets tall glyphs clip, the way Word
1169
+ * does. `atLeast` uses it as a floor.
1234
1170
  */
1235
- interface CascadedParagraphFormatting {
1236
- /** Flat paragraph properties in cascade order (defaults → bases → style → direct). */
1237
- readonly paragraphProperties: readonly OoxmlProperty[];
1238
- /**
1239
- * The same list WITHOUT the paragraph's own `w:pPr` — everything it inherits.
1240
- *
1241
- * Numbering needs the two tiers apart: a level's `w:pPr/w:ind` outranks the style's and is
1242
- * outranked by the paragraph's own, and a flattened list cannot say which is which.
1243
- */
1244
- readonly inheritedParagraphProperties: readonly OoxmlProperty[];
1245
- /** Matching `w:pPr` nodes for nested border resolution. */
1246
- readonly paragraphPropertyNodes: readonly OoxmlNode[];
1171
+ type LineSpacingRule = 'auto' | 'exact' | 'atLeast';
1172
+ /**
1173
+ * Resolved line spacing: the rule, and the value it applies.
1174
+ *
1175
+ * `value` means different things per rule — 240ths of a line under `auto`, points under `exact`
1176
+ * and `atLeast` — which is why the two travel together and neither is useful alone.
1177
+ */
1178
+ interface ParagraphLineSpacing {
1179
+ readonly rule: LineSpacingRule;
1180
+ /** `auto`: the 240ths-of-a-line multiplier numerator. Otherwise points. */
1181
+ readonly value: number;
1182
+ }
1183
+ /** Single spacing: what a paragraph that says nothing gets. */
1184
+ declare const SINGLE_LINE_SPACING: ParagraphLineSpacing;
1185
+ /**
1186
+ * One resolved `w:pBdr` edge: its style, colour, thickness and gap.
1187
+ *
1188
+ * `widthPt` and `spacePt` are already converted and CLAMPED — both come from a file, and an
1189
+ * unbounded border width becomes a layout dimension.
1190
+ */
1191
+ interface ParagraphBorderEdge {
1192
+ /** Authored `ST_Border` value (`single`, `dashed`, …). */
1193
+ readonly val: string;
1194
+ /** RRGGBB, or null when auto/missing (paint defaults to black). */
1195
+ readonly color: string | null;
1196
+ /** Border thickness in points (`w:sz` is eighths of a point). */
1197
+ readonly widthPt: number;
1198
+ /** Gap from text to the rule, in points (`w:space`). */
1199
+ readonly spacePt: number;
1247
1200
  /**
1248
- * Inherited run properties for CONTENT runs (before direct run `rPr`).
1201
+ * `w:shadow` — Word offsets a drop shadow behind the rule.
1249
1202
  *
1250
- * Does NOT include direct `w:pPr/w:rPr` — that formats the paragraph MARK only
1251
- * (ECMA-376 §17.3.1.36). Folding mark `w:sz` into content made BodyText runs with no
1252
- * direct size paint at the mark's size (Selection Notice "or" alternative → 6.5pt).
1253
- */
1254
- readonly runProperties: readonly OoxmlProperty[];
1255
- /**
1256
- * Content cascade plus direct `w:pPr/w:rPr` — empty-line metrics and last-line mark height.
1257
- */
1258
- readonly markRunProperties: readonly OoxmlProperty[];
1259
- /**
1260
- * The style this paragraph resolved to, or null when it names none and there is no
1261
- * document default. `w:contextualSpacing` compares neighbours by exactly this.
1203
+ * Present only when authored true, so an edge that says nothing keeps the shape earlier
1204
+ * fixtures assert. Resolved and carried; drawing it is deferred.
1262
1205
  */
1263
- readonly styleId: string | null;
1206
+ readonly shadow?: true;
1264
1207
  }
1208
+ /** The six `CT_PBdr` children, in schema order (ECMA-376 §17.3.1.24). */
1209
+ declare const PARAGRAPH_BORDER_SIDES: readonly ["top", "left", "bottom", "right", "between", "bar"];
1265
1210
  /**
1266
- * What a table style contributes to the paragraphs of ONE cell: the style's whole-table
1267
- * `w:pPr`/`w:rPr` followed by every `w:tblStylePr` the cell is under (17.7.6.6), weakest
1268
- * first in the caller's condition order (banding, column, row, corner).
1211
+ * Which of the six `CT_PBdr` edges.
1269
1212
  *
1270
- * This is how Word makes a header row bold and centred while the document states nothing
1271
- * but `<w:tblStyle w:val="…"/>` on the table and plain runs in the cells.
1213
+ * Four are physical box edges; `between` and `bar` are group-relative, drawn only where
1214
+ * consecutive paragraphs share a border definition.
1272
1215
  */
1273
- interface TableCellStyleFormatting {
1274
- readonly paragraphProperties: readonly OoxmlProperty[];
1275
- /** Matching `w:pPr` nodes, for nested `w:pBdr` / `w:tabs` resolution. */
1276
- readonly paragraphPropertyNodes: readonly OoxmlElement[];
1277
- /** Inherited run properties for every run in the cell, before the paragraph style. */
1278
- readonly runProperties: readonly OoxmlProperty[];
1216
+ type ParagraphBorderSide = (typeof PARAGRAPH_BORDER_SIDES)[number];
1217
+ /**
1218
+ * A paragraph's resolved `w:pBdr` (ECMA-376 §17.3.1.24).
1219
+ *
1220
+ * `top`/`left`/`bottom`/`right` are the four physical edges of the box. The other two are
1221
+ * group-relative: `between` draws at a boundary INSIDE a run of consecutive paragraphs whose
1222
+ * border settings are identical, and `bar` is the vertical change-bar rule beside the
1223
+ * paragraph, drawn whether or not the paragraph groups with its neighbours.
1224
+ */
1225
+ interface ParagraphBorders {
1226
+ readonly top?: ParagraphBorderEdge;
1227
+ readonly left?: ParagraphBorderEdge;
1228
+ readonly bottom?: ParagraphBorderEdge;
1229
+ readonly right?: ParagraphBorderEdge;
1230
+ readonly between?: ParagraphBorderEdge;
1231
+ readonly bar?: ParagraphBorderEdge;
1279
1232
  }
1280
1233
  /**
1281
- * Build a cascade table from a styles part root.
1234
+ * Resolve `w:spacing` before/after from flat paragraph properties.
1282
1235
  *
1283
- * Only direct `w:style` children of the root participate (bounded count). Duplicate
1284
- * `styleId` values keep the last definition, matching Word's reader for this fixture class.
1285
- * Default paragraph/character style ids track `w:default="1"` with the same last-wins rule.
1236
+ * Line spacing (`w:line` / `w:lineRule`) is a separate concern — it changes measured line
1237
+ * height, not the gap between paragraphs — and is not resolved here.
1238
+ *
1239
+ * `w:beforeAutospacing` / `w:afterAutospacing` REPLACE the authored measurement on their own
1240
+ * side rather than adding to it; see {@link AUTO_PARAGRAPH_SPACING_PT}.
1286
1241
  */
1287
- declare function buildStyleCascadeTable(stylesRoot: OoxmlElement | null, themeFonts?: ThemeFonts): StyleCascadeTable;
1242
+ declare function paragraphSpacing(props: readonly OoxmlProperty[], context?: ParagraphAutoSpacingContext): ParagraphSpacing;
1288
1243
  /**
1289
- * Cascade paragraph + inherited run properties for one paragraph's direct `w:pPr`.
1244
+ * Resolve `w:line` / `w:lineRule` from flat paragraph properties.
1290
1245
  *
1291
- * Order: `docDefaults` → table style → `basedOn` ancestors → paragraph style → direct
1292
- * formatting, which is the style hierarchy of 17.7.2: a table style sits above the document
1293
- * defaults and below the paragraph style a cell paragraph names for itself.
1294
- * When `w:pStyle` is absent, the document's default paragraph style (`w:default="1"`) is used.
1295
- * Direct formatting is last so it overrides inherited values inside the existing resolvers.
1246
+ * Merged per attribute for the same reason as before/after: `w:spacing` is one element
1247
+ * carrying independent attributes, and a style that states only `@line` must not reset the
1248
+ * rule an earlier entry in the cascade set.
1249
+ */
1250
+ declare function paragraphLineSpacing(props: readonly OoxmlProperty[]): ParagraphLineSpacing;
1251
+ /**
1252
+ * Apply resolved line spacing to a line's natural (glyph-derived) box.
1253
+ *
1254
+ * Word places `auto` / `atLeast` extras BELOW the line (the last line's multiple spacing
1255
+ * still separates it from the next paragraph). Putting that delta above inverted cover-page
1256
+ * rhythm: `w:line="460"` on "between" opened a large gap above the word and almost none
1257
+ * before "MERIDIAN". `exact` taller than the glyphs centers the text (ECMA-376 17.3.1.33).
1258
+ * An `exact` box smaller than the glyphs keeps the baseline inside so clipped text still
1259
+ * sits on it.
1260
+ */
1261
+ declare function applyLineSpacing(spacing: ParagraphLineSpacing, naturalHeight: number, naturalBaseline: number): {
1262
+ height: number;
1263
+ baseline: number;
1264
+ };
1265
+ /**
1266
+ * `w:contextualSpacing` (17.3.1.9): drop before/after between paragraphs of the SAME
1267
+ * style. Word's built-in `ListParagraph` sets it, so every list authored in Word gets a
1268
+ * paragraph gap between items without this.
1269
+ */
1270
+ declare function paragraphContextualSpacing(props: readonly OoxmlProperty[]): boolean;
1271
+ /**
1272
+ * Resolve `w:pBdr` from the paragraph-properties node.
1273
+ *
1274
+ * Nested — every edge is a child of `pBdr`, not an attribute — so this reads the typed tree
1275
+ * rather than the flattened `OoxmlProperty[]` bag `propertiesOf` builds for leaf props.
1276
+ */
1277
+ declare function paragraphBorders(pPr: OoxmlNode | undefined): ParagraphBorders;
1278
+ /**
1279
+ * `w:pBdr` after the style cascade: a later `w:pBdr` replaces an earlier one WHOLESALE.
1280
+ *
1281
+ * Word does not merge edges across the cascade. A style that states only `w:bottom` discards
1282
+ * the box its `basedOn` ancestor declared, so folding edge by edge would leave a lone
1283
+ * underline surrounded by a box no one authored. Absence inherits; `nil`/`none` clear.
1284
+ */
1285
+ declare function cascadedParagraphBorders(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorders;
1286
+ /**
1287
+ * Visual stroke thickness layout publishes for one edge (points).
1288
+ *
1289
+ * Compound `ST_Border` values (`double`, …) use the shared inflated band so a thin
1290
+ * `w:sz="3"` double still occupies a visible double-line box — matching table borders.
1291
+ */
1292
+ declare function paragraphBorderStrokeWidthPt(edge: ParagraphBorderEdge): number;
1293
+ /**
1294
+ * Extent one border edge occupies away from the text it decorates: gap plus rule, in points.
1295
+ *
1296
+ * Vertically that is flow height — a top rule pushes the first line down, a bottom rule holds
1297
+ * the page open below the last one — so pagination has to see it. Horizontally it is
1298
+ * publish-only: Word draws left/right paragraph rules OUTSIDE the text column and never
1299
+ * re-breaks the lines, which is why adding a box to a paragraph in Word does not reflow it.
1300
+ */
1301
+ declare function paragraphBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
1302
+ /** Vertical extent a bottom border adds below the last line (gap + rule). */
1303
+ declare function bottomBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
1304
+ /**
1305
+ * Identity of a paragraph's border set, for the `w:between` group rule.
1306
+ *
1307
+ * Word treats consecutive paragraphs whose border settings are IDENTICAL as ONE bordered
1308
+ * block: the top rule draws above the first, the bottom rule below the last, and each
1309
+ * interior boundary gets `w:between` or nothing (§17.3.1.24). That is why applying a box to
1310
+ * three selected paragraphs in Word draws one box and not three.
1311
+ *
1312
+ * Empty string means "no borders", which never groups with anything.
1296
1313
  */
1297
- declare function cascadeParagraphFormatting(table: StyleCascadeTable, directPPr: OoxmlNode | undefined, tableCellStyle?: TableCellStyleFormatting): CascadedParagraphFormatting;
1314
+ declare function paragraphBordersFingerprint(borders: ParagraphBorders): string;
1298
1315
  /**
1299
- * Bottom border after cascade: a later `w:pBdr` replaces an earlier one; absence inherits.
1300
- * `nil`/`none` clear the edge via `paragraphBorders`.
1316
+ * Gap to insert before a paragraph once the previous paragraph's `after` is already in the
1317
+ * flow cursor — Word takes the larger of the two rather than summing them.
1301
1318
  */
1302
- declare function cascadedBottomBorder(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorderEdge | undefined;
1319
+ declare function collapsedSpaceBefore(before: number, previousAfter: number): number;
1303
1320
  /**
1304
- * Merge inherited paragraph-style run props with a run's direct `rPr` (direct last).
1305
- *
1306
- * When a cascade table is supplied, also resolves `w:rStyle` character styles (basedOn chain,
1307
- * cycle/depth capped). Runs without an explicit `rStyle` pick up the default character style
1308
- * (`w:default="1"`). Precedence: inherited → character style chain → direct formatting.
1321
+ * Applied before-spacing for placement (Word 2013+ / compat mode 15).
1309
1322
  *
1310
- * PASS `inheritedRunProperties` BY IDENTITY. It must be an array
1311
- * {@link cascadeParagraphFormatting} returned — `runProperties` or `markRunProperties` — and
1312
- * not a copy of one. A toggle property (ECMA-376 §17.7.3) resolves per level of the style
1313
- * hierarchy, and the levels below the character style resolve to more than a true or a false:
1314
- * whether the document defaults' short circuit is still standing decides what the character
1315
- * style's own toggle does next, and no single `w:b` element can spell that. The paragraph
1316
- * cascade attaches that state to the array it returns, so spreading, filtering or sorting the
1317
- * list drops it. (`Object.freeze` returns the same object, so that one is safe.) A list
1318
- * without it is read as one ordinary level, which is the most a bare property list can say
1319
- * and is what a caller assembling its own list gets.
1323
+ * Adjacent before/after still collapse to the larger gap, but before is dropped entirely when
1324
+ * the paragraph begins at the top of a page mid-section. The first paragraph of a document or
1325
+ * section retains before. Callers publish this applied value on the fragment so shading, borders,
1326
+ * selection, and paint share one geometry.
1320
1327
  */
1321
- declare function cascadeRunProperties(inheritedRunProperties: readonly OoxmlProperty[], directRunProperties: readonly OoxmlProperty[], table?: StyleCascadeTable): readonly OoxmlProperty[];
1322
- /** Everything the line breaker needs about one paragraph, already cascaded and converted. */
1323
- interface ParagraphLayoutInputs {
1324
- readonly props: OoxmlProperty[];
1325
- readonly indent: {
1326
- left: number;
1327
- right: number;
1328
- hanging: number;
1329
- firstLine: number;
1330
- };
1331
- readonly available: number;
1332
- readonly alignment: Alignment;
1333
- readonly spacing: ParagraphSpacing;
1334
- /** Resolved `w:line` / `w:lineRule`; single spacing where the cascade says nothing. */
1335
- readonly lineSpacing: ParagraphLineSpacing;
1336
- /** `w:contextualSpacing`: drop before/after between same-style neighbours. */
1337
- readonly contextualSpacing: boolean;
1338
- /** Resolved paragraph style id, for the `w:contextualSpacing` neighbour comparison. */
1339
- readonly styleId: string | null;
1340
- /** Resolved outline level, with 9/invalid values treated as body text. */
1341
- readonly outlineLevel: number | null;
1342
- readonly bottomBorder: ParagraphBorderEdge | undefined;
1343
- /**
1344
- * Every `CT_PBdr` edge after cascade, not just the bottom one.
1345
- *
1346
- * `bottomBorder` stays alongside it because the fragment signature and the table flow
1347
- * read that field by name; this is the whole box, so a cell paragraph gets the same
1348
- * frame a body paragraph does.
1349
- */
1350
- readonly borders: ParagraphBorders;
1351
- /** Validated 6-hex paragraph shading fill from cascaded `w:pPr/w:shd`, absent for none. */
1352
- readonly shading: string | undefined;
1353
- readonly inheritedRunProperties: readonly OoxmlProperty[];
1354
- /**
1355
- * Paragraph-mark cascade (`inheritedRunProperties` + direct `w:pPr/w:rPr`).
1356
- * Empty-line sizing and last-line mark height — never content-run face.
1357
- */
1358
- readonly markRunProperties: readonly OoxmlProperty[];
1359
- /** Cascaded custom tab stops + default interval for paragraph-flow breaking. */
1360
- readonly tabStops: ResolvedTabStops;
1361
- /**
1362
- * Fingerprint folded into the paragraph layout cache key — nested `w:tabs` are absent
1363
- * from flat property bags, so style-inherited stops must be named explicitly.
1364
- */
1365
- readonly tabStopsCacheToken: string;
1366
- /** Resolved list marker inputs when the paragraph participates in numbering. */
1367
- readonly listItem?: ResolvedListItem;
1328
+ declare function appliedSpaceBefore(before: number, previousAfter: number, atTopOfPage: boolean, firstParagraphOfSection: boolean): number;
1329
+
1330
+ interface DrawingPoint {
1331
+ readonly x: number;
1332
+ readonly y: number;
1333
+ }
1334
+ interface DrawingInsets {
1335
+ readonly top: number;
1336
+ readonly right: number;
1337
+ readonly bottom: number;
1338
+ readonly left: number;
1339
+ }
1340
+ type DrawingClipFallback = 'none' | 'unsupported-preset';
1341
+ interface DrawingGeometry {
1342
+ readonly contentBounds: LayoutBox;
1343
+ readonly paintBounds: LayoutBox;
1344
+ readonly hitBounds: LayoutBox;
1345
+ readonly transformedCorners: readonly DrawingPoint[];
1346
+ readonly clipPolygon: readonly DrawingPoint[] | null;
1347
+ readonly clipFallback: DrawingClipFallback;
1348
+ readonly effectInsets: DrawingInsets;
1368
1349
  }
1369
- /**
1370
- * Resolve every paragraph input semantic layout / table cells share: cascaded props when a
1371
- * style table is present, otherwise direct formatting only.
1372
- *
1373
- * When `listItem` is provided, its merged level indent becomes the paragraph indent (list
1374
- * hanging / left from `numbering.xml`), which is what Word uses for fixture list paragraphs
1375
- * that author no direct `w:ind`.
1376
- *
1377
- * `tableCellStyle` carries what the enclosing table's style says about this cell's
1378
- * paragraphs; body paragraphs pass nothing.
1379
- *
1380
- * `inTableCell` is asked for separately because a cell paragraph may have no table style to
1381
- * inherit at all, and `w:beforeAutospacing` still needs to know it is in a cell.
1382
- */
1383
- declare function resolveParagraphLayoutInputs(paragraph: OoxmlElement, contentWidth: number, styleCascade: StyleCascadeTable | undefined, listItem?: ResolvedListItem, tableCellStyle?: TableCellStyleFormatting, inTableCell?: boolean): ParagraphLayoutInputs;
1350
+
1351
+ /** Horizontal alignment of a paragraph (`w:jc`, ECMA-376 §17.3.1.13). */
1352
+ type Alignment = 'left' | 'center' | 'right' | 'both';
1384
1353
 
1385
1354
  /** Test-only mutable counters; omitted by production call sites. Not package-public. */
1386
1355
  interface TableBorderGridResolveWork {
@@ -1531,34 +1500,159 @@ interface BorderGridGeometry {
1531
1500
  /** Absolute column widths for the whole table (points). */
1532
1501
  readonly columnWidthsPt: readonly number[];
1533
1502
  /**
1534
- * Per laid-out row: absolute top and height in the same coordinate space as cell boxes
1535
- * (only relative differences matter for vertical edge segmentation).
1503
+ * Per laid-out row: absolute top and height in the same coordinate space as cell boxes
1504
+ * (only relative differences matter for vertical edge segmentation).
1505
+ */
1506
+ readonly rowBands: readonly {
1507
+ readonly y: number;
1508
+ readonly height: number;
1509
+ }[];
1510
+ /** Per laid-out cell: width/height after vMerge expansion (cell-local stroke space). */
1511
+ readonly cellBoxes: readonly (readonly {
1512
+ readonly width: number;
1513
+ readonly height: number;
1514
+ }[])[];
1515
+ }
1516
+ /**
1517
+ * Resolve borders for every cell in a laid-out table fragment.
1518
+ *
1519
+ * Shared vertical edges: conflict(left.right, right.left) → assigned to the left cell,
1520
+ * segmented per row when a vMerge restart faces differing neighbors.
1521
+ * Shared horizontal edges: conflict(above.bottom, below.top) → assigned to the above cell,
1522
+ * segmented per grid column when a gridSpan cell faces differing below neighbors; vMerge
1523
+ * interior seams are suppressed per interval.
1524
+ *
1525
+ * When `geometry` is provided, compound edges expand into explicit stroke records with
1526
+ * corner-adjusted endpoints in cell-local points.
1527
+ */
1528
+ declare function resolveTableCellBorderGrid(rows: readonly (readonly BorderGridCell[])[], table: TableBorderBox, columnCount: number, geometry?: BorderGridGeometry, work?: TableBorderGridResolveWork, ownershipBudget?: TableBorderOwnershipBudget): ResolvedCellBorders[][];
1529
+ /** Width contribution of a resolved edge for content inset / row sizing. */
1530
+ declare function borderExtentPt(edge: ResolvedTableBorderEdge | TableBorderSide | undefined): number;
1531
+
1532
+ /**
1533
+ * Why textbox story layout stopped short.
1534
+ *
1535
+ * Every one is a BOUND rather than a bug: nesting depth, fragment counts and the extent all
1536
+ * come from a file. Falling back with a reason keeps the drawing rendered (clipped) instead
1537
+ * of failing the layout pass.
1538
+ */
1539
+ type TextboxStoryFallbackReason = 'textbox-nesting-limit' | 'textbox-fragment-limit'
1540
+ /** Flowed content is taller than the extent; trailing fragments were dropped. */
1541
+ | 'textbox-height-clip';
1542
+ /**
1543
+ * One text box's story laid out inside its extent, in content-box-relative coordinates.
1544
+ *
1545
+ * Fragments origin at the content box's top-left; paint places the content box at
1546
+ * `drawing origin + contentOffset` and clips to the extent.
1547
+ */
1548
+ interface TextboxStoryLayout {
1549
+ /** Content-box-relative fragments (origin at the content box's top-left). */
1550
+ readonly fragments: readonly BlockFragmentRecord[];
1551
+ /** Height the blocks flow to (points), before vertical anchoring. */
1552
+ readonly flowHeight: number;
1553
+ /** Offset of the content box inside the drawing extent: insets plus vertical anchoring. */
1554
+ readonly contentOffset: Readonly<{
1555
+ x: number;
1556
+ y: number;
1557
+ }>;
1558
+ /** Content box width (extent minus horizontal insets). */
1559
+ readonly contentWidth: number;
1560
+ /** Content box height (extent minus vertical insets). */
1561
+ readonly contentHeight: number;
1562
+ /** Solid fill of the hosting shape, painted behind the story; null for no fill. */
1563
+ readonly fillHex: string | null;
1564
+ /** Solid outline of the hosting shape; null for no outline. */
1565
+ readonly strokeHex: string | null;
1566
+ /** Outline width in points; 0 when absent. */
1567
+ readonly strokeWidthPt: number;
1568
+ /** True when layout hit a named bound and returned a truncated / empty story. */
1569
+ readonly fallbackReason?: TextboxStoryFallbackReason;
1570
+ /**
1571
+ * Resource identities of the drawings the clip DROPPED, in flow order.
1572
+ *
1573
+ * The furniture invalidation token walks laid-out records, and a dropped fragment leaves
1574
+ * none — while a `withPageContext` projection of the same story can wrap differently
1575
+ * (PAGE digits) and keep the drawing. Without this a picture clipped out of the baseline
1576
+ * but painted by a projection never reaches the session context, so its decode settling
1577
+ * moves nothing and the unchanged-pass early exit reuses the placeholder pages forever
1578
+ * (#467). Absent when the clip dropped no drawings, so the common story keys byte-for-byte
1579
+ * as before.
1580
+ */
1581
+ readonly clippedResourceToken?: string;
1582
+ }
1583
+
1584
+ interface InlineDrawingRecord {
1585
+ readonly kind: 'inlineDrawing';
1586
+ readonly drawingNodeId: string;
1587
+ readonly paragraphId: string;
1588
+ readonly ownerPartName: string;
1589
+ readonly start: number;
1590
+ /** Left edge of the extent box (slot + distL). */
1591
+ readonly x: number;
1592
+ readonly y: number;
1593
+ readonly width: number;
1594
+ readonly height: number;
1595
+ readonly distL: number;
1596
+ readonly distR: number;
1597
+ readonly distT: number;
1598
+ readonly distB: number;
1599
+ /** Caret/hit advance start (slot left, before distL). */
1600
+ readonly advanceStart: number;
1601
+ /** Caret/hit advance end (slot + totalWidth). */
1602
+ readonly advanceEnd: number;
1603
+ readonly baselineOffset: number;
1604
+ readonly paintBounds: LayoutBox;
1605
+ readonly hitBounds: LayoutBox;
1606
+ readonly geometry: DrawingGeometry;
1607
+ readonly resource: ImageResourceState;
1608
+ readonly accessibility: DrawingAccessibility;
1609
+ /** Sanitized external hyperlink projection; inert until an explicit gesture activates it. */
1610
+ readonly hyperlinkHref: string | null;
1611
+ readonly effects: DrawingImageEffects;
1612
+ readonly crop: SourceCrop;
1613
+ readonly transform: DrawingTransform;
1614
+ /** Fixed non-picture graphic kind for refusal labels (`chart`, `group`, …); null for pictures. */
1615
+ readonly placeholderGraphicKind: string | null;
1616
+ /** Typed solid-geometry payload for a renderable `wps:wsp` shape; null otherwise. */
1617
+ readonly vectorShape: VectorShapeProjection | null;
1618
+ /**
1619
+ * The revision wrappers enclosing the owning run, outermost first — the same stack spans
1620
+ * carry, so paint and review chrome give a tracked picture the same cues as tracked text.
1621
+ * Absent when the drawing is untracked.
1622
+ */
1623
+ readonly revisions?: readonly RevisionAttribution[];
1624
+ }
1625
+ interface InlineDrawingLayoutContext {
1626
+ readonly ownerPartName: string;
1627
+ /** Precomputed run-level atom id (`w:drawing` or MC wrapper) → projection. */
1628
+ readonly projectionForAtom?: (atomNodeId: string) => DrawingProjection | null;
1629
+ readonly project: (drawing: OoxmlDrawingNode) => DrawingProjection | null;
1630
+ readonly resourceOf: (projection: DrawingProjection) => ImageResourceState;
1631
+ }
1632
+ /** Named fallback when a positioning frame cannot be resolved (OpenSpec 4.6). */
1633
+ type AnchoredDrawingLayoutFallback = 'unresolvable-frame' | 'page-defer-exhausted';
1634
+ interface AnchoredDrawingRecord extends Omit<InlineDrawingRecord, 'kind' | 'baselineOffset' | 'advanceStart' | 'advanceEnd' | 'distL' | 'distR' | 'distT' | 'distB'> {
1635
+ readonly kind: 'anchoredDrawing';
1636
+ readonly anchorParagraphId: string;
1637
+ readonly horizontalFrame: DrawingHorizontalReferenceFrame;
1638
+ readonly verticalFrame: DrawingVerticalReferenceFrame;
1639
+ readonly horizontalFrameOrigin: number;
1640
+ readonly verticalFrameOrigin: number;
1641
+ readonly behindDocument: boolean;
1642
+ readonly allowOverlap: boolean;
1643
+ readonly layoutInCell: boolean;
1644
+ readonly relativeHeight: number;
1645
+ readonly wrap: Exclude<ImageWrapTarget, 'inline'>;
1646
+ readonly layoutFallback?: AnchoredDrawingLayoutFallback;
1647
+ /** Canonical document traversal index within the owner story part. */
1648
+ readonly sourceOrder?: number;
1649
+ /**
1650
+ * Laid-out textbox story for a `wps:txbx` drawing; paint renders it clipped inside the
1651
+ * extent instead of a placeholder. Absent when the drawing carries no story or the host
1652
+ * did not thread story layout (the record then degrades to the placeholder path).
1536
1653
  */
1537
- readonly rowBands: readonly {
1538
- readonly y: number;
1539
- readonly height: number;
1540
- }[];
1541
- /** Per laid-out cell: width/height after vMerge expansion (cell-local stroke space). */
1542
- readonly cellBoxes: readonly (readonly {
1543
- readonly width: number;
1544
- readonly height: number;
1545
- }[])[];
1654
+ readonly textboxStory?: TextboxStoryLayout;
1546
1655
  }
1547
- /**
1548
- * Resolve borders for every cell in a laid-out table fragment.
1549
- *
1550
- * Shared vertical edges: conflict(left.right, right.left) → assigned to the left cell,
1551
- * segmented per row when a vMerge restart faces differing neighbors.
1552
- * Shared horizontal edges: conflict(above.bottom, below.top) → assigned to the above cell,
1553
- * segmented per grid column when a gridSpan cell faces differing below neighbors; vMerge
1554
- * interior seams are suppressed per interval.
1555
- *
1556
- * When `geometry` is provided, compound edges expand into explicit stroke records with
1557
- * corner-adjusted endpoints in cell-local points.
1558
- */
1559
- declare function resolveTableCellBorderGrid(rows: readonly (readonly BorderGridCell[])[], table: TableBorderBox, columnCount: number, geometry?: BorderGridGeometry, work?: TableBorderGridResolveWork, ownershipBudget?: TableBorderOwnershipBudget): ResolvedCellBorders[][];
1560
- /** Width contribution of a resolved edge for content inset / row sizing. */
1561
- declare function borderExtentPt(edge: ResolvedTableBorderEdge | TableBorderSide | undefined): number;
1562
1656
 
1563
1657
  /**
1564
1658
  * A paragraph's list membership fully resolved: definition, level, marker text and geometry.
@@ -1673,297 +1767,465 @@ declare function listMarkerBox(item: ResolvedListItem, markerWidth: number, line
1673
1767
  height: number;
1674
1768
  } | null;
1675
1769
 
1770
+ interface StyleDefinition {
1771
+ readonly styleId: string;
1772
+ readonly type: string;
1773
+ readonly basedOn: string | null;
1774
+ /** `w:next` — the authoring style for a following paragraph. */
1775
+ readonly next: string | null;
1776
+ readonly outlineLevel: number | null;
1777
+ readonly paragraphProperties: readonly OoxmlProperty[];
1778
+ readonly runProperties: readonly OoxmlProperty[];
1779
+ readonly paragraphPropertiesNode: OoxmlElement | undefined;
1780
+ readonly tablePropertiesNode: OoxmlElement | undefined;
1781
+ readonly tableRowPropertiesNode: OoxmlElement | undefined;
1782
+ readonly conditionalTableFormats: ReadonlyMap<string, OoxmlElement>;
1783
+ }
1784
+ declare function isValidStyleId(raw: string | undefined): raw is string;
1785
+
1786
+ /** Soft ceiling on `basedOn` chain length — enough for real templates, refuses hostile graphs. */
1787
+ declare const MAX_STYLE_BASED_ON_DEPTH = 32;
1788
+ /** Soft ceiling on style definitions read from one styles part. */
1789
+ declare const MAX_STYLE_DEFINITIONS = 4096;
1676
1790
  /**
1677
- * The story's resolved REF inputs for one layout pass.
1791
+ * The whole styles part, indexed and ready to resolve against.
1678
1792
  *
1679
- * Threaded as a runtime rider on the layout options (see `layoutSemanticDocument`) rather
1680
- * than a `SemanticLayoutOptions` member, so the public options surface stays put. Absent
1681
- * means every REF field paints its cached result — the pre-existing degradation, and the one
1682
- * header/footer and text-box stories still take. Note stories receive the body's context
1683
- * through `NotesLayoutInput.refFields`.
1793
+ * `cacheToken` is load-bearing: it folds into layout cache producers so breaks measured under one
1794
+ * styles part are never reused under another.
1684
1795
  */
1685
- interface RefFieldContext {
1796
+ interface StyleCascadeTable {
1797
+ readonly typography?: CjkTypographySettings;
1686
1798
  /**
1687
- * Content token over every PAINTED REF output in the story, for the section prepass memo.
1688
- * A renumbering edit can move a REF value in a section whose own blocks and list map are
1689
- * identity-unchanged, and this token is the only validator that sees it. A field that
1690
- * failed calibration contributes its (session-constant) cached text, so the token still
1691
- * moves exactly when painted output moves.
1799
+ * Bounded fingerprint folded into layout cache producers so a different styles part cannot
1800
+ * reuse breaks measured under another cascade. Computed once per table (FNV-1a hex).
1692
1801
  */
1693
- readonly valuesToken: string;
1694
- /** The paragraph's REF outputs folded for its block cache key; `''` when it holds none. */
1695
- tokenForParagraph(paragraphId: string): string;
1802
+ readonly cacheToken: string;
1803
+ readonly docDefaultsRun: readonly OoxmlProperty[];
1804
+ readonly docDefaultsParagraph: readonly OoxmlProperty[];
1805
+ readonly docDefaultsParagraphNode: OoxmlElement | undefined;
1806
+ /** `w:style[@w:default='1'][@w:type='paragraph']` — last wins among defaults of that type. */
1807
+ readonly defaultParagraphStyleId: string | null;
1808
+ /** `w:style[@w:default='1'][@w:type='character']` — last wins among defaults of that type. */
1809
+ readonly defaultCharacterStyleId: string | null;
1696
1810
  /**
1697
- * The live value ONE field paints, keyed by its begin / `w:fldSimple` node id, or null to
1698
- * keep the cached result (failed calibration, unresolvable, or an anchor this scan never
1699
- * saw). Anchor-keyed so the projection's paint and this context's token fold read the SAME
1700
- * calibration verdict, however each walk collected the field's cached text.
1811
+ * `w:style[@w:default='1'][@w:type='table']` — last wins among defaults of that type.
1812
+ *
1813
+ * A `w:tbl` with no `w:tblStyle` still resolves against this one. Word's own
1814
+ * `TableNormal` is where the 0/108/0/108 twip cell margins live, so skipping it gives
1815
+ * every unstyled table padding the document says it does not have.
1701
1816
  */
1702
- liveValueOf(anchorId: string, spec: RefFieldSpec): string | null;
1817
+ readonly defaultTableStyleId: string | null;
1703
1818
  /**
1704
- * The synthesized display of ONE AUTONUM-family field, keyed by its begin / `w:fldSimple`
1705
- * node id, or null to paint nothing (unsupported switches, or an anchor this scan never
1706
- * saw — both the field's historical rendering). These fields carry no cached result at
1707
- * all, so there is no calibration: the sequential value is the only display they have.
1708
- * Optional so hand-built contexts predating it stay valid.
1819
+ * The theme part's typefaces, for `w:rFonts` theme references.
1820
+ *
1821
+ * Lives on the cascade because it is document-level style material with the same
1822
+ * lifetime, and because every site that resolves run properties already holds this table.
1709
1823
  */
1710
- autonumValueOf?(anchorId: string): string | null;
1824
+ readonly themeFonts: ThemeFonts;
1825
+ readonly styles: ReadonlyMap<string, StyleDefinition>;
1826
+ }
1827
+ /**
1828
+ * A paragraph's properties after the cascade, plus the same list WITHOUT its own `w:pPr`.
1829
+ *
1830
+ * Both, because a writer needs to know what a paragraph INHERITS to decide whether setting a
1831
+ * value is a change or a no-op — and writing back an inherited value freezes it into the
1832
+ * paragraph as though the author had chosen it.
1833
+ */
1834
+ interface CascadedParagraphFormatting {
1835
+ /** Flat paragraph properties in cascade order (defaults → bases → style → direct). */
1836
+ readonly paragraphProperties: readonly OoxmlProperty[];
1711
1837
  /**
1712
- * The deferred projection of ONE `PAGEREF` field, or null to keep the cached result
1713
- * (missing bookmark, unsupported switches, or an anchor this scan never saw).
1838
+ * The same list WITHOUT the paragraph's own `w:pPr` — everything it inherits.
1714
1839
  *
1715
- * A `PAGEREF` value is a property of pagination, so the scan cannot compute it — it hands
1716
- * the projection the resolved TARGET and the calibration inputs instead, and document
1717
- * finalize (`finalizePageFieldProjection`) substitutes the page number the target's first
1718
- * fragment lands on. Optional so hand-built contexts predating it stay valid.
1840
+ * Numbering needs the two tiers apart: a level's `w:pPr/w:ind` outranks the style's and is
1841
+ * outranked by the paragraph's own, and a flattened list cannot say which is which.
1719
1842
  */
1720
- pageRefProjectionOf?(anchorId: string, spec: RefFieldSpec): PageRefFieldProjection | null;
1843
+ readonly inheritedParagraphProperties: readonly OoxmlProperty[];
1844
+ /** Matching `w:pPr` nodes for nested border resolution. */
1845
+ readonly paragraphPropertyNodes: readonly OoxmlNode[];
1846
+ /**
1847
+ * Inherited run properties for CONTENT runs (before direct run `rPr`).
1848
+ *
1849
+ * Does NOT include direct `w:pPr/w:rPr` — that formats the paragraph MARK only
1850
+ * (ECMA-376 §17.3.1.36). Folding mark `w:sz` into content made BodyText runs with no
1851
+ * direct size paint at the mark's size (Selection Notice "or" alternative → 6.5pt).
1852
+ */
1853
+ readonly runProperties: readonly OoxmlProperty[];
1854
+ /**
1855
+ * Content cascade plus direct `w:pPr/w:rPr` — empty-line metrics and last-line mark height.
1856
+ */
1857
+ readonly markRunProperties: readonly OoxmlProperty[];
1858
+ /**
1859
+ * The style this paragraph resolved to, or null when it names none and there is no
1860
+ * document default. `w:contextualSpacing` compares neighbours by exactly this.
1861
+ */
1862
+ readonly styleId: string | null;
1863
+ }
1864
+ /**
1865
+ * What a table style contributes to the paragraphs of ONE cell: the style's whole-table
1866
+ * `w:pPr`/`w:rPr` followed by every `w:tblStylePr` the cell is under (17.7.6.6), weakest
1867
+ * first in the caller's condition order (banding, column, row, corner).
1868
+ *
1869
+ * This is how Word makes a header row bold and centred while the document states nothing
1870
+ * but `<w:tblStyle w:val="…"/>` on the table and plain runs in the cells.
1871
+ */
1872
+ interface TableCellStyleFormatting {
1873
+ readonly paragraphProperties: readonly OoxmlProperty[];
1874
+ /** Matching `w:pPr` nodes, for nested `w:pBdr` / `w:tabs` resolution. */
1875
+ readonly paragraphPropertyNodes: readonly OoxmlElement[];
1876
+ /** Inherited run properties for every run in the cell, before the paragraph style. */
1877
+ readonly runProperties: readonly OoxmlProperty[];
1878
+ }
1879
+ /**
1880
+ * Build a cascade table from a styles part root.
1881
+ *
1882
+ * Only direct `w:style` children of the root participate (bounded count). Duplicate
1883
+ * `styleId` values keep the last definition, matching Word's reader for this fixture class.
1884
+ * Default paragraph/character style ids track `w:default="1"` with the same last-wins rule.
1885
+ */
1886
+ declare function buildStyleCascadeTable(stylesRoot: OoxmlElement | null, themeFonts?: ThemeFonts, settingsRoot?: OoxmlElement | null): StyleCascadeTable;
1887
+ /**
1888
+ * Cascade paragraph + inherited run properties for one paragraph's direct `w:pPr`.
1889
+ *
1890
+ * Order: `docDefaults` → table style → `basedOn` ancestors → paragraph style → direct
1891
+ * formatting, which is the style hierarchy of 17.7.2: a table style sits above the document
1892
+ * defaults and below the paragraph style a cell paragraph names for itself.
1893
+ * When `w:pStyle` is absent, the document's default paragraph style (`w:default="1"`) is used.
1894
+ * Direct formatting is last so it overrides inherited values inside the existing resolvers.
1895
+ */
1896
+ declare function cascadeParagraphFormatting(table: StyleCascadeTable, directPPr: OoxmlNode | undefined, tableCellStyle?: TableCellStyleFormatting): CascadedParagraphFormatting;
1897
+ /**
1898
+ * Bottom border after cascade: a later `w:pBdr` replaces an earlier one; absence inherits.
1899
+ * `nil`/`none` clear the edge via `paragraphBorders`.
1900
+ */
1901
+ declare function cascadedBottomBorder(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorderEdge | undefined;
1902
+ /**
1903
+ * Merge inherited paragraph-style run props with a run's direct `rPr` (direct last).
1904
+ *
1905
+ * When a cascade table is supplied, also resolves `w:rStyle` character styles (basedOn chain,
1906
+ * cycle/depth capped). Runs without an explicit `rStyle` pick up the default character style
1907
+ * (`w:default="1"`). Precedence: inherited → character style chain → direct formatting.
1908
+ *
1909
+ * PASS `inheritedRunProperties` BY IDENTITY. It must be an array
1910
+ * {@link cascadeParagraphFormatting} returned — `runProperties` or `markRunProperties` — and
1911
+ * not a copy of one. A toggle property (ECMA-376 §17.7.3) resolves per level of the style
1912
+ * hierarchy, and the levels below the character style resolve to more than a true or a false:
1913
+ * whether the document defaults' short circuit is still standing decides what the character
1914
+ * style's own toggle does next, and no single `w:b` element can spell that. The paragraph
1915
+ * cascade attaches that state to the array it returns, so spreading, filtering or sorting the
1916
+ * list drops it. (`Object.freeze` returns the same object, so that one is safe.) A list
1917
+ * without it is read as one ordinary level, which is the most a bare property list can say
1918
+ * and is what a caller assembling its own list gets.
1919
+ */
1920
+ declare function cascadeRunProperties(inheritedRunProperties: readonly OoxmlProperty[], directRunProperties: readonly OoxmlProperty[], table?: StyleCascadeTable): readonly OoxmlProperty[];
1921
+ /** Everything the line breaker needs about one paragraph, already cascaded and converted. */
1922
+ interface ParagraphLayoutInputs {
1923
+ readonly props: OoxmlProperty[];
1924
+ readonly indent: {
1925
+ left: number;
1926
+ right: number;
1927
+ hanging: number;
1928
+ firstLine: number;
1929
+ };
1930
+ readonly available: number;
1931
+ readonly alignment: Alignment;
1932
+ readonly spacing: ParagraphSpacing;
1933
+ /** Resolved `w:line` / `w:lineRule`; single spacing where the cascade says nothing. */
1934
+ readonly lineSpacing: ParagraphLineSpacing;
1935
+ /** `w:contextualSpacing`: drop before/after between same-style neighbours. */
1936
+ readonly contextualSpacing: boolean;
1937
+ /** Resolved paragraph style id, for the `w:contextualSpacing` neighbour comparison. */
1938
+ readonly styleId: string | null;
1939
+ /** Resolved outline level, with 9/invalid values treated as body text. */
1940
+ readonly outlineLevel: number | null;
1941
+ readonly bottomBorder: ParagraphBorderEdge | undefined;
1942
+ /**
1943
+ * Every `CT_PBdr` edge after cascade, not just the bottom one.
1944
+ *
1945
+ * `bottomBorder` stays alongside it because the fragment signature and the table flow
1946
+ * read that field by name; this is the whole box, so a cell paragraph gets the same
1947
+ * frame a body paragraph does.
1948
+ */
1949
+ readonly borders: ParagraphBorders;
1950
+ /** Validated 6-hex paragraph shading fill from cascaded `w:pPr/w:shd`, absent for none. */
1951
+ readonly shading: string | undefined;
1952
+ readonly inheritedRunProperties: readonly OoxmlProperty[];
1953
+ /**
1954
+ * Paragraph-mark cascade (`inheritedRunProperties` + direct `w:pPr/w:rPr`).
1955
+ * Empty-line sizing and last-line mark height — never content-run face.
1956
+ */
1957
+ readonly markRunProperties: readonly OoxmlProperty[];
1958
+ /** Cascaded custom tab stops + default interval for paragraph-flow breaking. */
1959
+ readonly tabStops: ResolvedTabStops;
1960
+ /**
1961
+ * Fingerprint folded into the paragraph layout cache key — nested `w:tabs` are absent
1962
+ * from flat property bags, so style-inherited stops must be named explicitly.
1963
+ */
1964
+ readonly tabStopsCacheToken: string;
1965
+ /** Resolved list marker inputs when the paragraph participates in numbering. */
1966
+ readonly listItem?: ResolvedListItem;
1721
1967
  }
1722
1968
  /**
1723
- * Sticky calibration identity for one `PAGEREF` field.
1969
+ * Resolve every paragraph input semantic layout / table cells share: cascaded props when a
1970
+ * style table is present, otherwise direct formatting only.
1971
+ *
1972
+ * When `listItem` is provided, its merged level indent becomes the paragraph indent (list
1973
+ * hanging / left from `numbering.xml`), which is what Word uses for fixture list paragraphs
1974
+ * that author no direct `w:ind`.
1975
+ *
1976
+ * `tableCellStyle` carries what the enclosing table's style says about this cell's
1977
+ * paragraphs; body paragraphs pass nothing.
1978
+ *
1979
+ * `inTableCell` is asked for separately because a cell paragraph may have no table style to
1980
+ * inherit at all, and `w:beforeAutospacing` still needs to know it is in a cell.
1981
+ */
1982
+ declare function resolveParagraphLayoutInputs(paragraph: OoxmlElement, contentWidth: number, styleCascade: StyleCascadeTable | undefined, listItem?: ResolvedListItem, tableCellStyle?: TableCellStyleFormatting, inTableCell?: boolean): ParagraphLayoutInputs;
1983
+
1984
+ /**
1985
+ * `w:tblW` / `w:tcW` / `w:wBefore` (CT_TblWidth, 17.4.63 / 17.4.71 / 17.4.86): a PREFERRED
1986
+ * width plus the unit it is stated in. Preferred is the operative word — it is what the
1987
+ * producer asked for, not what the table resolved to.
1724
1988
  *
1725
- * An opaque frozen object rather than a mutable verdict holder because span markers are
1726
- * serialized into fragment signatures — a verdict written into the marker would move a
1727
- * signature that no painted output moved. The verdict itself lives beside the finalize pass
1728
- * (`field-page-furniture.ts`), keyed weakly on this object; the registry below carries the
1729
- * object across passes the same way REF verdicts are carried.
1989
+ * `pct` is stated in fiftieths of a percent (5000 = 100%) by Word, and in the `"50%"`
1990
+ * string form of `ST_Percentage` by others; both are read. `auto` and `nil` carry no width.
1730
1991
  */
1731
- type PageRefCalibrationCell = Readonly<Record<never, never>>;
1992
+ type PreferredWidthType = 'dxa' | 'pct' | 'auto' | 'nil';
1732
1993
  /**
1733
- * What a `PAGEREF` span carries to document finalize: the resolved target, the normalized
1734
- * authored cache (the calibration oracle), and the sticky calibration identity.
1994
+ * `w:tblW` / `w:tcW` — a requested width, whose UNIT depends on its type.
1995
+ *
1996
+ * Points for `dxa`, percent for `pct`, and zero for `auto`/`nil`. Reading `value` without `type`
1997
+ * is always wrong.
1735
1998
  */
1736
- interface PageRefFieldProjection {
1737
- /** Canonical id of the paragraph the bookmark names — first declaration wins. */
1738
- readonly targetParagraphId: string;
1739
- /** The authored cached result, whitespace-collapsed — what the computed number must reproduce. */
1740
- readonly cached: string;
1741
- readonly calibration: PageRefCalibrationCell;
1999
+ interface PreferredWidth {
2000
+ readonly type: PreferredWidthType;
2001
+ /** POINTS for `dxa`, PERCENT (0–100) for `pct`, 0 for `auto`/`nil`. */
2002
+ readonly value: number;
1742
2003
  }
2004
+ /** The frozen "no preferred width" value — what a table or cell that declares none resolves to. */
2005
+ declare const AUTO_PREFERRED_WIDTH: PreferredWidth;
2006
+
1743
2007
  /**
1744
- * Note parts whose stories join the context: their REF fields resolve against the body's
1745
- * bookmarks and numbering (a footnote citing "Section 1.2(c)" targets a body paragraph),
1746
- * and their bookmark declarations become plain-REF targets. Number switches aimed AT a
1747
- * note paragraph still fall back to the cached result — the list map is the body's.
2008
+ * The uniform 3 pt (60 twip) inset this lane used to apply on every side.
2009
+ *
2010
+ * Word's defaults are not uniform — see {@link DEFAULT_CELL_MARGINS} — so nothing resolves
2011
+ * against this any more. Kept because it is published.
1748
2012
  */
1749
- interface RefNoteParts {
1750
- readonly footnotesPart: OoxmlPart | null;
1751
- readonly endnotesPart: OoxmlPart | null;
2013
+ declare const CELL_PAD = 3;
2014
+ /** Resolved cell padding in points, after the table default and any per-cell override. */
2015
+ interface CellMarginsPt {
2016
+ readonly top: number;
2017
+ readonly right: number;
2018
+ readonly bottom: number;
2019
+ readonly left: number;
1752
2020
  }
1753
2021
  /**
1754
- * Resolve the document's REF fields for one layout pass, or null when it has none.
2022
+ * Word's own default cell padding, for a document whose styles part states none.
1755
2023
  *
1756
- * Bookmarks and REF fields resolve across the body story and (when `notes` is given) the
1757
- * footnote/endnote stories, against ONE shared target index — a footnote REF finds the
1758
- * body bookmark it cites. Header/footer and text-box stories are not given a context and
1759
- * keep painting cached results.
2024
+ * 0 top, 0 bottom, 108 twips left and right — the values `TableNormal` carries. A uniform
2025
+ * 3 pt on all four sides made every row of every table that authored no `w:tblCellMar` 6 pt
2026
+ * taller than Word's, and that error compounds down a table until it paginates a page early.
2027
+ * A document that DOES ship a default table style resolves against that instead; this is the
2028
+ * floor beneath it.
1760
2029
  */
1761
- declare function resolveStoryRefFields(blocks: readonly OoxmlElement[], listItems: ReadonlyMap<string, ResolvedListItem> | undefined, notes?: RefNoteParts): RefFieldContext | null;
2030
+ declare const DEFAULT_CELL_MARGINS: CellMarginsPt;
2031
+
2032
+ /** `w:vAlign` — where a cell's content sits when the row is taller than the content. */
2033
+ type CellVerticalAlign = 'top' | 'center' | 'bottom';
1762
2034
 
1763
2035
  /**
1764
- * Why textbox story layout stopped short.
2036
+ * Layout-time nesting ceiling. Parse-time depth (MAX_DEPTH = 256 XML levels) alone still
2037
+ * admits ~80 levels of `w:tbl` recursion into the layout walk; deeper tables render as an
2038
+ * empty cell box rather than recursing.
2039
+ */
2040
+ declare const MAX_TABLE_NESTING = 16;
2041
+ /**
2042
+ * Soft ceiling on an authored `w:trHeight` (~22"). Hostile `w:val` otherwise becomes a
2043
+ * multi-page row that every pagination preflight and cell box inherits.
2044
+ */
2045
+ declare const MAX_TABLE_ROW_HEIGHT_PT: number;
2046
+ /**
2047
+ * `w:trPr/w:trHeight` (17.4.81) resolved for layout. Points leave the reader already —
2048
+ * twips convert once here, matching every other table geometry boundary.
1765
2049
  *
1766
- * Every one is a BOUND rather than a bug: nesting depth, fragment counts and the extent all
1767
- * come from a file. Falling back with a reason keeps the drawing rendered (clipped) instead
1768
- * of failing the layout pass.
2050
+ * Word quirk (matches Form025U and Word's UI export): a present `@w:val` with an omitted
2051
+ * `@w:hRule` is treated as `atLeast`, not ECMA's `auto`. Explicit `auto` still ignores val.
1769
2052
  */
1770
- type TextboxStoryFallbackReason = 'textbox-nesting-limit' | 'textbox-fragment-limit'
1771
- /** Flowed content is taller than the extent; trailing fragments were dropped. */
1772
- | 'textbox-height-clip';
2053
+ type TableRowHeightRule = 'auto' | 'atLeast' | 'exact';
1773
2054
  /**
1774
- * One text box's story laid out inside its extent, in content-box-relative coordinates.
2055
+ * `w:trHeight` — a row's height rule and its value.
1775
2056
  *
1776
- * Fragments origin at the content box's top-left; paint places the content box at
1777
- * `drawing origin + contentOffset` and clips to the extent.
2057
+ * `auto` carries no value at all, which is why this is a union rather than a rule plus an
2058
+ * optional number.
1778
2059
  */
1779
- interface TextboxStoryLayout {
1780
- /** Content-box-relative fragments (origin at the content box's top-left). */
1781
- readonly fragments: readonly BlockFragmentRecord[];
1782
- /** Height the blocks flow to (points), before vertical anchoring. */
1783
- readonly flowHeight: number;
1784
- /** Offset of the content box inside the drawing extent: insets plus vertical anchoring. */
1785
- readonly contentOffset: Readonly<{
1786
- x: number;
1787
- y: number;
1788
- }>;
1789
- /** Content box width (extent minus horizontal insets). */
1790
- readonly contentWidth: number;
1791
- /** Content box height (extent minus vertical insets). */
1792
- readonly contentHeight: number;
1793
- /** Solid fill of the hosting shape, painted behind the story; null for no fill. */
1794
- readonly fillHex: string | null;
1795
- /** Solid outline of the hosting shape; null for no outline. */
1796
- readonly strokeHex: string | null;
1797
- /** Outline width in points; 0 when absent. */
1798
- readonly strokeWidthPt: number;
1799
- /** True when layout hit a named bound and returned a truncated / empty story. */
1800
- readonly fallbackReason?: TextboxStoryFallbackReason;
2060
+ type TableRowHeight = {
2061
+ readonly rule: 'auto';
2062
+ } | {
2063
+ readonly rule: 'atLeast' | 'exact';
2064
+ readonly valuePt: number;
2065
+ };
2066
+ /** `w:tblPr/w:jc` (17.4.29, ST_JcTable): where the table sits within the text column. */
2067
+ type TableAlignment = 'left' | 'center' | 'right';
2068
+ /**
2069
+ * `w:tblpPr/@w:horzAnchor` (17.4.58) and `@w:vertAnchor` (17.4.66): the box a floated
2070
+ * table's offsets are measured from. Absent means `text` for both.
2071
+ */
2072
+ type TableFloatAnchor = 'text' | 'margin' | 'page';
2073
+ /** `w:tblpPr/@w:tblpXSpec` (17.4.63, ST_XAlign). */
2074
+ type TableFloatXSpec = 'left' | 'center' | 'right' | 'inside' | 'outside';
2075
+ /** `w:tblpPr/@w:tblpYSpec` (17.4.65, ST_YAlign). */
2076
+ type TableFloatYSpec = 'inline' | 'top' | 'center' | 'bottom' | 'inside' | 'outside';
2077
+ /**
2078
+ * `w:tblPr/w:tblpPr` (17.4.57) — a table positioned against an anchor box rather than at
2079
+ * the point in the text where it was authored.
2080
+ *
2081
+ * A spec (`tblpXSpec`/`tblpYSpec`) supersedes the matching offset when both are present:
2082
+ * 17.4.57 states the alignment outright, and the offset only answers "how far from the
2083
+ * anchor" for the case where no alignment was stated.
2084
+ */
2085
+ interface TableFloatPosition {
2086
+ readonly horzAnchor: TableFloatAnchor;
2087
+ readonly vertAnchor: TableFloatAnchor;
2088
+ readonly xSpec?: TableFloatXSpec;
2089
+ /** `w:tblpX` in points; signed, so a table can be pulled into the margin. */
2090
+ readonly xPt: number;
2091
+ readonly ySpec?: TableFloatYSpec;
2092
+ /** `w:tblpY` in points; signed. */
2093
+ readonly yPt: number;
2094
+ readonly distances?: {
2095
+ readonly top: number;
2096
+ readonly right: number;
2097
+ readonly bottom: number;
2098
+ readonly left: number;
2099
+ };
2100
+ }
2101
+ /**
2102
+ * One cell in the resolved table structure.
2103
+ *
2104
+ * `gridSpan` is clamped at READ time and layout never re-derives it — the value comes from a file
2105
+ * and would otherwise be a loop bound an attacker controls.
2106
+ */
2107
+ interface SemanticTableCell {
2108
+ readonly id: string;
2109
+ /** Derived content-edge geometry for a verified legacy full-width parent table. */
2110
+ readonly legacyContentAlignment?: true;
2111
+ /** Clamped to [1, MAX_TABLE_COLUMNS] at read time; layout never re-derives it. */
2112
+ readonly gridSpan: number;
1801
2113
  /**
1802
- * Resource identities of the drawings the clip DROPPED, in flow order.
2114
+ * Absolute grid column this cell starts on, after `w:gridBefore` and every preceding
2115
+ * span. Structural conditional formats and cell geometry both key on this, never on the
2116
+ * cell's position in the row: one `gridSpan` cell otherwise shifts firstCol/lastCol and
2117
+ * the vertical bands for every cell after it.
2118
+ */
2119
+ readonly gridColumn: number;
2120
+ /** Canonical `w:gridCol` node id for this cell's start column, when the grid is authored. */
2121
+ readonly gridColumnId?: string;
2122
+ /** A vMerge cell that is not the restart continues the cell above: box, no content. */
2123
+ readonly vMergeContinue: boolean;
2124
+ /** `w:vAlign` — defaults to top when omitted/unrecognised. */
2125
+ readonly vAlign: CellVerticalAlign;
2126
+ /** `w:textDirection`; unsupported values keep horizontal layout. */
2127
+ readonly textDirection: 'horizontal' | 'btLr';
2128
+ /** Resolved per-side margins (tcMar over tblCellMar over the table style over Word's default). */
2129
+ readonly margins: CellMarginsPt;
2130
+ /** Three-state authored `tcBorders` (omitted / none / edge). */
2131
+ readonly borders: CellBorderBox;
2132
+ /** Validated 6-hex shading fill, absent for none/auto. */
2133
+ readonly shading?: string;
2134
+ /**
2135
+ * `w:tcW` — the width this cell asked for, as authored.
1803
2136
  *
1804
- * The furniture invalidation token walks laid-out records, and a dropped fragment leaves
1805
- * none — while a `withPageContext` projection of the same story can wrap differently
1806
- * (PAGE digits) and keep the drawing. Without this a picture clipped out of the baseline
1807
- * but painted by a projection never reaches the session context, so its decode settling
1808
- * moves nothing and the unchanged-pass early exit reuses the placeholder pages forever
1809
- * (#467). Absent when the clip dropped no drawings, so the common story keys byte-for-byte
1810
- * as before.
2137
+ * Published for consumers that need the cell's own statement (a column-resize handle has
2138
+ * to write back to it). Column geometry is NOT derived from this field: the resolver works
2139
+ * from a flat claim list built in the same pass, because resolving a column means looking
2140
+ * at every cell that covers it across every row, not at one cell at a time. Read
2141
+ * `columnWidthsPt` for what the table actually laid out.
1811
2142
  */
1812
- readonly clippedResourceToken?: string;
1813
- }
1814
-
1815
- interface InlineDrawingRecord {
1816
- readonly kind: 'inlineDrawing';
1817
- readonly drawingNodeId: string;
1818
- readonly paragraphId: string;
1819
- readonly ownerPartName: string;
1820
- readonly start: number;
1821
- /** Left edge of the extent box (slot + distL). */
1822
- readonly x: number;
1823
- readonly y: number;
1824
- readonly width: number;
1825
- readonly height: number;
1826
- readonly distL: number;
1827
- readonly distR: number;
1828
- readonly distT: number;
1829
- readonly distB: number;
1830
- /** Caret/hit advance start (slot left, before distL). */
1831
- readonly advanceStart: number;
1832
- /** Caret/hit advance end (slot + totalWidth). */
1833
- readonly advanceEnd: number;
1834
- readonly baselineOffset: number;
1835
- readonly paintBounds: LayoutBox;
1836
- readonly hitBounds: LayoutBox;
1837
- readonly geometry: DrawingGeometry;
1838
- readonly resource: ImageResourceState;
1839
- readonly accessibility: DrawingAccessibility;
1840
- /** Sanitized external hyperlink projection; inert until an explicit gesture activates it. */
1841
- readonly hyperlinkHref: string | null;
1842
- readonly effects: Readonly<{
1843
- readonly grayscale: boolean;
1844
- readonly brightness: number;
1845
- readonly contrast: number;
1846
- }>;
1847
- readonly crop: SourceCrop;
1848
- readonly transform: DrawingTransform;
1849
- /** Fixed non-picture graphic kind for refusal labels (`chart`, `group`, …); null for pictures. */
1850
- readonly placeholderGraphicKind: string | null;
1851
- /** Typed solid-geometry payload for a renderable `wps:wsp` shape; null otherwise. */
1852
- readonly vectorShape: VectorShapeProjection | null;
2143
+ readonly preferredWidth: PreferredWidth;
1853
2144
  /**
1854
- * The revision wrappers enclosing the owning run, outermost first — the same stack spans
1855
- * carry, so paint and review chrome give a tracked picture the same cues as tracked text.
1856
- * Absent when the drawing is untracked.
2145
+ * What the table style says about this cell's paragraphs and runs (17.7.6.6) — a header
2146
+ * row's bold and centring live here, not in the cell's own properties.
1857
2147
  */
1858
- readonly revisions?: readonly RevisionAttribution[];
1859
- }
1860
- interface InlineDrawingLayoutContext {
1861
- readonly ownerPartName: string;
1862
- /** Precomputed run-level atom id (`w:drawing` or MC wrapper) → projection. */
1863
- readonly projectionForAtom?: (atomNodeId: string) => DrawingProjection | null;
1864
- readonly project: (drawing: OoxmlDrawingNode) => DrawingProjection | null;
1865
- readonly resourceOf: (projection: DrawingProjection) => ImageResourceState;
2148
+ readonly styleFormatting: TableCellStyleFormatting;
2149
+ /** Block children in reading order, with content-control wrappers flattened. */
2150
+ readonly blocks: readonly OoxmlElement[];
1866
2151
  }
1867
- /** Named fallback when a positioning frame cannot be resolved (OpenSpec 4.6). */
1868
- type AnchoredDrawingLayoutFallback = 'unresolvable-frame' | 'page-defer-exhausted';
1869
- interface AnchoredDrawingRecord extends Omit<InlineDrawingRecord, 'kind' | 'baselineOffset' | 'advanceStart' | 'advanceEnd' | 'distL' | 'distR' | 'distT' | 'distB'> {
1870
- readonly kind: 'anchoredDrawing';
1871
- readonly anchorParagraphId: string;
1872
- readonly horizontalFrame: DrawingHorizontalReferenceFrame;
1873
- readonly verticalFrame: DrawingVerticalReferenceFrame;
1874
- readonly horizontalFrameOrigin: number;
1875
- readonly verticalFrameOrigin: number;
1876
- readonly behindDocument: boolean;
1877
- readonly allowOverlap: boolean;
1878
- readonly layoutInCell: boolean;
1879
- readonly relativeHeight: number;
1880
- readonly wrap: Exclude<ImageWrapTarget, 'inline'>;
1881
- readonly layoutFallback?: AnchoredDrawingLayoutFallback;
1882
- /** Canonical document traversal index within the owner story part. */
1883
- readonly sourceOrder?: number;
2152
+ /** One row in the resolved structure: its cells, its height rule, and any row-level revision. */
2153
+ interface SemanticTableRow {
2154
+ readonly id: string;
2155
+ /** Pending Word row insertion/deletion authored in `w:trPr`. */
2156
+ readonly revisionKind?: 'insert' | 'delete';
1884
2157
  /**
1885
- * Laid-out textbox story for a `wps:txbx` drawing; paint renders it clipped inside the
1886
- * extent instead of a placeholder. Absent when the drawing carries no story or the host
1887
- * did not thread story layout (the record then degrades to the placeholder path).
2158
+ * The `w:trPr/w:ins|w:del` attribution, carried with the kind so a painted row can say
2159
+ * WHOSE pending decision it is — the review model addresses the decision by exactly this
2160
+ * `(id, author, date)` triple, and a surface with only the kind could highlight the row
2161
+ * but never open its card.
1888
2162
  */
1889
- readonly textboxStory?: TextboxStoryLayout;
1890
- }
1891
-
1892
- /**
1893
- * One parsed `HYPERLINK` instruction: the raw, unsanitized pieces.
1894
- *
1895
- * `target` and `anchor` are verbatim from the instruction — the projector at the trust
1896
- * boundary decides what, if anything, they become in a DOM sink.
1897
- */
1898
- interface HyperlinkFieldSpec {
1899
- /** The target as authored (first quoted or bare non-switch token), or null. */
1900
- readonly target: string | null;
1901
- /** `\l` — a bookmark name in this document, or null. */
1902
- readonly anchor: string | null;
1903
- /** `\o` — the hover tooltip, or null. */
1904
- readonly tooltip: string | null;
2163
+ readonly revisionId?: string;
2164
+ readonly revisionAuthor?: string;
2165
+ readonly revisionDate?: string;
2166
+ /** `w:trPr/w:tblHeader` — the row repeats atop each page the table continues onto. */
2167
+ readonly isHeader: boolean;
2168
+ /**
2169
+ * `w:trPr/w:cantSplit` — the row must stay on one page. When it cannot fit a fresh page,
2170
+ * layout fails closed rather than fragmenting or overflowing the content box.
2171
+ */
2172
+ readonly cantSplit: boolean;
2173
+ /** `w:trPr/w:trHeight` — auto / atLeast floor / exact (clipped) row height. */
2174
+ readonly height: TableRowHeight;
2175
+ readonly cells: readonly SemanticTableCell[];
1905
2176
  }
1906
-
1907
2177
  /**
1908
- * What a piece says about the FIELD result it came from, for Word's shading.
2178
+ * A table resolved into a rectangular grid: column widths, rows, and the widths it asked for.
1909
2179
  *
1910
- * Carried from layout rather than decided at paint time because only the walk knows an atom was
1911
- * a field at all — by paint the result is just text. Whether the shading is actually drawn is a
1912
- * view decision made downstream, so this states the fact and nothing about the appearance.
2180
+ * The grid is normalized here so layout never has to reconcile `w:gridCol` against actual cell
2181
+ * spans — vertical merges and column spans are already accounted for.
1913
2182
  */
1914
- interface FieldAtomMarker {
2183
+ interface SemanticTableStructure {
2184
+ readonly columnWidthsPt: readonly number[];
2185
+ readonly rows: readonly SemanticTableRow[];
2186
+ /** Verified pre-2013 content-aligned full-width inline table; derived, never serialized. */
2187
+ readonly legacyContentAlignment?: true;
2188
+ /** `w:tblPr/w:tblW` — the width the table asked for. */
2189
+ readonly tableWidth: PreferredWidth;
1915
2190
  /**
1916
- * A legacy form field: `w:fldChar/w:ffData` (FORMTEXT, FORMCHECKBOX, FORMDROPDOWN).
1917
- *
1918
- * Word shades these on a different rule from ordinary fields — always, unless the document
1919
- * turns it off — because they mark the blanks somebody is meant to fill in.
2191
+ * `w:tblInd` (17.4.50) in points — "this indentation should shift the table into the text
2192
+ * margin by the specified amount". Applies to a left-aligned table; `w:jc` decides the
2193
+ * placement outright for the other two.
1920
2194
  */
1921
- readonly formField: boolean;
2195
+ readonly indentPt: number;
2196
+ /** `w:tblPr/w:jc` (17.4.29) — where the table sits in the text column. */
2197
+ readonly alignment: TableAlignment;
1922
2198
  /**
1923
- * A BODY PAGE / NUMPAGES / SECTIONPAGES atom whose value depends on pagination.
1924
- *
1925
- * The paragraph walk cannot know which page the field lands on — layout runs before the page
1926
- * count — so it paints a placeholder and records the field's kind here. Document finalize
1927
- * (`substituteBodyPageFields`) reads this marker and substitutes the real value per page.
1928
- * Absent in headers/footers, which evaluate live through their own per-page projector.
2199
+ * `w:tblPr/w:tblpPr` (17.4.57) — present when the table is positioned against an anchor
2200
+ * box. Placement then comes from {@link tableFloatOriginX} rather than `w:jc`/`w:tblInd`.
1929
2201
  */
1930
- readonly pageField?: {
1931
- readonly kind: AllowlistedPageField;
1932
- /** The field's `\#` numeric picture, applied when finalize substitutes the value. */
1933
- readonly picture?: string;
1934
- };
2202
+ readonly float?: TableFloatPosition;
1935
2203
  /**
1936
- * A BODY `PAGEREF` atom whose value is the page number its bookmark target lands on.
1937
- *
1938
- * Deferred exactly like {@link pageField}: the paragraph walk paints the field's cached
1939
- * result (or the placeholder digit when the file cached none) and records the resolved
1940
- * target here; document finalize substitutes the number of the page hosting the target's
1941
- * first fragment, gated per field on the calibration verdict.
2204
+ * `w:tblCellSpacing` (17.4.45) in points: the gap between adjacent cell edges. Applied as
2205
+ * a half-gap inset on each side of every cell, so cells separate visually without the grid
2206
+ * itself moving. Word ALSO grows the table's overall width by the spacing it adds around
2207
+ * the outside; that part is not modelled, so a spaced table is laid out on the same grid
2208
+ * its file states rather than a wider one.
1942
2209
  */
1943
- readonly pageRef?: PageRefFieldProjection;
1944
- }
1945
- /** A half-open model-offset range, in the paragraph's own UTF-16 offset space. */
1946
- interface ModelRange {
1947
- readonly start: number;
1948
- readonly end: number;
2210
+ readonly cellSpacingPt: number;
2211
+ /**
2212
+ * `w:tblPr/w:tblLayout/@w:type="fixed"` (17.4.52 — 17.4.53 is the `w:tblPrEx` exception
2213
+ * variant, not this element). Fixed layout takes the grid as final;
2214
+ * anything else is autofit, which in Word never renders wider than the text column.
2215
+ */
2216
+ readonly layoutFixed: boolean;
2217
+ /** Table-level `tblBorders` (three-state, including insideH/insideV). */
2218
+ readonly tableBorders: TableBorderBox;
2219
+ /** Table-level `tblCellMar` defaults (per-side, Word's own default when a side is omitted). */
2220
+ readonly defaultMargins: CellMarginsPt;
1949
2221
  }
1950
2222
  /**
1951
- * How layout turns a typed `w:hyperlink` node into the sanitized record spans carry.
1952
- *
1953
- * Injected rather than computed here because resolving `r:id` needs the PACKAGE's
1954
- * relationships and this module only ever sees one part's tree. `null` means the caller
1955
- * declined to project — the runs still measure and paint, they simply carry no link, which is
1956
- * the right degradation: text is never lost for want of a target.
1957
- */
1958
- type HyperlinkProjector = (link: OoxmlNode) => SpanLinkRecord | null;
1959
- /**
1960
- * How layout turns a parsed HYPERLINK field instruction into the sanitized record spans carry.
1961
- *
1962
- * Injected for the same reason as {@link HyperlinkProjector}: the spec's raw target must cross
1963
- * the surface's ONE href trust boundary, and layout owns no sanitization policy. `null` means
1964
- * no link — the cached result still paints as plain text, which is the right degradation.
2223
+ * Read one typed table node into a bounded structure, or null when the node is not a
2224
+ * typed table or sits beyond the nesting ceiling.
1965
2225
  */
1966
- type FieldLinkProjector = (spec: HyperlinkFieldSpec) => SpanLinkRecord | null;
2226
+ declare function readTableStructure(table: OoxmlNode, contentWidthPt: number, depth: number, styleCascade?: StyleCascadeTable,
2227
+ /** Which revisions the view resolves away; only the proposed result performs the join. */
2228
+ displayMode?: RevisionDisplayMode, authorFilter?: RevisionAuthorFilter, compatibilityMode?: number): SemanticTableStructure | null;
1967
2229
 
1968
2230
  interface EquationGeometryBase {
1969
2231
  readonly box: LayoutBox;
@@ -2384,6 +2646,21 @@ interface ParagraphFragmentRecord {
2384
2646
  readonly paragraphId: string;
2385
2647
  /** 0 for the first fragment of the paragraph, 1 for its continuation, and so on. */
2386
2648
  readonly fragmentIndex: number;
2649
+ /** A fixed text frame clips its painted ink to this fragment's box; source ranges remain intact. */
2650
+ readonly clipToBox?: true;
2651
+ /** A positioned text frame retains its source identity without consuming body flow height. */
2652
+ readonly outOfFlow?: true;
2653
+ /** Placement and wrapping of one authored text-frame group, in page-content coordinates. */
2654
+ readonly positionedFrame?: {
2655
+ readonly anchorId: string;
2656
+ readonly columnIndex: number;
2657
+ readonly groupId: string;
2658
+ readonly sourceOrder: number;
2659
+ readonly wrap: 'around' | 'none' | 'notBeside';
2660
+ readonly hSpace: number;
2661
+ readonly vSpace: number;
2662
+ readonly box: LayoutBox;
2663
+ };
2387
2664
  readonly range: SourceRange;
2388
2665
  readonly props: readonly OoxmlProperty[];
2389
2666
  /** Resolved paragraph style after the style/default cascade, or null when none applies. */
@@ -2543,6 +2820,13 @@ interface ListMarkerRecord {
2543
2820
  */
2544
2821
  interface TableFragmentRecord {
2545
2822
  readonly kind: 'table';
2823
+ /** Source and positioning inputs needed to reconstruct wrapping on an incremental pass. */
2824
+ readonly floatingWrap?: {
2825
+ readonly anchorId: string;
2826
+ readonly columnIndex: number;
2827
+ readonly float: TableFloatPosition;
2828
+ readonly sourceOrder: number;
2829
+ };
2546
2830
  readonly id: string;
2547
2831
  /** Canonical node id of the `w:tbl`. */
2548
2832
  readonly tableId: string;
@@ -2914,4 +3198,4 @@ interface TextMeasurer {
2914
3198
  };
2915
3199
  }
2916
3200
 
2917
- export { type AnchoredDrawingLayoutFallback as $, type AnchoredDrawingRecord as A, type BlockFragmentRecord as B, type ContentControlBoundaryRecord as C, type DrawingPoint as D, type FixedPoint as E, type FontResourceSnapshot as F, type SourceRange as G, type HeaderFooterStoryRecord as H, type InlineDrawingRecord as I, type HyperlinkProjector as J, type InlineDrawingLayoutContext as K, type LineRecord as L, type ModelRange as M, type NormalizationPolicy as N, type FieldLinkProjector as O, type PageGeometry as P, type StoryPageFieldNeeds as Q, type ResolvedRunStyle as R, type SemanticLayout as S, type TextMeasurer as T, type RefFieldContext as U, type VersionedShapingLibrary as V, type HyperlinkFieldSpec as W, type SpanLinkRecord as X, type ResolvedListItem as Y, AUTO_PARAGRAPH_SPACING_PT as Z, type AbstractNumDefinition as _, type StyleSpanRecord as a, type ParagraphLayoutInputs as a$, type BidiEmbeddingLevels as a0, type BorderGridGeometry as a1, type CascadedParagraphFormatting as a2, type ContentControlGeometryFragment as a3, type ContentControlLevel as a4, type ContentControlMappedType as a5, DEFAULT_PAGE_GEOMETRY as a6, DEFAULT_RUN_STYLE as a7, DEFAULT_TAB_INTERVAL_PT as a8, DEFAULT_TAB_INTERVAL_TWIPS as a9, type LevelOverride as aA, type LineSpacingRule as aB, type ListMarkerAlign as aC, type ListMarkerRecord as aD, type ListSuffix as aE, MAX_BORDER_SPACE_PT as aF, MAX_BORDER_WIDTH_PT as aG, MAX_LVL_OVERRIDES as aH, MAX_NUMBERING_DEFINITIONS as aI, MAX_PARAGRAPH_SPACING_PT as aJ, MAX_STYLE_BASED_ON_DEPTH as aK, MAX_STYLE_DEFINITIONS as aL, MAX_TABLE_BORDER_STROKES as aM, MAX_TAB_POSITION_TWIPS as aN, MAX_TAB_STOPS as aO, type NumDefinition as aP, type NumberingLevelIndent as aQ, PARAGRAPH_BORDER_SIDES as aR, type PageRefCalibrationCell as aS, type PageRefFieldProjection as aT, type ParagraphAutoSpacingContext as aU, type ParagraphBorderEdge as aV, type ParagraphBorderSide as aW, type ParagraphBorderStrokeRecord as aX, type ParagraphBorders as aY, type ParagraphBottomBorderRecord as aZ, type ParagraphIndent as a_, type DeclaredFontSubstitution as aa, type DrawingClipFallback as ab, type DrawingGeometry as ac, EMPTY_NUMBERING_INDEX as ad, EMPTY_TAB_STOPS as ae, type EquationFractionGeometry as af, type EquationGeometry as ag, type EquationNaryGeometry as ah, type EquationRadicalGeometry as ai, type EquationRowGeometry as aj, type EquationScriptGeometry as ak, type EquationSpanRecord as al, type EquationTextGeometry as am, type FieldAtomMarker as an, type FontFingerprintInputs as ao, FontResolutionError as ap, type FontResolutionErrorCode as aq, type FontResourceDefinition as ar, type FontResourceInstrumentation as as, type FontResourceSnapshotOptions as at, type FontSlot as au, type FontValidationResult as av, type GlyphOutline as aw, HARD_MAX_AGGREGATE_FONT_BYTES as ax, HARD_MAX_FONT_BYTES as ay, HARD_MAX_FONT_SOURCES as az, type TableRowFragmentRecord as b, isValidStyleId as b$, type ParagraphLineSpacing as b0, type ParagraphSpacing as b1, type RefFieldSpec as b2, type RefNoteParts as b3, type ResolvedCellBorders as b4, type ResolvedTabStops as b5, type ResolvedTableBorderEdge as b6, type ResolvedTableBorderEdgeSegment as b7, type ResolvedUnderline as b8, SINGLE_LINE_SPACING as b9, type TextDirection as bA, type TextboxStoryFallbackReason as bB, type TextboxStoryLayout as bC, UnsupportedScriptError as bD, type VerticalAlign as bE, appliedSpaceBefore as bF, applyLineSpacing as bG, baselineShiftPtOf as bH, borderExtentPt as bI, borderWeight as bJ, bottomBorderExtentPt as bK, boundedStructuralFontValidator as bL, buildNumberingIndex as bM, buildStyleCascadeTable as bN, cascadeParagraphFormatting as bO, cascadeRunProperties as bP, cascadedBottomBorder as bQ, cascadedParagraphBorders as bR, cascadedTabStops as bS, collapsedSpaceBefore as bT, createFontResourceSnapshot as bU, createShapedRun as bV, createShapingEnvironment as bW, defaultTabIntervalFromSettings as bX, displayText as bY, fixedPoint as bZ, fontRequestKey as b_, type ScriptItem as ba, type SemanticArtifactRootStoryKind as bb, type SemanticArtifactStoryKind as bc, type SemanticCommentArtifactRecord as bd, type SemanticReviewArtifactOccurrence as be, type SemanticReviewArtifactOccurrenceGeometry as bf, type SemanticReviewArtifactPosition as bg, type SemanticReviewArtifactSource as bh, type SemanticTrackedChangeArtifactRecord as bi, type ShapeInput as bj, type ShapedCluster as bk, type ShapedFontSpan as bl, type ShapedGlyph as bm, type ShapedRunComparatorInputs as bn, type ShapedVerticalMetrics as bo, type ShapingEnvironment as bp, type ShapingEnvironmentFingerprintInputs as bq, type StyleDefinition as br, TAB_LEADER_GLYPH as bs, type TabAlignment as bt, type TabDestination as bu, type TabLeader as bv, type TabStop as bw, type TableBorderSideName as bx, type TableBorderStrokeRecord as by, type TableFragmentRecord as bz, type TableCellFragmentRecord as c, itemizeScriptFontSlots as c0, layoutEquation as c1, listMarkerBox as c2, measureDisplayText as c3, mergeListIndent as c4, nextTabDestination as c5, paragraphBorderExtentPt as c6, paragraphBorderStrokeWidthPt as c7, paragraphBorders as c8, paragraphBordersFingerprint as c9, withNumberingStyleLinks as cA, withResolvedListItems as cB, paragraphBreaksBefore as ca, paragraphContextualSpacing as cb, paragraphLineSpacing as cc, paragraphSpacing as cd, paragraphTabStops as ce, parseRefInstruction as cf, readBorderSide as cg, readCellBorders as ch, readNumPr as ci, readTableBorders as cj, resolveBorderConflict as ck, resolveNumberingLevel as cl, resolveParagraphLayoutInputs as cm, resolveRunStyle as cn, resolveStoryListItems as co, resolveStoryRefFields as cp, resolveTableCellBorderGrid as cq, runStylesEqual as cr, shapedRunComparatorInputs as cs, shapingEnvironmentFingerprint as ct, shapingEnvironmentFingerprintInputs as cu, styleForFontSlot as cv, tabAdvanceWidth as cw, tabStopsFingerprint as cx, walkStoryParagraphs as cy, withDefaultTabInterval as cz, type ShapingEnvironmentInput as d, type TextShaper as e, type ResolvedFont as f, type FixedPointRoundingMode as g, type FontRequest as h, type SemanticReviewArtifactRecord as i, type FontSubstitution as j, type ShapedRun as k, type NumberingLevel as l, type NumberingIndex as m, type LayoutBox as n, type DrawingInsets as o, type TableBorderSide as p, type CellBorderBox as q, type TableCellStyleFormatting as r, type TableBorderBox as s, type StyleCascadeTable as t, type PageRecord as u, type ParagraphFragmentRecord as v, type NoteAreaRecord as w, type NoteStoryRecord as x, type ContentControlLock as y, type FontByteValidator as z };
3201
+ export { type BidiEmbeddingLevels as $, type AnchoredDrawingRecord as A, type BlockFragmentRecord as B, type ContentControlBoundaryRecord as C, type DrawingPoint as D, type HyperlinkProjector as E, type FontResourceSnapshot as F, type InlineDrawingLayoutContext as G, type HeaderFooterStoryRecord as H, type InlineDrawingRecord as I, type FieldLinkProjector as J, type StoryPageFieldNeeds as K, type LineRecord as L, type ModelRange as M, type NormalizationPolicy as N, type RefFieldContext as O, type PageGeometry as P, type HyperlinkFieldSpec as Q, type ResolvedRunStyle as R, type SemanticLayout as S, type TextMeasurer as T, type SpanLinkRecord as U, type VersionedShapingLibrary as V, type ResolvedListItem as W, AUTO_PARAGRAPH_SPACING_PT as X, AUTO_PREFERRED_WIDTH as Y, type AbstractNumDefinition as Z, type AnchoredDrawingLayoutFallback as _, type StyleSpanRecord as a, type ParagraphAutoSpacingContext as a$, type BorderGridGeometry as a0, CELL_PAD as a1, type CascadedParagraphFormatting as a2, type CellBorderBox as a3, type CellMarginsPt as a4, type CellVerticalAlign as a5, type CjkTypographySettings as a6, type ContentControlGeometryFragment as a7, type ContentControlLevel as a8, type ContentControlMappedType as a9, type FontValidationResult as aA, type GlyphOutline as aB, HARD_MAX_AGGREGATE_FONT_BYTES as aC, HARD_MAX_FONT_BYTES as aD, HARD_MAX_FONT_SOURCES as aE, type LevelOverride as aF, type LineSpacingRule as aG, type ListMarkerAlign as aH, type ListMarkerRecord as aI, type ListSuffix as aJ, MAX_BORDER_SPACE_PT as aK, MAX_BORDER_WIDTH_PT as aL, MAX_LVL_OVERRIDES as aM, MAX_NUMBERING_DEFINITIONS as aN, MAX_PARAGRAPH_SPACING_PT as aO, MAX_STYLE_BASED_ON_DEPTH as aP, MAX_STYLE_DEFINITIONS as aQ, MAX_TABLE_BORDER_STROKES as aR, MAX_TABLE_NESTING as aS, MAX_TABLE_ROW_HEIGHT_PT as aT, MAX_TAB_POSITION_TWIPS as aU, MAX_TAB_STOPS as aV, type NumDefinition as aW, type NumberingLevelIndent as aX, PARAGRAPH_BORDER_SIDES as aY, type PageRefCalibrationCell as aZ, type PageRefFieldProjection as a_, DEFAULT_CELL_MARGINS as aa, DEFAULT_PAGE_GEOMETRY as ab, DEFAULT_RUN_STYLE as ac, DEFAULT_TAB_INTERVAL_PT as ad, DEFAULT_TAB_INTERVAL_TWIPS as ae, type DeclaredFontSubstitution as af, type DrawingClipFallback as ag, type DrawingGeometry as ah, EMPTY_NUMBERING_INDEX as ai, EMPTY_TAB_STOPS as aj, type EquationFractionGeometry as ak, type EquationGeometry as al, type EquationNaryGeometry as am, type EquationRadicalGeometry as an, type EquationRowGeometry as ao, type EquationScriptGeometry as ap, type EquationSpanRecord as aq, type EquationTextGeometry as ar, type FieldAtomMarker as as, type FontFingerprintInputs as at, FontResolutionError as au, type FontResolutionErrorCode as av, type FontResourceDefinition as aw, type FontResourceInstrumentation as ax, type FontResourceSnapshotOptions as ay, type FontSlot as az, type TableRowFragmentRecord as b, boundedStructuralFontValidator as b$, type ParagraphBorderEdge as b0, type ParagraphBorderSide as b1, type ParagraphBorderStrokeRecord as b2, type ParagraphBorders as b3, type ParagraphBottomBorderRecord as b4, type ParagraphIndent as b5, type ParagraphLayoutInputs as b6, type ParagraphLineSpacing as b7, type ParagraphSpacing as b8, type PreferredWidth as b9, type ShapingEnvironment as bA, type ShapingEnvironmentFingerprintInputs as bB, type StyleDefinition as bC, TAB_LEADER_GLYPH as bD, type TabAlignment as bE, type TabDestination as bF, type TabLeader as bG, type TabStop as bH, type TableAlignment as bI, type TableBorderBox as bJ, type TableBorderSideName as bK, type TableBorderStrokeRecord as bL, type TableCellStyleFormatting as bM, type TableFragmentRecord as bN, type TableRowHeight as bO, type TableRowHeightRule as bP, type TextDirection as bQ, type TextboxStoryFallbackReason as bR, type TextboxStoryLayout as bS, UnsupportedScriptError as bT, type VerticalAlign as bU, appliedSpaceBefore as bV, applyLineSpacing as bW, baselineShiftPtOf as bX, borderExtentPt as bY, borderWeight as bZ, bottomBorderExtentPt as b_, type PreferredWidthType as ba, type RefFieldSpec as bb, type RefNoteParts as bc, type ResolvedCellBorders as bd, type ResolvedTabStops as be, type ResolvedTableBorderEdge as bf, type ResolvedTableBorderEdgeSegment as bg, type ResolvedUnderline as bh, SINGLE_LINE_SPACING as bi, type ScriptItem as bj, type SemanticArtifactRootStoryKind as bk, type SemanticArtifactStoryKind as bl, type SemanticCommentArtifactRecord as bm, type SemanticReviewArtifactOccurrence as bn, type SemanticReviewArtifactOccurrenceGeometry as bo, type SemanticReviewArtifactPosition as bp, type SemanticReviewArtifactSource as bq, type SemanticTableCell as br, type SemanticTableRow as bs, type SemanticTrackedChangeArtifactRecord as bt, type ShapeInput as bu, type ShapedCluster as bv, type ShapedFontSpan as bw, type ShapedGlyph as bx, type ShapedRunComparatorInputs as by, type ShapedVerticalMetrics as bz, type TableCellFragmentRecord as c, buildNumberingIndex as c0, buildStyleCascadeTable as c1, cascadeParagraphFormatting as c2, cascadeRunProperties as c3, cascadedBottomBorder as c4, cascadedParagraphBorders as c5, cascadedTabStops as c6, collapsedSpaceBefore as c7, createFontResourceSnapshot as c8, createShapedRun as c9, readTableStructure as cA, resolveBorderConflict as cB, resolveNumberingLevel as cC, resolveParagraphLayoutInputs as cD, resolveRunStyle as cE, resolveStoryListItems as cF, resolveStoryRefFields as cG, resolveTableCellBorderGrid as cH, runStylesEqual as cI, shapedRunComparatorInputs as cJ, shapingEnvironmentFingerprint as cK, shapingEnvironmentFingerprintInputs as cL, styleForFontSlot as cM, tabAdvanceWidth as cN, tabStopsFingerprint as cO, walkStoryParagraphs as cP, withDefaultTabInterval as cQ, withNumberingStyleLinks as cR, withResolvedListItems as cS, createShapingEnvironment as ca, defaultTabIntervalFromSettings as cb, displayText as cc, fixedPoint as cd, fontRequestKey as ce, isValidStyleId as cf, itemizeScriptFontSlots as cg, layoutEquation as ch, listMarkerBox as ci, measureDisplayText as cj, mergeListIndent as ck, nextTabDestination as cl, paragraphBorderExtentPt as cm, paragraphBorderStrokeWidthPt as cn, paragraphBorders as co, paragraphBordersFingerprint as cp, paragraphBreaksBefore as cq, paragraphContextualSpacing as cr, paragraphLineSpacing as cs, paragraphSpacing as ct, paragraphTabStops as cu, parseRefInstruction as cv, readBorderSide as cw, readCellBorders as cx, readNumPr as cy, readTableBorders as cz, type ShapingEnvironmentInput as d, type TextShaper as e, type ResolvedFont as f, type FixedPointRoundingMode as g, type FontRequest as h, type SemanticReviewArtifactRecord as i, type FontSubstitution as j, type ShapedRun as k, type NumberingLevel as l, type NumberingIndex as m, type ParagraphFragmentRecord as n, type LayoutBox as o, type DrawingInsets as p, type TableBorderSide as q, type PageRecord as r, type SemanticTableStructure as s, type NoteAreaRecord as t, type NoteStoryRecord as u, type ContentControlLock as v, type FontByteValidator as w, type FixedPoint as x, type SourceRange as y, type StyleCascadeTable as z };