@docx-editor.dev/core 0.0.1 → 2.1.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 (116) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +91 -2
  3. package/THIRD_PARTY_NOTICES.md +18 -0
  4. package/dist/automation.cjs +1 -0
  5. package/dist/automation.d.cts +74 -0
  6. package/dist/automation.d.ts +74 -0
  7. package/dist/automation.js +1 -0
  8. package/dist/binding.cjs +1 -0
  9. package/dist/binding.d.cts +88 -0
  10. package/dist/binding.d.ts +88 -0
  11. package/dist/binding.js +1 -0
  12. package/dist/chunk-2DTQDAN7.js +2 -0
  13. package/dist/chunk-3JKQE4QJ.cjs +2 -0
  14. package/dist/chunk-4WWEDTTE.js +2 -0
  15. package/dist/chunk-6WIIHJFY.js +1 -0
  16. package/dist/chunk-7VQ7YLW3.cjs +1 -0
  17. package/dist/chunk-BS554B6D.cjs +2 -0
  18. package/dist/chunk-BZK64YU3.cjs +10 -0
  19. package/dist/chunk-CZFUMACF.cjs +12 -0
  20. package/dist/chunk-GQQOL4PV.js +1 -0
  21. package/dist/chunk-HBL3NNH5.cjs +1 -0
  22. package/dist/chunk-JQFDLZXQ.cjs +2 -0
  23. package/dist/chunk-O2WQY7IT.cjs +5 -0
  24. package/dist/chunk-PC76F2HR.cjs +1 -0
  25. package/dist/chunk-QRAUDDVY.js +2 -0
  26. package/dist/chunk-REZUWYB7.js +1 -0
  27. package/dist/chunk-STCITBJK.js +12 -0
  28. package/dist/chunk-TBBXCC7D.js +5 -0
  29. package/dist/chunk-TSXT537P.cjs +12 -0
  30. package/dist/chunk-WOGYVM3W.js +12 -0
  31. package/dist/chunk-Y7TA7TPF.js +10 -0
  32. package/dist/content-control-walk-CbTqHq6O.d.cts +56 -0
  33. package/dist/content-control-walk-D06GijR0.d.ts +56 -0
  34. package/dist/contracts/document.cjs +1 -0
  35. package/dist/contracts/document.d.cts +246 -0
  36. package/dist/contracts/document.d.ts +246 -0
  37. package/dist/contracts/document.js +0 -0
  38. package/dist/contracts/editor.cjs +1 -0
  39. package/dist/contracts/editor.d.cts +11 -0
  40. package/dist/contracts/editor.d.ts +11 -0
  41. package/dist/contracts/editor.js +1 -0
  42. package/dist/contracts/interaction.cjs +1 -0
  43. package/dist/contracts/interaction.d.cts +11 -0
  44. package/dist/contracts/interaction.d.ts +11 -0
  45. package/dist/contracts/interaction.js +0 -0
  46. package/dist/contracts/modules.cjs +1 -0
  47. package/dist/contracts/modules.d.cts +117 -0
  48. package/dist/contracts/modules.d.ts +117 -0
  49. package/dist/contracts/modules.js +1 -0
  50. package/dist/contracts/types.cjs +1 -0
  51. package/dist/contracts/types.d.cts +452 -0
  52. package/dist/contracts/types.d.ts +452 -0
  53. package/dist/contracts/types.js +0 -0
  54. package/dist/custom-node-writes-YqISlSRl.d.cts +885 -0
  55. package/dist/custom-node-writes-ttKuYd0M.d.ts +885 -0
  56. package/dist/editor-B93PggnR.d.cts +1690 -0
  57. package/dist/editor-CFsSrxGR.d.ts +1690 -0
  58. package/dist/editor.cjs +1 -0
  59. package/dist/editor.css +7106 -0
  60. package/dist/editor.d.cts +271 -0
  61. package/dist/editor.d.ts +271 -0
  62. package/dist/editor.js +1 -0
  63. package/dist/hf-references-BdjsQNE_.d.ts +71 -0
  64. package/dist/hf-references-CgEOSw3i.d.cts +71 -0
  65. package/dist/image-resources-C_YnqPhn.d.ts +203 -0
  66. package/dist/image-resources-DwpNnTg3.d.cts +203 -0
  67. package/dist/index-BjZelfWL.d.cts +2532 -0
  68. package/dist/index-DP1yJJ3R.d.ts +2532 -0
  69. package/dist/index.cjs +1 -0
  70. package/dist/index.d.cts +22 -0
  71. package/dist/index.d.ts +22 -0
  72. package/dist/index.js +1 -0
  73. package/dist/layout.cjs +1 -0
  74. package/dist/layout.d.cts +2366 -0
  75. package/dist/layout.d.ts +2366 -0
  76. package/dist/layout.js +1 -0
  77. package/dist/note-nodes-BPSTVDMr.d.ts +108 -0
  78. package/dist/note-nodes-C6DLxlvj.d.cts +108 -0
  79. package/dist/ooxml-package-CR5us6ad.d.cts +354 -0
  80. package/dist/ooxml-package-D_CtywTW.d.ts +354 -0
  81. package/dist/ooxml-tree-BU0e3DVi.d.cts +871 -0
  82. package/dist/ooxml-tree-BU0e3DVi.d.ts +871 -0
  83. package/dist/output.cjs +1 -0
  84. package/dist/output.d.cts +158 -0
  85. package/dist/output.d.ts +158 -0
  86. package/dist/output.js +1 -0
  87. package/dist/protocol-BWVA3Q1p.d.ts +1255 -0
  88. package/dist/protocol-FAz_nPm_.d.cts +1255 -0
  89. package/dist/review-reads-CsoXSvUg.d.ts +401 -0
  90. package/dist/review-reads-qW0jvdF1.d.cts +401 -0
  91. package/dist/review-support-D64Wu236.d.cts +358 -0
  92. package/dist/review-support-nsLXC9jf.d.ts +358 -0
  93. package/dist/revision-projection-72ZApe-g.d.cts +84 -0
  94. package/dist/revision-projection-DHAkcD_L.d.ts +84 -0
  95. package/dist/semantic-cell-selection-D_8RbPII.d.ts +427 -0
  96. package/dist/semantic-cell-selection-DwLB71fF.d.cts +427 -0
  97. package/dist/semantic-interaction-CXH2wFqi.d.ts +187 -0
  98. package/dist/semantic-interaction-DqcEonqD.d.cts +187 -0
  99. package/dist/semantic-paint-drawings-B5Wk2so5.d.ts +21 -0
  100. package/dist/semantic-paint-drawings-x8g44lD_.d.cts +21 -0
  101. package/dist/semantic-records-COVFW5aS.d.cts +1505 -0
  102. package/dist/semantic-records-zTC0_1zi.d.ts +1505 -0
  103. package/dist/shaped-measurer-AFnFxoot.d.cts +613 -0
  104. package/dist/shaped-measurer-CZXL-a6v.d.ts +613 -0
  105. package/dist/store.cjs +1 -0
  106. package/dist/store.d.cts +2153 -0
  107. package/dist/store.d.ts +2153 -0
  108. package/dist/store.js +1 -0
  109. package/dist/tree-op-types-DWLCllG7.d.ts +1486 -0
  110. package/dist/tree-op-types-UOXlRUqQ.d.cts +1486 -0
  111. package/dist/tree-session-BtVFXsF6.d.ts +580 -0
  112. package/dist/tree-session-DxIOHUOD.d.cts +580 -0
  113. package/dist/tree-store-B1lEkdXc.d.cts +273 -0
  114. package/dist/tree-store-BscFPB7x.d.ts +273 -0
  115. package/package.json +110 -8
  116. package/index.js +0 -1
@@ -0,0 +1,1505 @@
1
+ import { O as OoxmlProperty, j as DrawingAccessibility, S as SourceCrop, k as DrawingTransform, V as VectorShapeProjection, f as DrawingHorizontalReferenceFrame, g as DrawingVerticalReferenceFrame, b as ImageWrapTarget, D as DrawingProjection, l as TableBorderStyle } from './tree-op-types-DWLCllG7.js';
2
+ import { O as OoxmlNode, o as OoxmlDrawingNode, a as OoxmlElement } from './ooxml-tree-BU0e3DVi.js';
3
+ import { I as ImageResourceState } from './image-resources-C_YnqPhn.js';
4
+ import { a as RevisionAttribution } from './revision-projection-DHAkcD_L.js';
5
+
6
+ /** Whether a paragraph must start a new page (`w:pageBreakBefore`). */
7
+ declare function paragraphBreaksBefore(props: readonly OoxmlProperty[]): boolean;
8
+ /**
9
+ * Soft ceiling matching the spike's resolved-style limit (31_680 twips ≈ 22"). Beyond
10
+ * that an attacker-authored spacing would push pagination into pathological page counts.
11
+ */
12
+ declare const MAX_PARAGRAPH_SPACING_PT: number;
13
+ /** Soft ceiling on border width (96 eighths = 12pt). Word's UI tops out well below this. */
14
+ declare const MAX_BORDER_WIDTH_PT = 12;
15
+ /** Soft ceiling on border-to-text gap (`w:space`, already in points). */
16
+ declare const MAX_BORDER_SPACE_PT = 3168;
17
+ /**
18
+ * A paragraph's resolved space before and after, in points.
19
+ *
20
+ * Already collapsed against `w:contextualSpacing`, so adjacent same-style paragraphs that suppress
21
+ * their gap arrive here with it removed rather than leaving that to whoever stacks them.
22
+ */
23
+ interface ParagraphSpacing {
24
+ /** `w:spacing/@before`, in points. */
25
+ readonly before: number;
26
+ /** `w:spacing/@after`, in points. */
27
+ readonly after: number;
28
+ }
29
+ /**
30
+ * The gap Word substitutes when `w:beforeAutospacing` / `w:afterAutospacing` is on
31
+ * (ECMA-376 §17.3.1.2, §17.3.1.13).
32
+ *
33
+ * The attribute means "the consumer decides", and the authored `@before` / `@after` beside it
34
+ * is IGNORED rather than used as the value. Word's answer is HTML's default `<p>` margin,
35
+ * 14pt, which is what a document round-tripped through Word's HTML filter carries — and this
36
+ * one is everywhere, because Word writes `w:before="100" w:beforeAutospacing="1"` for it. Reading
37
+ * only the literal 100 twips lays every such paragraph out 9pt tight, which moves page breaks.
38
+ */
39
+ declare const AUTO_PARAGRAPH_SPACING_PT = 14;
40
+ /**
41
+ * Where a paragraph sits, for the two contexts in which Word's auto spacing resolves to 0
42
+ * instead of {@link AUTO_PARAGRAPH_SPACING_PT}.
43
+ *
44
+ * Both come from the HTML model the attribute emulates: a `<li>` and a `<td>` collapse the
45
+ * paragraph margin, a bare `<p>` does not. A caller that says nothing gets the body answer.
46
+ */
47
+ interface ParagraphAutoSpacingContext {
48
+ /** The paragraph participates in numbering (`w:numPr`), i.e. it is a list item. */
49
+ readonly inList?: boolean;
50
+ /** The paragraph lives in a table cell. */
51
+ readonly inTableCell?: boolean;
52
+ }
53
+ /**
54
+ * Resolved line spacing (`w:spacing/@line` + `@lineRule`, ECMA-376 17.3.1.33).
55
+ *
56
+ * `auto` is the interesting one: `@line` is 240ths of a line, so 240 is single, 360 is
57
+ * one-and-a-half, 480 is double — and Word's own Normal style since 2013 is 259, i.e.
58
+ * 1.08. A document laid out at a flat single spacing is ~8% tight on EVERY line, which
59
+ * moves every page break, so this is not a cosmetic detail.
60
+ *
61
+ * `exact` fixes the line box at `@line` twips and lets tall glyphs clip, the way Word
62
+ * does. `atLeast` uses it as a floor.
63
+ */
64
+ type LineSpacingRule = 'auto' | 'exact' | 'atLeast';
65
+ /**
66
+ * Resolved line spacing: the rule, and the value it applies.
67
+ *
68
+ * `value` means different things per rule — 240ths of a line under `auto`, points under `exact`
69
+ * and `atLeast` — which is why the two travel together and neither is useful alone.
70
+ */
71
+ interface ParagraphLineSpacing {
72
+ readonly rule: LineSpacingRule;
73
+ /** `auto`: the 240ths-of-a-line multiplier numerator. Otherwise points. */
74
+ readonly value: number;
75
+ }
76
+ /** Single spacing: what a paragraph that says nothing gets. */
77
+ declare const SINGLE_LINE_SPACING: ParagraphLineSpacing;
78
+ /**
79
+ * One resolved `w:pBdr` edge: its style, colour, thickness and gap.
80
+ *
81
+ * `widthPt` and `spacePt` are already converted and CLAMPED — both come from a file, and an
82
+ * unbounded border width becomes a layout dimension.
83
+ */
84
+ interface ParagraphBorderEdge {
85
+ /** Authored `ST_Border` value (`single`, `dashed`, …). */
86
+ readonly val: string;
87
+ /** RRGGBB, or null when auto/missing (paint defaults to black). */
88
+ readonly color: string | null;
89
+ /** Border thickness in points (`w:sz` is eighths of a point). */
90
+ readonly widthPt: number;
91
+ /** Gap from text to the rule, in points (`w:space`). */
92
+ readonly spacePt: number;
93
+ /**
94
+ * `w:shadow` — Word offsets a drop shadow behind the rule.
95
+ *
96
+ * Present only when authored true, so an edge that says nothing keeps the shape earlier
97
+ * fixtures assert. Resolved and carried; drawing it is deferred.
98
+ */
99
+ readonly shadow?: true;
100
+ }
101
+ /** The six `CT_PBdr` children, in schema order (ECMA-376 §17.3.1.24). */
102
+ declare const PARAGRAPH_BORDER_SIDES: readonly ["top", "left", "bottom", "right", "between", "bar"];
103
+ /**
104
+ * Which of the six `CT_PBdr` edges.
105
+ *
106
+ * Four are physical box edges; `between` and `bar` are group-relative, drawn only where
107
+ * consecutive paragraphs share a border definition.
108
+ */
109
+ type ParagraphBorderSide = (typeof PARAGRAPH_BORDER_SIDES)[number];
110
+ /**
111
+ * A paragraph's resolved `w:pBdr` (ECMA-376 §17.3.1.24).
112
+ *
113
+ * `top`/`left`/`bottom`/`right` are the four physical edges of the box. The other two are
114
+ * group-relative: `between` draws at a boundary INSIDE a run of consecutive paragraphs whose
115
+ * border settings are identical, and `bar` is the vertical change-bar rule beside the
116
+ * paragraph, drawn whether or not the paragraph groups with its neighbours.
117
+ */
118
+ interface ParagraphBorders {
119
+ readonly top?: ParagraphBorderEdge;
120
+ readonly left?: ParagraphBorderEdge;
121
+ readonly bottom?: ParagraphBorderEdge;
122
+ readonly right?: ParagraphBorderEdge;
123
+ readonly between?: ParagraphBorderEdge;
124
+ readonly bar?: ParagraphBorderEdge;
125
+ }
126
+ /**
127
+ * Resolve `w:spacing` before/after from flat paragraph properties.
128
+ *
129
+ * Line spacing (`w:line` / `w:lineRule`) is a separate concern — it changes measured line
130
+ * height, not the gap between paragraphs — and is not resolved here.
131
+ *
132
+ * `w:beforeAutospacing` / `w:afterAutospacing` REPLACE the authored measurement on their own
133
+ * side rather than adding to it; see {@link AUTO_PARAGRAPH_SPACING_PT}.
134
+ */
135
+ declare function paragraphSpacing(props: readonly OoxmlProperty[], context?: ParagraphAutoSpacingContext): ParagraphSpacing;
136
+ /**
137
+ * Resolve `w:line` / `w:lineRule` from flat paragraph properties.
138
+ *
139
+ * Merged per attribute for the same reason as before/after: `w:spacing` is one element
140
+ * carrying independent attributes, and a style that states only `@line` must not reset the
141
+ * rule an earlier entry in the cascade set.
142
+ */
143
+ declare function paragraphLineSpacing(props: readonly OoxmlProperty[]): ParagraphLineSpacing;
144
+ /**
145
+ * Apply resolved line spacing to a line's natural (glyph-derived) box.
146
+ *
147
+ * Word places `auto` / `atLeast` extras BELOW the line (the last line's multiple spacing
148
+ * still separates it from the next paragraph). Putting that delta above inverted cover-page
149
+ * rhythm: `w:line="460"` on "between" opened a large gap above the word and almost none
150
+ * before "MERIDIAN". `exact` taller than the glyphs centers the text (ECMA-376 17.3.1.33).
151
+ * An `exact` box smaller than the glyphs keeps the baseline inside so clipped text still
152
+ * sits on it.
153
+ */
154
+ declare function applyLineSpacing(spacing: ParagraphLineSpacing, naturalHeight: number, naturalBaseline: number): {
155
+ height: number;
156
+ baseline: number;
157
+ };
158
+ /**
159
+ * `w:contextualSpacing` (17.3.1.9): drop before/after between paragraphs of the SAME
160
+ * style. Word's built-in `ListParagraph` sets it, so every list authored in Word gets a
161
+ * paragraph gap between items without this.
162
+ */
163
+ declare function paragraphContextualSpacing(props: readonly OoxmlProperty[]): boolean;
164
+ /**
165
+ * Resolve `w:pBdr` from the paragraph-properties node.
166
+ *
167
+ * Nested — every edge is a child of `pBdr`, not an attribute — so this reads the typed tree
168
+ * rather than the flattened `OoxmlProperty[]` bag `propertiesOf` builds for leaf props.
169
+ */
170
+ declare function paragraphBorders(pPr: OoxmlNode | undefined): ParagraphBorders;
171
+ /**
172
+ * `w:pBdr` after the style cascade: a later `w:pBdr` replaces an earlier one WHOLESALE.
173
+ *
174
+ * Word does not merge edges across the cascade. A style that states only `w:bottom` discards
175
+ * the box its `basedOn` ancestor declared, so folding edge by edge would leave a lone
176
+ * underline surrounded by a box no one authored. Absence inherits; `nil`/`none` clear.
177
+ */
178
+ declare function cascadedParagraphBorders(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorders;
179
+ /**
180
+ * Visual stroke thickness layout publishes for one edge (points).
181
+ *
182
+ * Compound `ST_Border` values (`double`, …) use the shared inflated band so a thin
183
+ * `w:sz="3"` double still occupies a visible double-line box — matching table borders.
184
+ */
185
+ declare function paragraphBorderStrokeWidthPt(edge: ParagraphBorderEdge): number;
186
+ /**
187
+ * Extent one border edge occupies away from the text it decorates: gap plus rule, in points.
188
+ *
189
+ * Vertically that is flow height — a top rule pushes the first line down, a bottom rule holds
190
+ * the page open below the last one — so pagination has to see it. Horizontally it is
191
+ * publish-only: Word draws left/right paragraph rules OUTSIDE the text column and never
192
+ * re-breaks the lines, which is why adding a box to a paragraph in Word does not reflow it.
193
+ */
194
+ declare function paragraphBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
195
+ /** Vertical extent a bottom border adds below the last line (gap + rule). */
196
+ declare function bottomBorderExtentPt(edge: ParagraphBorderEdge | undefined): number;
197
+ /**
198
+ * Identity of a paragraph's border set, for the `w:between` group rule.
199
+ *
200
+ * Word treats consecutive paragraphs whose border settings are IDENTICAL as ONE bordered
201
+ * block: the top rule draws above the first, the bottom rule below the last, and each
202
+ * interior boundary gets `w:between` or nothing (§17.3.1.24). That is why applying a box to
203
+ * three selected paragraphs in Word draws one box and not three.
204
+ *
205
+ * Empty string means "no borders", which never groups with anything.
206
+ */
207
+ declare function paragraphBordersFingerprint(borders: ParagraphBorders): string;
208
+ /**
209
+ * Gap to insert before a paragraph once the previous paragraph's `after` is already in the
210
+ * flow cursor — Word takes the larger of the two rather than summing them.
211
+ */
212
+ declare function collapsedSpaceBefore(before: number, previousAfter: number): number;
213
+ /**
214
+ * Applied before-spacing for placement (Word 2013+ / compat mode 15).
215
+ *
216
+ * Adjacent before/after still collapse to the larger gap, but before is dropped entirely when
217
+ * the paragraph begins at the top of a page mid-section. The first paragraph of a document or
218
+ * section retains before. Callers publish this applied value on the fragment so shading, borders,
219
+ * selection, and paint share one geometry.
220
+ */
221
+ declare function appliedSpaceBefore(before: number, previousAfter: number, atTopOfPage: boolean, firstParagraphOfSection: boolean): number;
222
+
223
+ /** Soft ceiling matching Word's practical custom-tab UI limit. */
224
+ declare const MAX_TAB_STOPS = 64;
225
+ /**
226
+ * Soft ceiling on a tab position (31_680 twips ≈ 22"), matching paragraph-spacing bounds so
227
+ * a hostile stop cannot shove layout into pathological widths.
228
+ */
229
+ declare const MAX_TAB_POSITION_TWIPS = 31680;
230
+ /** OOXML / Word default when `w:settings/w:defaultTabStop` is absent: 720 twips = 0.5". */
231
+ declare const DEFAULT_TAB_INTERVAL_TWIPS = 720;
232
+ /** The default tab interval in points — {@link DEFAULT_TAB_INTERVAL_TWIPS} converted. */
233
+ declare const DEFAULT_TAB_INTERVAL_PT: number;
234
+ /**
235
+ * How a tab stop positions the text that follows it.
236
+ *
237
+ * Only `left` is a plain cursor jump. The other three size the tab glyph from the MEASURED
238
+ * following segment, so its end, centre or decimal point lands on the stop.
239
+ */
240
+ type TabAlignment = 'left' | 'center' | 'right' | 'decimal';
241
+ /**
242
+ * `w:tab/@w:leader` (ECMA-376 §17.3.1.38, ST_TabTlc): the character repeated across the
243
+ * space a tab reserves. `none` is the default and is represented by an absent leader.
244
+ *
245
+ * This is the difference between a Word table of contents and a column of headings floating
246
+ * next to a column of page numbers, so it is carried through layout to paint rather than
247
+ * dropped as a geometry-irrelevant attribute.
248
+ */
249
+ type TabLeader = 'dot' | 'hyphen' | 'underscore' | 'heavy' | 'middleDot';
250
+ /**
251
+ * The character each leader repeats (§17.3.1.38, ST_TabTlc).
252
+ *
253
+ * Lives with the type rather than with the painter because LAYOUT has to measure it: a
254
+ * leader is the same character typed over and over, and the only way to space it the way
255
+ * typing it would is to ask the measurer how wide it actually is.
256
+ */
257
+ declare const TAB_LEADER_GLYPH: ReadonlyMap<TabLeader, string>;
258
+ /** One authored `w:tab`: where it sits, how it aligns, and what fills the gap. */
259
+ interface TabStop {
260
+ /** Position from the paragraph content origin, in points. */
261
+ readonly positionPt: number;
262
+ readonly alignment: TabAlignment;
263
+ /** Absent for `none` — the schema default and the overwhelming majority of stops. */
264
+ readonly leader?: TabLeader;
265
+ }
266
+ /**
267
+ * A paragraph's tab stops after the style cascade, with the default interval that applies past
268
+ * the last explicit one.
269
+ */
270
+ interface ResolvedTabStops {
271
+ /** Custom stops sorted by ascending position. */
272
+ readonly stops: readonly TabStop[];
273
+ /** Default-tab interval in points (always positive and bounded). */
274
+ readonly defaultIntervalPt: number;
275
+ }
276
+ /** The frozen "no custom stops" value, so a paragraph without tabs mints no object. */
277
+ declare const EMPTY_TAB_STOPS: ResolvedTabStops;
278
+ /**
279
+ * Resolve tab stops from cascaded `w:pPr` nodes (docDefaults → style chain → direct).
280
+ *
281
+ * Each `w:tabs` merges with `clear` support; absence inherits. The leader travels with the
282
+ * stop that declared it — a `clear` at the same position discards both together.
283
+ */
284
+ declare function cascadedTabStops(paragraphPropertyNodes: readonly OoxmlNode[]): ResolvedTabStops;
285
+ /** Direct `w:pPr` only — used when no style cascade table is present. */
286
+ declare function paragraphTabStops(pPr: OoxmlNode | undefined): ResolvedTabStops;
287
+ /**
288
+ * Republish stops under a document-wide default-tab interval (`w:defaultTabStop`).
289
+ *
290
+ * The cascade resolves stops from the paragraph's own property chain, which cannot see
291
+ * `settings.xml`; the interval is a document constant that arrives from the session. Returns
292
+ * the input unchanged when nothing moves, so a cache-key fingerprint stays stable.
293
+ */
294
+ declare function withDefaultTabInterval(tabs: ResolvedTabStops, defaultIntervalPt: number | undefined): ResolvedTabStops;
295
+ /**
296
+ * Read `w:settings/w:defaultTabStop` (ECMA-376 §17.15.1.25), in points.
297
+ *
298
+ * Word's own interval, not a constant: a metric-locale template writes `w:val="1134"` (2cm)
299
+ * and every default-interval tab in the document lands on that grid instead of the 0.5"
300
+ * one. The value is FILE-DERIVED, so a non-integer, non-positive or out-of-range `val` falls
301
+ * back to the schema default rather than being trusted into layout arithmetic.
302
+ *
303
+ * `ST_TwipsMeasure` also admits a universal measure (`"2cm"`); Word writes plain twips, and
304
+ * the spelled form falls back to the default rather than being parsed here.
305
+ */
306
+ declare function defaultTabIntervalFromSettings(settings: OoxmlNode | null | undefined): number;
307
+ /** Where one tab character lands: the resolved x, and the stop that decided it. */
308
+ interface TabDestination {
309
+ readonly positionPt: number;
310
+ readonly alignment: TabAlignment;
311
+ /** Leader of the stop that was reached; absent for `none` and for default-interval tabs. */
312
+ readonly leader?: TabLeader;
313
+ }
314
+ /**
315
+ * Next tab destination strictly past `currentX`, preferring custom stops then the default
316
+ * interval. Destination is clamped to `rightEdge` so stops cannot escape the content box.
317
+ */
318
+ declare function nextTabDestination(tabs: ResolvedTabStops, currentX: number, rightEdge: number): TabDestination;
319
+ /**
320
+ * Width of a tab glyph so the following segment lands on the destination.
321
+ *
322
+ * `segmentWidth` / `decimalOffset` are already measured in points. Decimal offset is the
323
+ * advance from the segment start to the decimal point (0 when none — treated like right).
324
+ */
325
+ declare function tabAdvanceWidth(alignment: TabAlignment, currentX: number, destinationX: number, segmentWidth: number, decimalOffset: number): number;
326
+ /**
327
+ * Stable fingerprint for layout cache keys — nested `w:tabs` are not in flat `OoxmlProperty`
328
+ * bags, so style-inherited stops must be named explicitly or breaks would collide.
329
+ */
330
+ declare function tabStopsFingerprint(tabs: ResolvedTabStops): string;
331
+
332
+ /** `w:vertAlign` — script position, which also scales the run's effective size. */
333
+ type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
334
+ /** A resolved underline: its variant, and its colour when it does not follow the text. */
335
+ interface ResolvedUnderline {
336
+ /** The authored `ST_Underline` variant. */
337
+ readonly variant: string;
338
+ /** RRGGBB, or null when the underline follows the text colour. */
339
+ readonly color: string | null;
340
+ }
341
+ /**
342
+ * A run's character properties after the full cascade, in the units layout works in.
343
+ *
344
+ * Points rather than half-points, RRGGBB rather than theme references — everything already
345
+ * resolved, so measurement and paint never re-run the cascade per glyph.
346
+ */
347
+ interface ResolvedRunStyle {
348
+ readonly fontFamily: string | null;
349
+ /** Points. `w:sz` is half-points, so 22 becomes 11. */
350
+ readonly fontSizePt: number;
351
+ /** RRGGBB, or null for the inherited/automatic colour. */
352
+ readonly color: string | null;
353
+ readonly bold: boolean;
354
+ readonly italic: boolean;
355
+ readonly underline: ResolvedUnderline | null;
356
+ readonly strike: boolean;
357
+ readonly doubleStrike: boolean;
358
+ /** An `ST_HighlightColor` name, or null. */
359
+ readonly highlight: string | null;
360
+ /**
361
+ * Character shading fill (`w:rPr/w:shd`), validated RRGGBB, or null.
362
+ *
363
+ * Paint applies this as the glyph-box background; a recognised highlight overrides it.
364
+ * Measurement ignores shading.
365
+ */
366
+ readonly shading: string | null;
367
+ readonly verticalAlign: VerticalAlign;
368
+ /** `w:position`, in points. Positive raises the baseline. */
369
+ readonly baselineShiftPt: number;
370
+ readonly caps: boolean;
371
+ readonly smallCaps: boolean;
372
+ /** `w:spacing`, in points. Added to every advance. */
373
+ readonly characterSpacingPt: number;
374
+ /** `w:w`, as a percentage. 100 is unscaled. */
375
+ readonly horizontalScalePercent: number;
376
+ /** `w:kern`, in points: the size at or above which kerning applies. 0 disables it. */
377
+ readonly kerningMinPt: number;
378
+ /**
379
+ * `w:vanish` (ECMA-376 §17.3.2.45): the run is hidden text.
380
+ *
381
+ * Word does not draw it AND does not paginate it — hidden index or comment text takes no
382
+ * space at all. So this cannot be a paint-time opacity: a hidden run that is still measured
383
+ * pushes every following line, and every following page break, to the wrong place. Layout
384
+ * drops the content instead (see `piecesOfParagraph`).
385
+ *
386
+ * `w:specVanish` (§17.3.2.36) is a different property — an always-hidden paragraph mark on
387
+ * a heading — and never sets this.
388
+ */
389
+ readonly hidden: boolean;
390
+ }
391
+ /** The style a run inherits when it authors nothing. */
392
+ declare const DEFAULT_RUN_STYLE: ResolvedRunStyle;
393
+ /**
394
+ * The theme part's two Latin typefaces, for resolving `w:rFonts` theme attributes.
395
+ *
396
+ * Structurally identical to the binding lane's `DocumentThemeFonts` and assignable from it.
397
+ * Declared here so this lane reads two validated strings rather than the theme tree.
398
+ */
399
+ interface ThemeFonts {
400
+ /** `a:majorFont` latin typeface — headings. */
401
+ readonly major: string | null;
402
+ /** `a:minorFont` latin typeface — body text. */
403
+ readonly minor: string | null;
404
+ }
405
+ /**
406
+ * Resolve one run's direct formatting.
407
+ *
408
+ * Unrecognised values are DROPPED rather than guessed: a `w:sz` of `"large"` leaves the
409
+ * default size rather than inventing one, because a wrong measurement moves every glyph
410
+ * after it and a missing one is visible immediately.
411
+ *
412
+ * `themeFonts` resolves `w:rFonts` theme references. Absent, a theme-only `rFonts` leaves
413
+ * the family inherited — which is what every run of a theme-fonted document does, so the
414
+ * whole document falls back to the surface default face.
415
+ */
416
+ declare function resolveRunStyle(props: readonly OoxmlProperty[], themeFonts?: ThemeFonts): ResolvedRunStyle;
417
+ /** The text as it is DRAWN, after case transforms. Measurement must use this, not the source. */
418
+ declare function displayText(text: string, style: ResolvedRunStyle): string;
419
+ /** Measure run text the way layout breaks lines and paints glyphs (caps/small-caps aware). */
420
+ declare function measureDisplayText(text: string, style: ResolvedRunStyle, measurer: TextMeasurer): number;
421
+ /** Whether two resolved styles are identical, for span merging and cache keys. */
422
+ declare function runStylesEqual(a: ResolvedRunStyle, b: ResolvedRunStyle): boolean;
423
+ /**
424
+ * How far a run's glyphs are lifted off the line's baseline, in points. Positive is up.
425
+ *
426
+ * Super and subscript move the GLYPHS without moving the run's box, so the box keeps tiling
427
+ * the line and the selection band stays continuous. Anything drawing at the glyphs — the
428
+ * painter, and the caret — has to apply this itself, and from one place, or the two drift.
429
+ */
430
+ declare function baselineShiftPtOf(style: ResolvedRunStyle): number;
431
+
432
+ interface DrawingPoint {
433
+ readonly x: number;
434
+ readonly y: number;
435
+ }
436
+ interface DrawingInsets {
437
+ readonly top: number;
438
+ readonly right: number;
439
+ readonly bottom: number;
440
+ readonly left: number;
441
+ }
442
+ type DrawingClipFallback = 'none' | 'unsupported-preset';
443
+ interface DrawingGeometry {
444
+ readonly contentBounds: LayoutBox;
445
+ readonly paintBounds: LayoutBox;
446
+ readonly hitBounds: LayoutBox;
447
+ readonly transformedCorners: readonly DrawingPoint[];
448
+ readonly clipPolygon: readonly DrawingPoint[] | null;
449
+ readonly clipFallback: DrawingClipFallback;
450
+ readonly effectInsets: DrawingInsets;
451
+ }
452
+
453
+ /**
454
+ * Why textbox story layout stopped short.
455
+ *
456
+ * Every one is a BOUND rather than a bug: nesting depth, fragment counts and the extent all
457
+ * come from a file. Falling back with a reason keeps the drawing rendered (clipped) instead
458
+ * of failing the layout pass.
459
+ */
460
+ type TextboxStoryFallbackReason = 'textbox-nesting-limit' | 'textbox-fragment-limit'
461
+ /** Flowed content is taller than the extent; trailing fragments were dropped. */
462
+ | 'textbox-height-clip';
463
+ /**
464
+ * One text box's story laid out inside its extent, in content-box-relative coordinates.
465
+ *
466
+ * Fragments origin at the content box's top-left; paint places the content box at
467
+ * `drawing origin + contentOffset` and clips to the extent.
468
+ */
469
+ interface TextboxStoryLayout {
470
+ /** Content-box-relative fragments (origin at the content box's top-left). */
471
+ readonly fragments: readonly BlockFragmentRecord[];
472
+ /** Height the blocks flow to (points), before vertical anchoring. */
473
+ readonly flowHeight: number;
474
+ /** Offset of the content box inside the drawing extent: insets plus vertical anchoring. */
475
+ readonly contentOffset: Readonly<{
476
+ x: number;
477
+ y: number;
478
+ }>;
479
+ /** Content box width (extent minus horizontal insets). */
480
+ readonly contentWidth: number;
481
+ /** Content box height (extent minus vertical insets). */
482
+ readonly contentHeight: number;
483
+ /** Solid fill of the hosting shape, painted behind the story; null for no fill. */
484
+ readonly fillHex: string | null;
485
+ /** Solid outline of the hosting shape; null for no outline. */
486
+ readonly strokeHex: string | null;
487
+ /** Outline width in points; 0 when absent. */
488
+ readonly strokeWidthPt: number;
489
+ /** True when layout hit a named bound and returned a truncated / empty story. */
490
+ readonly fallbackReason?: TextboxStoryFallbackReason;
491
+ }
492
+
493
+ interface InlineDrawingRecord {
494
+ readonly kind: 'inlineDrawing';
495
+ readonly drawingNodeId: string;
496
+ readonly paragraphId: string;
497
+ readonly ownerPartName: string;
498
+ readonly start: number;
499
+ /** Left edge of the extent box (slot + distL). */
500
+ readonly x: number;
501
+ readonly y: number;
502
+ readonly width: number;
503
+ readonly height: number;
504
+ readonly distL: number;
505
+ readonly distR: number;
506
+ readonly distT: number;
507
+ readonly distB: number;
508
+ /** Caret/hit advance start (slot left, before distL). */
509
+ readonly advanceStart: number;
510
+ /** Caret/hit advance end (slot + totalWidth). */
511
+ readonly advanceEnd: number;
512
+ readonly baselineOffset: number;
513
+ readonly paintBounds: LayoutBox;
514
+ readonly hitBounds: LayoutBox;
515
+ readonly geometry: DrawingGeometry;
516
+ readonly resource: ImageResourceState;
517
+ readonly accessibility: DrawingAccessibility;
518
+ /** Sanitized external hyperlink projection; inert until an explicit gesture activates it. */
519
+ readonly hyperlinkHref: string | null;
520
+ readonly effects: Readonly<{
521
+ readonly grayscale: boolean;
522
+ readonly brightness: number;
523
+ readonly contrast: number;
524
+ }>;
525
+ readonly crop: SourceCrop;
526
+ readonly transform: DrawingTransform;
527
+ /** Fixed non-picture graphic kind for refusal labels (`chart`, `group`, …); null for pictures. */
528
+ readonly placeholderGraphicKind: string | null;
529
+ /** Typed solid-geometry payload for a renderable `wps:wsp` shape; null otherwise. */
530
+ readonly vectorShape: VectorShapeProjection | null;
531
+ }
532
+ interface InlineDrawingLayoutContext {
533
+ readonly ownerPartName: string;
534
+ /** Precomputed run-level atom id (`w:drawing` or MC wrapper) → projection. */
535
+ readonly projectionForAtom?: (atomNodeId: string) => DrawingProjection | null;
536
+ readonly project: (drawing: OoxmlDrawingNode) => DrawingProjection | null;
537
+ readonly resourceOf: (projection: DrawingProjection) => ImageResourceState;
538
+ }
539
+ /** Named fallback when a positioning frame cannot be resolved (OpenSpec 4.6). */
540
+ type AnchoredDrawingLayoutFallback = 'unresolvable-frame' | 'page-defer-exhausted';
541
+ interface AnchoredDrawingRecord extends Omit<InlineDrawingRecord, 'kind' | 'baselineOffset' | 'advanceStart' | 'advanceEnd' | 'distL' | 'distR' | 'distT' | 'distB'> {
542
+ readonly kind: 'anchoredDrawing';
543
+ readonly anchorParagraphId: string;
544
+ readonly horizontalFrame: DrawingHorizontalReferenceFrame;
545
+ readonly verticalFrame: DrawingVerticalReferenceFrame;
546
+ readonly horizontalFrameOrigin: number;
547
+ readonly verticalFrameOrigin: number;
548
+ readonly behindDocument: boolean;
549
+ readonly allowOverlap: boolean;
550
+ readonly layoutInCell: boolean;
551
+ readonly relativeHeight: number;
552
+ readonly wrap: Exclude<ImageWrapTarget, 'inline'>;
553
+ readonly layoutFallback?: AnchoredDrawingLayoutFallback;
554
+ /** Canonical document traversal index within the owner story part. */
555
+ readonly sourceOrder?: number;
556
+ /**
557
+ * Laid-out textbox story for a `wps:txbx` drawing; paint renders it clipped inside the
558
+ * extent instead of a placeholder. Absent when the drawing carries no story or the host
559
+ * did not thread story layout (the record then degrades to the placeholder path).
560
+ */
561
+ readonly textboxStory?: TextboxStoryLayout;
562
+ }
563
+
564
+ /**
565
+ * Page-field evaluation context for furniture projection.
566
+ *
567
+ * `pageNumber` is the displayed PAGE value after section `w:pgNumType/@w:start` (1-based).
568
+ * `pageCount` is document NUMPAGES. `sectionPageCount` is SECTIONPAGES for the attached
569
+ * section. `format` is the authored `w:pgNumType/@w:fmt` applied only to PAGE.
570
+ */
571
+ interface FieldPageContext {
572
+ readonly pageNumber: number;
573
+ readonly pageCount: number;
574
+ /** SECTIONPAGES; defaults to `pageCount` when omitted (single-section callers). */
575
+ readonly sectionPageCount?: number;
576
+ /** Authored ST_NumberFormat for PAGE; absent → decimal. */
577
+ readonly format?: string;
578
+ }
579
+ /** A half-open model-offset range, in the paragraph's own UTF-16 offset space. */
580
+ interface ModelRange {
581
+ readonly start: number;
582
+ readonly end: number;
583
+ }
584
+ /**
585
+ * How layout turns a typed `w:hyperlink` node into the sanitized record spans carry.
586
+ *
587
+ * Injected rather than computed here because resolving `r:id` needs the PACKAGE's
588
+ * relationships and this module only ever sees one part's tree. `null` means the caller
589
+ * declined to project — the runs still measure and paint, they simply carry no link, which is
590
+ * the right degradation: text is never lost for want of a target.
591
+ */
592
+ type HyperlinkProjector = (link: OoxmlNode) => SpanLinkRecord | null;
593
+ /**
594
+ * Format a displayed PAGE value through the shared ST_NumberFormat resolver.
595
+ *
596
+ * Unknown / script-specific formats fall back to decimal (same convention as list markers).
597
+ * `none` / `bullet` are meaningless for page numbers and also fall back to decimal so a
598
+ * hostile fmt cannot blank the furniture.
599
+ */
600
+ declare function formatPageNumber(value: number, format: string | undefined): string;
601
+
602
+ /** Test-only mutable counters; omitted by production call sites. Not package-public. */
603
+ interface TableBorderGridResolveWork {
604
+ /** Sparse intervals emitted (not dense column slots). */
605
+ ownershipSlotsWritten: number;
606
+ columnLookups: number;
607
+ }
608
+ /** Shared remaining interval budget for nested table finalization in one layout. */
609
+ interface TableBorderOwnershipBudget {
610
+ intervalsRemaining: number;
611
+ }
612
+
613
+ /** Which physical edge of a box a border sits on. */
614
+ type TableBorderSideName = 'top' | 'right' | 'bottom' | 'left';
615
+ /**
616
+ * One border edge in one of three states.
617
+ *
618
+ * `omitted` and `none` are NOT the same: an omitted edge inherits from the table style or the
619
+ * neighbouring cell, while an explicit `none` wins the conflict and draws nothing. Collapsing
620
+ * them would let a style's border reappear where the author removed it.
621
+ */
622
+ type TableBorderSide = {
623
+ readonly state: 'omitted';
624
+ } | {
625
+ readonly state: 'none';
626
+ } | {
627
+ readonly state: 'edge';
628
+ readonly style: TableBorderStyle;
629
+ /** Validated RRGGBB, or null for auto/missing (paint defaults to black). */
630
+ readonly color: string | null;
631
+ /** Thickness in points (`w:sz` is eighths of a point). */
632
+ readonly widthPt: number;
633
+ };
634
+ /** A table's six authored border edges: four outer, plus the two interior intervals. */
635
+ interface TableBorderBox {
636
+ readonly top: TableBorderSide;
637
+ readonly left: TableBorderSide;
638
+ readonly bottom: TableBorderSide;
639
+ readonly right: TableBorderSide;
640
+ readonly insideH: TableBorderSide;
641
+ readonly insideV: TableBorderSide;
642
+ }
643
+ /** The four resolved edges of one cell, after conflict resolution against its neighbours. */
644
+ interface CellBorderBox {
645
+ readonly top: TableBorderSide;
646
+ readonly left: TableBorderSide;
647
+ readonly bottom: TableBorderSide;
648
+ readonly right: TableBorderSide;
649
+ }
650
+ /** Final edge winner; absent means the side/interval is not drawn. */
651
+ interface ResolvedTableBorderEdge {
652
+ readonly style: TableBorderStyle;
653
+ readonly color: string | null;
654
+ readonly widthPt: number;
655
+ }
656
+ /**
657
+ * Conflict winner over one grid interval of a cell side.
658
+ *
659
+ * `gridStart`/`gridEnd` are absolute grid column indices for horizontal sides and absolute
660
+ * row indices for vertical sides (half-open). `startPt`/`endPt` are cell-local along-axis
661
+ * positions in layout points.
662
+ */
663
+ interface ResolvedTableBorderEdgeSegment {
664
+ readonly side: TableBorderSideName;
665
+ readonly gridStart: number;
666
+ readonly gridEnd: number;
667
+ readonly startPt: number;
668
+ readonly endPt: number;
669
+ readonly edge: ResolvedTableBorderEdge;
670
+ }
671
+ /**
672
+ * One published stroke rectangle in cell-local layout points.
673
+ *
674
+ * Paint multiplies x/y/width/height by scale and draws — no metrics, gaps, or corner math.
675
+ */
676
+ interface TableBorderStrokeRecord {
677
+ readonly side: TableBorderSideName;
678
+ readonly role: 'outer' | 'inner' | 'middle' | 'edge';
679
+ readonly color: string | null;
680
+ /** CSS keyword for this stroke (compound strokes are always solid). */
681
+ readonly cssStyle: 'solid' | 'dashed' | 'dotted';
682
+ readonly x: number;
683
+ readonly y: number;
684
+ readonly width: number;
685
+ readonly height: number;
686
+ }
687
+ /**
688
+ * Layout-owned cell borders after conflict resolution and compound expansion.
689
+ *
690
+ * Convenience `top`/`left`/`bottom`/`right` are set when that side has a single uniform
691
+ * full-span winner (existing consumers / CSS simple edges). Multi-interval winners live
692
+ * only on `edgeSegments`. Compound geometry is always on `strokes`.
693
+ */
694
+ interface ResolvedCellBorders {
695
+ readonly top?: ResolvedTableBorderEdge;
696
+ readonly left?: ResolvedTableBorderEdge;
697
+ readonly bottom?: ResolvedTableBorderEdge;
698
+ readonly right?: ResolvedTableBorderEdge;
699
+ readonly edgeSegments?: readonly ResolvedTableBorderEdgeSegment[];
700
+ readonly strokes?: readonly TableBorderStrokeRecord[];
701
+ }
702
+ /** Soft cap on published stroke rectangles per cell (security / pathological spans). */
703
+ declare const MAX_TABLE_BORDER_STROKES = 256;
704
+ /** Read one OOXML border child into the three-state model. */
705
+ declare function readBorderSide(node: OoxmlElement | undefined): TableBorderSide;
706
+ /**
707
+ * Read a table's `w:tblBorders`, dropping or clamping hostile values.
708
+ *
709
+ * Widths and colours come from a file: an out-of-range `w:sz` becomes a layout dimension, so it is
710
+ * bounded here rather than downstream.
711
+ */
712
+ declare function readTableBorders(tblPr: OoxmlElement | undefined): TableBorderBox;
713
+ /** Read one cell's `w:tcBorders`, under the same bounds {@link readTableBorders} applies. */
714
+ declare function readCellBorders(tcPr: OoxmlElement | undefined): CellBorderBox;
715
+ /**
716
+ * Conflict weight: the authored width in eighths of a point, and nothing else.
717
+ *
718
+ * Word-matching, not conformance — §17.4.39 (`w:tblBorders`) and §17.4.66 (`w:tcBorders`)
719
+ * describe the elements and specify no conflict algorithm. Word picks the heavier RULE, so
720
+ * a 6pt dashed rule beats a hairline single. Folding the style rank into the weight (an
721
+ * `sz × border-number` product) made a 0.5pt double outrank a 1pt single and made every
722
+ * dashed or dotted rule weigh 1 regardless of `w:sz`, which erased its width entirely.
723
+ */
724
+ declare function borderWeight(side: TableBorderSide): number;
725
+ /**
726
+ * Pick the winner between two candidates on a shared grid line (zero cell spacing).
727
+ *
728
+ * `none` loses to any edge; two `none`/omitted yield omitted (no paint). Width decides;
729
+ * equal widths rank by style, then prefer the darker color, then `preferFirst`
730
+ * (reading-order / first candidate).
731
+ */
732
+ declare function resolveBorderConflict(first: TableBorderSide, second: TableBorderSide, preferFirst?: boolean): TableBorderSide;
733
+ interface BorderGridCell {
734
+ readonly gridColumn: number;
735
+ readonly gridSpan: number;
736
+ readonly vMergeContinue: boolean;
737
+ readonly borders: CellBorderBox;
738
+ /** Set on restart cells that visually span into later rows. */
739
+ readonly mergeRowSpan?: number;
740
+ }
741
+ /**
742
+ * The absolute grid a table's borders are drawn on: column widths, and per-row tops and heights.
743
+ *
744
+ * Shared by every edge so adjacent cells resolve to the SAME line, rather than each computing its
745
+ * own and leaving a hairline gap between them.
746
+ */
747
+ interface BorderGridGeometry {
748
+ /** Absolute column widths for the whole table (points). */
749
+ readonly columnWidthsPt: readonly number[];
750
+ /**
751
+ * Per laid-out row: absolute top and height in the same coordinate space as cell boxes
752
+ * (only relative differences matter for vertical edge segmentation).
753
+ */
754
+ readonly rowBands: readonly {
755
+ readonly y: number;
756
+ readonly height: number;
757
+ }[];
758
+ /** Per laid-out cell: width/height after vMerge expansion (cell-local stroke space). */
759
+ readonly cellBoxes: readonly (readonly {
760
+ readonly width: number;
761
+ readonly height: number;
762
+ }[])[];
763
+ }
764
+ /**
765
+ * Resolve borders for every cell in a laid-out table fragment.
766
+ *
767
+ * Shared vertical edges: conflict(left.right, right.left) → assigned to the left cell,
768
+ * segmented per row when a vMerge restart faces differing neighbors.
769
+ * Shared horizontal edges: conflict(above.bottom, below.top) → assigned to the above cell,
770
+ * segmented per grid column when a gridSpan cell faces differing below neighbors; vMerge
771
+ * interior seams are suppressed per interval.
772
+ *
773
+ * When `geometry` is provided, compound edges expand into explicit stroke records with
774
+ * corner-adjusted endpoints in cell-local points.
775
+ */
776
+ declare function resolveTableCellBorderGrid(rows: readonly (readonly BorderGridCell[])[], table: TableBorderBox, columnCount: number, geometry?: BorderGridGeometry, work?: TableBorderGridResolveWork, ownershipBudget?: TableBorderOwnershipBudget): ResolvedCellBorders[][];
777
+ /** Width contribution of a resolved edge for content inset / row sizing. */
778
+ declare function borderExtentPt(edge: ResolvedTableBorderEdge | TableBorderSide | undefined): number;
779
+
780
+ /** A half-open UTF-16 range inside one paragraph, addressed by its canonical node id. */
781
+ interface SourceRange {
782
+ readonly paragraphId: string;
783
+ readonly start: number;
784
+ readonly end: number;
785
+ }
786
+ /**
787
+ * A rectangle in layout POINTS.
788
+ *
789
+ * Points everywhere in this layer — twips convert at property-read boundaries and CSS pixels at
790
+ * paint. A box carrying either of those would eventually be added to one carrying the other.
791
+ */
792
+ interface LayoutBox {
793
+ readonly x: number;
794
+ readonly y: number;
795
+ readonly width: number;
796
+ readonly height: number;
797
+ }
798
+ /**
799
+ * A bottom paragraph border as layout published it.
800
+ *
801
+ * `box` is the rule's geometry in the same coordinate space as the fragment (page-content
802
+ * relative). Paint positions from this box and MUST NOT remeasure the border.
803
+ */
804
+ interface ParagraphBottomBorderRecord {
805
+ readonly edge: ParagraphBorderEdge;
806
+ readonly box: LayoutBox;
807
+ }
808
+ /**
809
+ * One `w:pBdr` rule as layout published it.
810
+ *
811
+ * `box` is the STROKE rectangle in the same coordinate space as the fragment, so paint sets a
812
+ * position and a colour and nothing else. It matters that paint cannot derive these itself:
813
+ * Word draws the side rules OUTSIDE the text column, so `box.x` on a `left`/`bar` stroke is
814
+ * left of the fragment box and a painter reasoning from the fragment alone would put it
815
+ * inside the text.
816
+ */
817
+ interface ParagraphBorderStrokeRecord {
818
+ readonly side: ParagraphBorderSide;
819
+ readonly edge: ParagraphBorderEdge;
820
+ readonly box: LayoutBox;
821
+ }
822
+ /**
823
+ * The hyperlink a span sits inside, as layout resolved it.
824
+ *
825
+ * Already SANITIZED: `href` is the runtime projection produced once at the trust boundary,
826
+ * and `null` means the link is inert — a refused scheme, or a relationship the package does
827
+ * not declare. Paint, hit-testing and the popover consume this and never the authored target,
828
+ * so there is exactly one place a file-derived URL becomes something a browser can follow.
829
+ *
830
+ * `id` is the `w:hyperlink` node's canonical id, which is what makes the spans of one link
831
+ * recognisable as one link across the several lines it wraps onto — and what an unlink or a
832
+ * retarget addresses.
833
+ */
834
+ interface SpanLinkRecord {
835
+ readonly id: string;
836
+ readonly kind: 'external' | 'internal' | 'unresolved';
837
+ /** Sanitized runtime projection: an absolute URL, `#anchor`, or null when inert. */
838
+ readonly href: string | null;
839
+ /** Bookmark name for an internal link, so navigation need not re-parse the fragment. */
840
+ readonly anchor?: string;
841
+ /** `w:tooltip` — paint puts it on the anchor's `title`. */
842
+ readonly tooltip?: string;
843
+ }
844
+ /** A run of text on one line sharing identical resolved formatting. */
845
+ interface StyleSpanRecord {
846
+ readonly range: SourceRange;
847
+ readonly text: string;
848
+ /** The run's authored properties, retained as evidence. */
849
+ readonly props: readonly OoxmlProperty[];
850
+ /**
851
+ * The same properties RESOLVED — one unit system, defaults applied.
852
+ *
853
+ * Carried on the span so the measurer, the span and the painter all read one resolution
854
+ * rather than each deriving its own. Two derivations that disagree by a fraction of a
855
+ * point put the caret where no glyph is.
856
+ */
857
+ readonly style: ResolvedRunStyle;
858
+ readonly box: LayoutBox;
859
+ /**
860
+ * Cumulative advances from {@link box}.x to each UTF-16 caret boundary in {@link text}.
861
+ *
862
+ * Length is `text.length + 1` (both endpoints). Layout publishes these so hit-testing and
863
+ * the caret read the same per-cluster edges the span was measured with, rather than
864
+ * re-measuring a prefix at interaction time or interpolating across {@link box}.width —
865
+ * OpenSpec task 13.5. Absent on older records; consumers fall back to the measurer.
866
+ */
867
+ readonly caretEdges?: readonly number[];
868
+ /**
869
+ * `w:tab/@w:leader` of the stop a `\t` span advanced to (ECMA-376 §17.3.1.38).
870
+ *
871
+ * Only ever set on a tab span, and only for a non-`none` leader. Paint repeats the glyph
872
+ * across the advance THIS box already reserved — the leader adds no width of its own, so
873
+ * it can never move the text that follows it.
874
+ */
875
+ readonly tabLeader?: TabLeader;
876
+ /**
877
+ * Advance of ONE leader glyph in this run's face, in points, measured by layout.
878
+ *
879
+ * Paint cannot ask a font how wide a character is, so without this it had to guess and
880
+ * deliberately overfill, leaving the dots at whatever spacing an over-long string
881
+ * happened to produce. Measured, the leader is spaced exactly as the same character
882
+ * typed there would be — which is what Word draws.
883
+ */
884
+ readonly tabLeaderAdvancePt?: number;
885
+ /**
886
+ * The hyperlink this span belongs to, or absent for ordinary text.
887
+ *
888
+ * Carried on the SPAN rather than looked up at paint time because a link that wraps
889
+ * produces one set of spans per line, and paint has no paragraph to walk — only the line
890
+ * it was handed.
891
+ */
892
+ readonly link?: SpanLinkRecord;
893
+ /**
894
+ * Horizontal jump, in points, that a floating object's wrap zone forced before this span.
895
+ *
896
+ * A line that resumes on the far side of a float is still ONE line: its spans keep running
897
+ * model offsets and share a selection band. Only layout knows the jump is an obstacle
898
+ * rather than justification slack, so it publishes it here. Paint reserves it with an inert
899
+ * advance and excludes it from the word spacing it reconstructs — inferring the difference
900
+ * from the gap alone spread the float's whole width across every space on the line.
901
+ */
902
+ readonly wrapAdvanceBefore?: number;
903
+ /**
904
+ * The revision wrappers this text sits inside, outermost first, absent when untracked.
905
+ *
906
+ * Carried on the span for the same reason `style` is: paint and the review surface must read
907
+ * ONE attribution. A sidebar that re-derived which revision covers a span by walking the tree
908
+ * could disagree with what was painted, and the card would point at the wrong text.
909
+ */
910
+ readonly revisions?: readonly RevisionAttribution[];
911
+ /**
912
+ * Live PAGE/NUMPAGES/SECTIONPAGES projection (layout-time evaluated text).
913
+ *
914
+ * Not model-editable until typed fields land: paint treats these as atomic furniture and
915
+ * selection mapping refuses them the way it refuses markers.
916
+ */
917
+ readonly projected?: boolean;
918
+ /**
919
+ * Footnote/endnote navigation metadata for projected note atoms.
920
+ *
921
+ * Paint tags body citations (`to-note`) and note-body marks (`to-body`) so React chrome
922
+ * can jump without owning layout logic.
923
+ */
924
+ readonly noteNav?: {
925
+ readonly scopeId: string;
926
+ readonly direction: 'to-note' | 'to-body';
927
+ };
928
+ }
929
+ /**
930
+ * One laid-out line: its geometry, its baseline, and the styled spans it renders.
931
+ *
932
+ * The unit hit-testing and caret placement resolve against. Geometry comes from HERE, never from
933
+ * the DOM, which is what lets an empty paragraph still get a caret.
934
+ */
935
+ interface LineRecord {
936
+ readonly id: string;
937
+ readonly range: SourceRange;
938
+ readonly spans: readonly StyleSpanRecord[];
939
+ readonly box: LayoutBox;
940
+ /**
941
+ * Where the line's content actually starts, after alignment and the first-line indent.
942
+ *
943
+ * {@link box} is the content BAND the line was broken against — its `x` is the indented
944
+ * column edge and its `width` the available measure, neither of which moves with
945
+ * `w:jc`. Alignment is otherwise expressed only as x offsets on the span boxes, so a line
946
+ * with no spans (an empty paragraph) had no aligned origin at all: paint, hit testing and
947
+ * the caret each fell back to `box.x` and drew a centred empty paragraph's caret hard
948
+ * against the left margin, where it stayed until the first character was typed.
949
+ *
950
+ * Equal to the first span's x whenever there is one, so it is the single origin every
951
+ * consumer can read without a spans-or-box fallback of its own.
952
+ */
953
+ readonly contentX: number;
954
+ /** Distance from the line box top to the text baseline. */
955
+ readonly baseline: number;
956
+ /**
957
+ * Space ABOVE the glyph band inside {@link box} (exact centering, not auto/atLeast).
958
+ *
959
+ * `auto` / `atLeast` extras grow the box BELOW the glyphs — paint puts that depth in
960
+ * padding-bottom. {@link baseline} is measured from the line top and already includes
961
+ * this above-band when present.
962
+ *
963
+ * Published by layout rather than recovered by paint: three paint sites each deriving it
964
+ * separately is how they came to disagree about where a line's text sits.
965
+ */
966
+ readonly leading: number;
967
+ /**
968
+ * Auto/atLeast line-spacing depth BELOW the glyph band, inside {@link box}.
969
+ *
970
+ * The complement of {@link leading}: exact spacing centres the glyphs and moves the
971
+ * baseline down, while auto/atLeast leave the band at the top and grow the box beneath it.
972
+ * So the band a consumer needs is `box.height - trailingSpacing - leading`, and
973
+ * subtracting `leading` alone is right only under the exact rule.
974
+ *
975
+ * A line WITH spans carries its band in the span heights too. An empty paragraph carries
976
+ * nothing, which is why the caret, paint's `padding-bottom` and the content-control
977
+ * boundary all need this published rather than recovered from the box.
978
+ *
979
+ * Zero under the exact rule and on lines holding drawings, where the box is authored.
980
+ * Absent on lines published before this was measured; treat as zero.
981
+ */
982
+ readonly trailingSpacing?: number;
983
+ /**
984
+ * Model ranges on this line covering DELETED content, absent when there is none.
985
+ *
986
+ * The caret steps over these rather than entering them: text typed inside a deletion exists
987
+ * in neither the original nor the proposal, and there is no valid tree for the result.
988
+ *
989
+ * Recorded even in display modes that lay the deletion out invisibly, because the offsets
990
+ * exist in the model in every mode and an offset-by-offset walk would otherwise stop at
991
+ * positions with no glyph.
992
+ */
993
+ readonly deletedRanges?: readonly ModelRange[];
994
+ /**
995
+ * Inline drawings on this line, absent when there are none.
996
+ *
997
+ * Each occupies one UTF-16 model unit at {@link InlineDrawingRecord.start}. Hidden drawings
998
+ * are omitted — they remain in the tree and projection but publish no geometry.
999
+ */
1000
+ readonly drawings?: readonly InlineDrawingRecord[];
1001
+ }
1002
+ /**
1003
+ * A paragraph's resolved indent in points, in the vocabulary `w:ind` uses.
1004
+ *
1005
+ * `left`/`right` are signed; `hanging` is not (`ST_TwipsMeasure`). `firstLine` is signed
1006
+ * even though the schema declares it unsigned, because Word's model keeps one signed
1007
+ * first-line indent and this engine follows it.
1008
+ */
1009
+ interface ParagraphIndent {
1010
+ readonly left: number;
1011
+ readonly right: number;
1012
+ readonly firstLine: number;
1013
+ readonly hanging: number;
1014
+ }
1015
+ /**
1016
+ * The part of one paragraph that sits on one page.
1017
+ *
1018
+ * A paragraph that crosses a page boundary produces several fragments that all name the SAME
1019
+ * `paragraphId`, which is what lets selection and hit-testing treat it as one paragraph while
1020
+ * pagination treats it as two boxes.
1021
+ */
1022
+ interface ParagraphFragmentRecord {
1023
+ readonly kind: 'paragraph';
1024
+ readonly id: string;
1025
+ readonly paragraphId: string;
1026
+ /** 0 for the first fragment of the paragraph, 1 for its continuation, and so on. */
1027
+ readonly fragmentIndex: number;
1028
+ readonly range: SourceRange;
1029
+ readonly props: readonly OoxmlProperty[];
1030
+ /**
1031
+ * Before/after spacing applied to THIS fragment, in points.
1032
+ *
1033
+ * Continuations carry `before: 0`; only the final fragment carries `after`. The numbers
1034
+ * already reflect Word's adjacent-collapse against the previous paragraph's after.
1035
+ */
1036
+ readonly spacing: ParagraphSpacing;
1037
+ /**
1038
+ * The paragraph's EFFECTIVE indent in points, cascade and numbering merge included.
1039
+ *
1040
+ * Published because it is not recoverable from anything else here. `props` carries the
1041
+ * cascaded `w:ind`, but a list paragraph's indent comes from `numbering.xml` and is merged
1042
+ * in after the cascade, so a numbered item that authors no `w:ind` reads zero there while
1043
+ * its text sits indented. The geometry is no better an answer: `box.x` is cell-relative
1044
+ * inside a table and is displaced by float zones.
1045
+ *
1046
+ * `firstLine` and `hanging` are kept as OOXML spells them. A consumer wanting the one
1047
+ * signed first-line offset Word models takes `hanging > 0 ? -hanging : firstLine` —
1048
+ * hanging WINS, it is not summed (ECMA-376 §17.3.1.12).
1049
+ */
1050
+ readonly indent: ParagraphIndent;
1051
+ /** Bottom rule on the final fragment when `w:pBdr/w:bottom` resolves; absent otherwise. */
1052
+ readonly bottomBorder?: ParagraphBottomBorderRecord;
1053
+ /**
1054
+ * Every `w:pBdr` rule this fragment draws, in paint order.
1055
+ *
1056
+ * A paragraph split across pages opens and closes exactly once: the `top` stroke rides the
1057
+ * first fragment, the closing stroke the last. `bottomBorder` remains the bottom rule alone
1058
+ * — a `between` rule closing a grouped paragraph is not one.
1059
+ */
1060
+ readonly borders?: readonly ParagraphBorderStrokeRecord[];
1061
+ /**
1062
+ * Validated 6-hex paragraph shading fill (`w:pPr/w:shd`), absent for none/auto.
1063
+ *
1064
+ * Paint fills {@link shadingBox}. Measurement ignores it.
1065
+ */
1066
+ readonly shading?: string;
1067
+ /**
1068
+ * Geometry of the paragraph shading band when {@link shading} is set. Absent when there
1069
+ * is no fill.
1070
+ *
1071
+ * TWO SHAPES, because Word fills two different things. An UNBORDERED paragraph is filled
1072
+ * across its line boxes (indent-aware width), and before/after spacing stays unfilled. A
1073
+ * paragraph that publishes any {@link borders} stroke is filled across the rectangle those
1074
+ * strokes draw instead — `w:space` padding and rule extent included — so a callout's fill
1075
+ * reaches its frame rather than leaving a pale stripe floating inside an empty box. Paint
1076
+ * draws the strokes after the fill, so the frame is never covered.
1077
+ */
1078
+ readonly shadingBox?: LayoutBox;
1079
+ /**
1080
+ * The revision on this paragraph's own MARK (`w:pPr/w:rPr/w:ins|w:del`), absent when there
1081
+ * is none.
1082
+ *
1083
+ * Carried on the fragment rather than on a span because it decorates no characters: the
1084
+ * pilcrow was inserted or deleted, which is how a split or a merge is recorded. Only the
1085
+ * FINAL fragment carries it — the mark lives at the end of the paragraph, so a paragraph
1086
+ * split across pages must not draw two of them.
1087
+ */
1088
+ readonly markRevision?: RevisionAttribution;
1089
+ /**
1090
+ * List marker painted in the hanging-indent slot of the FIRST fragment only.
1091
+ *
1092
+ * Not part of model text: no UTF-16 range, never contributes to caret/selection offsets,
1093
+ * and must not be serialised back into the paragraph.
1094
+ */
1095
+ readonly marker?: ListMarkerRecord;
1096
+ readonly lines: readonly LineRecord[];
1097
+ readonly box: LayoutBox;
1098
+ }
1099
+ /**
1100
+ * A numbering marker as layout published it.
1101
+ *
1102
+ * Geometry is in the same coordinate space as the fragment (page-content or cell-content
1103
+ * relative). Paint positions from this box and MUST NOT remeasure the marker.
1104
+ */
1105
+ interface ListMarkerRecord {
1106
+ readonly text: string;
1107
+ readonly style: ResolvedRunStyle;
1108
+ readonly box: LayoutBox;
1109
+ /**
1110
+ * The `w:ilvl` this marker was resolved at, 0..8.
1111
+ *
1112
+ * Published because the level, not the geometry, is what Increase/Decrease Indent moves
1113
+ * on a list paragraph — demoting an item re-resolves its format from `numbering.xml`,
1114
+ * which is why a bullet becomes a hollow circle and a `1.` becomes an `a.`.
1115
+ */
1116
+ readonly level: number;
1117
+ /**
1118
+ * The `w:numId` this marker resolved through.
1119
+ *
1120
+ * Published with the level because the two together are what identifies a list: whether
1121
+ * a demote is even possible depends on which levels THIS definition declares, and a
1122
+ * document may hold several lists whose level 0 looks identical.
1123
+ */
1124
+ readonly numId: string;
1125
+ /** `w:numFmt` of the resolved level — `bullet` or a numbering format. */
1126
+ readonly numFmt: string;
1127
+ }
1128
+ /**
1129
+ * The part of one table that sits on one page.
1130
+ *
1131
+ * A table that crosses a page boundary produces one fragment per page it spans, which is
1132
+ * what lets it checkpoint like a paragraph: the flow loop places whole fragments, and
1133
+ * resuming after a table needs no knowledge of its interior.
1134
+ */
1135
+ interface TableFragmentRecord {
1136
+ readonly kind: 'table';
1137
+ readonly id: string;
1138
+ /** Canonical node id of the `w:tbl`. */
1139
+ readonly tableId: string;
1140
+ /** 0 for the first page the table touches, 1 for its continuation, and so on. */
1141
+ readonly fragmentIndex: number;
1142
+ /** Nesting depth: 0 for body-level tables, increasing for nested tables. */
1143
+ readonly nestingDepth: number;
1144
+ /**
1145
+ * Resolved column boundary x positions in table-local points, left edge through right edge.
1146
+ * Length is column count + 1.
1147
+ */
1148
+ readonly columnEdges: readonly number[];
1149
+ readonly rows: readonly TableRowFragmentRecord[];
1150
+ readonly box: LayoutBox;
1151
+ }
1152
+ /**
1153
+ * One table row on one page.
1154
+ *
1155
+ * A FRAGMENT, not the row: a row split across a page break appears once per page it touches, and
1156
+ * a repeated header row appears on every page of its table.
1157
+ */
1158
+ interface TableRowFragmentRecord {
1159
+ /** Canonical node id of the `w:tr`. */
1160
+ readonly id: string;
1161
+ /** Pending tracked row insertion/deletion, when authored in `w:trPr`. */
1162
+ readonly revisionKind?: 'insert' | 'delete';
1163
+ /**
1164
+ * The `w:trPr/w:ins|w:del` attribution — the `(id, author, date)` triple the review model
1165
+ * addresses this decision by, so painted rows can carry it the way revision spans do.
1166
+ */
1167
+ readonly revisionId?: string;
1168
+ readonly revisionAuthor?: string;
1169
+ readonly revisionDate?: string;
1170
+ /** Authored row ordinal within the table; repeats share the original row's index. */
1171
+ readonly rowIndex: number;
1172
+ /**
1173
+ * True for a `w:tblHeader` row RE-EMITTED at the top of a continuation page. Painted,
1174
+ * but excluded from interaction walks so each caret stop exists exactly once.
1175
+ */
1176
+ readonly isHeaderRepeat: boolean;
1177
+ /**
1178
+ * True when this record continues a row that already emitted content on a prior page
1179
+ * (cell content fragmented at a paragraph/line boundary). Same `id` as the lead fragment.
1180
+ */
1181
+ readonly isContinuation?: boolean;
1182
+ readonly cells: readonly TableCellFragmentRecord[];
1183
+ readonly box: LayoutBox;
1184
+ }
1185
+ /**
1186
+ * One table cell on one page, already resolved against the grid.
1187
+ *
1188
+ * `gridSpan` is clamped at read time, because it comes from a file and an unclamped span is a
1189
+ * loop bound an attacker controls. A vertical-merge continuation paints its box but holds no
1190
+ * blocks — its content belongs to the cell that started the merge.
1191
+ */
1192
+ interface TableCellFragmentRecord {
1193
+ /** Canonical node id of the `w:tc`. */
1194
+ readonly id: string;
1195
+ /** First grid column this cell occupies. */
1196
+ readonly gridColumn: number;
1197
+ /** Canonical `w:gridCol` node id for this cell's start column, when authored. */
1198
+ readonly gridColumnId?: string;
1199
+ /** Grid columns spanned, already clamped at read time. */
1200
+ readonly gridSpan: number;
1201
+ /** A vertical-merge continuation paints its box but holds no blocks. */
1202
+ readonly vMergeContinue: boolean;
1203
+ /**
1204
+ * When true, paint skips borders/fill/content for this cell (vMerge continue). Grid
1205
+ * bookkeeping and the box remain so selection/geometry walks stay consistent.
1206
+ */
1207
+ readonly paintInert?: boolean;
1208
+ /** Number of rows this restart cell visually spans (1 when not a vertical merge). */
1209
+ readonly rowSpan?: number;
1210
+ /** Validated 6-hex cell shading fill, absent for none/auto. */
1211
+ readonly shading?: string;
1212
+ /**
1213
+ * Layout-owned resolved borders after collapsed conflict resolution.
1214
+ *
1215
+ * Includes convenience per-side edges, per-grid-interval winners, and explicit compound
1216
+ * stroke rectangles in cell-local points. Paint only scales and draws — it must not
1217
+ * invent stroke/gap/corner geometry.
1218
+ */
1219
+ readonly borders?: ResolvedCellBorders;
1220
+ /** Nested blocks in reading order; recursion carries nested tables. */
1221
+ readonly blocks: readonly BlockFragmentRecord[];
1222
+ readonly box: LayoutBox;
1223
+ }
1224
+ /** A top-level (or cell-level) block fragment, discriminated by `kind`. */
1225
+ type BlockFragmentRecord = ParagraphFragmentRecord | TableFragmentRecord;
1226
+ /**
1227
+ * One header or footer story as it sits on one page.
1228
+ *
1229
+ * `box` is absolute (sheet coordinates) and sized to the story's FLOW height — never to
1230
+ * any anchored-object extent, which is the rule that keeps a decorated header's hit area
1231
+ * from covering the body. `fragments` are story-relative (origin at the box's top-left).
1232
+ * Baseline furniture may be shared across pages of the same variant when the story has no
1233
+ * allowlisted PAGE/NUMPAGES/SECTIONPAGES fields; after page-field finalize, projections are
1234
+ * per page (or per distinct field values for count-only stories).
1235
+ */
1236
+ interface HeaderFooterStoryRecord {
1237
+ readonly kind: 'header' | 'footer';
1238
+ readonly variant: 'default' | 'first' | 'even';
1239
+ readonly partName: string;
1240
+ /**
1241
+ * Main-document relationship id that resolves to this part (`EditorScope.rId`).
1242
+ *
1243
+ * Present when the furniture source could name the relationship; scoped editing binds
1244
+ * on this id so shared parts stay one story across pages/sections.
1245
+ */
1246
+ readonly rId?: string;
1247
+ readonly box: LayoutBox;
1248
+ readonly fragments: readonly BlockFragmentRecord[];
1249
+ /** Anchored drawings owned by this story, in story-relative coordinates. */
1250
+ readonly anchoredDrawings?: readonly AnchoredDrawingRecord[];
1251
+ /**
1252
+ * Transient projector used between furniture attach and document-level page-field
1253
+ * finalize. Absent on published layout records after finalize.
1254
+ */
1255
+ readonly pageFieldProjector?: (context: {
1256
+ readonly pageNumber: number;
1257
+ readonly pageCount: number;
1258
+ readonly sectionPageCount?: number;
1259
+ readonly format?: string;
1260
+ }) => HeaderFooterStoryRecord;
1261
+ }
1262
+ /**
1263
+ * One footnote or endnote story as it sits on one page (or continuation page).
1264
+ *
1265
+ * Notes are ordinary editable stories — NOT `[data-docx-hf]` furniture. `box` is absolute
1266
+ * (sheet coordinates). `fragments` are story-relative. Separators are nonselectable paint
1267
+ * geometry owned by the parent {@link NoteAreaRecord}.
1268
+ */
1269
+ interface NoteStoryRecord {
1270
+ readonly noteKind: 'footnote' | 'endnote';
1271
+ readonly noteId: number;
1272
+ /** `footnote:N` / `endnote:N` — EditorScope note id. */
1273
+ readonly scopeId: string;
1274
+ /** Derived display mark for this occurrence; null when customMarkFollows / continuation. */
1275
+ readonly mark: string | null;
1276
+ /** True when this is a continuation fragment (no leading mark). */
1277
+ readonly continuation?: boolean;
1278
+ readonly box: LayoutBox;
1279
+ readonly fragments: readonly BlockFragmentRecord[];
1280
+ }
1281
+ /**
1282
+ * Footnote / endnote area on one page: separator + stacked note stories.
1283
+ *
1284
+ * `placement` records how the area was positioned. `fallbackReason` is set when the
1285
+ * bounded reflow loop exhausted and layout kept the reference with its note on a later
1286
+ * page (D12 named fallback).
1287
+ */
1288
+ interface NoteAreaRecord {
1289
+ readonly kind: 'footnotes' | 'endnotes';
1290
+ readonly placement: 'pageBottom' | 'beneathText' | 'sectEnd' | 'docEnd';
1291
+ readonly box: LayoutBox;
1292
+ /** Separator rule / authored separator story; absent when no notes on this page. */
1293
+ readonly separator?: {
1294
+ readonly kind: 'separator' | 'continuationSeparator';
1295
+ readonly box: LayoutBox;
1296
+ readonly fragments: readonly BlockFragmentRecord[];
1297
+ readonly synthetic: boolean;
1298
+ /** Layout-owned single/double rule when marker-only or synthetic; absent for authored stories. */
1299
+ readonly ruleStyle?: 'single' | 'double';
1300
+ };
1301
+ readonly notes: readonly NoteStoryRecord[];
1302
+ readonly fallbackReason?: string;
1303
+ }
1304
+ /**
1305
+ * Note-stream ownership for overflow sheets created by note pagination.
1306
+ *
1307
+ * - `footnote-drain`: continuation pages that exist only to finish pageBottom footnotes —
1308
+ * not free body hosts for sectEnd/docEnd endnotes.
1309
+ * - `endnote-overflow`: sheets inserted to finish sectEnd/docEnd collections.
1310
+ */
1311
+ type PageNoteStream = 'footnote-drain' | 'endnote-overflow';
1312
+ /**
1313
+ * Raw `w:lock/@w:val` on one control, or `unlocked` when absent / unrecognised.
1314
+ *
1315
+ * Effective permissions across a nesting chain are the union of every ancestor's lock on two
1316
+ * axes (content edit / removal); see {@link ContentControlBoundaryRecord.effectiveLock}.
1317
+ */
1318
+ type ContentControlLock = 'unlocked' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked';
1319
+ /**
1320
+ * Mapped control type for layout / chrome — same members as the shipped public
1321
+ * `ContentControlType`. Untyped and preserved-only kinds report as `richText`.
1322
+ */
1323
+ type ContentControlMappedType = 'richText' | 'plainText' | 'checkbox' | 'dropdown' | 'comboBox' | 'date' | 'picture' | 'repeatingSection';
1324
+ /** Where the control sits in the tree relative to its content. */
1325
+ type ContentControlLevel = 'block' | 'inline' | 'row' | 'cell';
1326
+ /**
1327
+ * One piece of a control's content geometry.
1328
+ *
1329
+ * A block control that crosses a page break publishes one fragment per page rather than a
1330
+ * single rectangle covering the inter-page gap. An inline control publishes one fragment per
1331
+ * LINE it touches, covering the text's vertical extent (line-spacing leading excluded), so a
1332
+ * wrapped control never claims the words beside it. Coordinates match fragment boxes
1333
+ * (page-content space).
1334
+ */
1335
+ interface ContentControlGeometryFragment {
1336
+ readonly pageIndex: number;
1337
+ readonly box: LayoutBox;
1338
+ }
1339
+ /**
1340
+ * Layout-published boundary for one content control (`w:sdt` / typed `contentControl`).
1341
+ *
1342
+ * Chrome, lock feedback, and hit resolution read this record — never painted DOM. The wrapper
1343
+ * itself is not a layout box; `fragments` cover the content that already flowed in place.
1344
+ */
1345
+ interface ContentControlBoundaryRecord {
1346
+ /** Canonical node id of the control wrapper — not `w:id`. */
1347
+ readonly id: string;
1348
+ readonly alias?: string;
1349
+ readonly tag?: string;
1350
+ readonly controlType: ContentControlMappedType;
1351
+ /** This control's own `w:lock`, before ancestor union. */
1352
+ readonly lock: ContentControlLock;
1353
+ /**
1354
+ * Nested lock union with every ancestor control, collapsed back to a single `ST_Lock`
1355
+ * vocabulary value (both axes locked → `sdtContentLocked`).
1356
+ */
1357
+ readonly effectiveLock: ContentControlLock;
1358
+ /** `w:showingPlcHdr` is present on the control's properties. */
1359
+ readonly placeholder: boolean;
1360
+ /** `w:dataBinding` is present — content edits are refused as bound. */
1361
+ readonly bound: boolean;
1362
+ /** 0 for a top-level control; increments through nested wrappers under the shared nesting bound. */
1363
+ readonly nestingDepth: number;
1364
+ readonly level: ContentControlLevel;
1365
+ readonly fragments: readonly ContentControlGeometryFragment[];
1366
+ }
1367
+ /**
1368
+ * One laid-out page: the sheet, the content area, and everything that landed on it.
1369
+ *
1370
+ * Page identity is REUSED across incremental passes — a pass that changes nothing returns the
1371
+ * previous records by reference, which is what lets paint skip untouched pages entirely.
1372
+ */
1373
+ interface PageRecord {
1374
+ readonly id: string;
1375
+ readonly index: number;
1376
+ /** The whole sheet. */
1377
+ readonly box: LayoutBox;
1378
+ /** The area inside the margins that content flows into. */
1379
+ readonly contentBox: LayoutBox;
1380
+ readonly fragments: readonly BlockFragmentRecord[];
1381
+ /** Layout-owned vertical rules requested by `w:cols/@w:sep`, content-box relative. */
1382
+ readonly columnSeparators?: readonly LayoutBox[];
1383
+ /** Page-content anchored drawings on this sheet, absent when there are none. */
1384
+ readonly anchoredDrawings?: readonly AnchoredDrawingRecord[];
1385
+ /** Page furniture for this page's variant, absent when the document declares none. */
1386
+ readonly header?: HeaderFooterStoryRecord;
1387
+ readonly footer?: HeaderFooterStoryRecord;
1388
+ /** Footnotes reserved on this page (pageBottom / beneathText / continuations). */
1389
+ readonly footnotes?: NoteAreaRecord;
1390
+ /** Endnotes collected on this page (sectEnd / docEnd). */
1391
+ readonly endnotes?: NoteAreaRecord;
1392
+ /**
1393
+ * Ownership of note-only overflow sheets. Absent on ordinary body pages.
1394
+ * Layout pagination sets this so endnote hosting does not treat footnote drain
1395
+ * pages as free body space.
1396
+ */
1397
+ readonly noteStream?: PageNoteStream;
1398
+ /**
1399
+ * Section-local PAGE/SECTIONPAGES inputs for finalize. Absent → physical page index and
1400
+ * document-wide section count (empty `w:pgNumType` behaviour).
1401
+ */
1402
+ readonly pageFieldSource?: {
1403
+ readonly pageNumber: number;
1404
+ readonly sectionPageCount: number;
1405
+ readonly format?: string;
1406
+ };
1407
+ /**
1408
+ * Content-control boundaries whose geometry intersects this page.
1409
+ *
1410
+ * Carried on the page so a consumer that only holds a page record still sees current
1411
+ * metadata; identity reuse of an unchanged page keeps the same array only when the
1412
+ * control-context token matches.
1413
+ */
1414
+ readonly contentControls?: readonly ContentControlBoundaryRecord[];
1415
+ }
1416
+ /**
1417
+ * A complete layout pass: every page, plus the document-wide indexes derived alongside them.
1418
+ *
1419
+ * Stamped with the store `revision` it was laid out from, so anything holding geometry can tell
1420
+ * whether the document has moved underneath it — which is how stale pointer gestures and
1421
+ * overlays are refused rather than applied to coordinates that no longer describe anything.
1422
+ */
1423
+ interface SemanticLayout {
1424
+ /** The store revision these records were laid out from. */
1425
+ readonly revision: number;
1426
+ readonly pages: readonly PageRecord[];
1427
+ /**
1428
+ * Every content-control boundary in document order, including multi-page fragment lists.
1429
+ *
1430
+ * Always recomputed when a document layout pass finishes, so incremental page-identity
1431
+ * reuse cannot publish stale alias/tag/lock metadata.
1432
+ */
1433
+ readonly contentControls?: readonly ContentControlBoundaryRecord[];
1434
+ /**
1435
+ * Fingerprint of wrapper-only control metadata (alias, tag, lock, type, placeholder, binding).
1436
+ *
1437
+ * Folded into the layout producer / session context so a metadata-only edit invalidates
1438
+ * reuse paths that would otherwise return previous pages by identity with stale boundaries.
1439
+ */
1440
+ readonly controlContextToken?: string;
1441
+ }
1442
+ /** Page geometry, in points. */
1443
+ interface PageGeometry {
1444
+ readonly width: number;
1445
+ readonly height: number;
1446
+ readonly margin: {
1447
+ readonly top: number;
1448
+ readonly right: number;
1449
+ readonly bottom: number;
1450
+ readonly left: number;
1451
+ };
1452
+ /** `w:pgMar/@header` — sheet edge to header top, in points. Defaults to 36 (720 twips). */
1453
+ readonly headerDistance?: number;
1454
+ /** `w:pgMar/@footer` — sheet edge to footer bottom, in points. Defaults to 36. */
1455
+ readonly footerDistance?: number;
1456
+ }
1457
+ /** US Letter with one-inch margins, in points. */
1458
+ declare const DEFAULT_PAGE_GEOMETRY: PageGeometry;
1459
+ /**
1460
+ * Text measurement, injected.
1461
+ *
1462
+ * A real implementation shapes with the resolved font; the tests supply a deterministic one.
1463
+ * Layout never reads the DOM, so this is the only way width and height enter it.
1464
+ */
1465
+ interface TextMeasurer {
1466
+ /** Advance width of `text` in the resolved style. */
1467
+ measure(text: string, style: ResolvedRunStyle): number;
1468
+ /** Line height and baseline for the resolved style. */
1469
+ lineMetrics(style: ResolvedRunStyle): {
1470
+ height: number;
1471
+ baseline: number;
1472
+ };
1473
+ }
1474
+ /**
1475
+ * Depth-first paragraph fragments of one page, in reading order.
1476
+ *
1477
+ * Table interiors flatten through rows and cells; header-repeat rows are skipped unless
1478
+ * asked for, so interaction sees each caret stop exactly once while paint sees everything.
1479
+ */
1480
+ declare function paragraphFragmentsOf(page: PageRecord, includeHeaderRepeats?: boolean): ParagraphFragmentRecord[];
1481
+ /**
1482
+ * Depth-first paragraph fragments of one block list, in reading order.
1483
+ *
1484
+ * The same walk as {@link paragraphFragmentsOf} for fragment lists that do not sit on the
1485
+ * page directly — a header/footer story's fragments, a note story's.
1486
+ */
1487
+ declare function paragraphFragmentsOfBlocks(blocks: readonly BlockFragmentRecord[], includeHeaderRepeats?: boolean): ParagraphFragmentRecord[];
1488
+ /** Every line in a layout, in reading order — the order caret navigation walks. */
1489
+ declare function linesOf(layout: SemanticLayout): LineRecord[];
1490
+ /** Every fragment belonging to one paragraph, in order, across page boundaries. */
1491
+ declare function fragmentsOfParagraph(layout: SemanticLayout, paragraphId: string): ParagraphFragmentRecord[];
1492
+ /** The line containing a model position, or null when the position is not laid out. */
1493
+ declare function lineAtPosition(layout: SemanticLayout, paragraphId: string, offset: number): LineRecord | null;
1494
+ /** Every content-control boundary on a layout, preferring the layout-level list. */
1495
+ declare function contentControlsOfLayout(layout: SemanticLayout): readonly ContentControlBoundaryRecord[];
1496
+ /**
1497
+ * Axis-aligned union of boxes, or null when the list is empty.
1498
+ *
1499
+ * Used when a control's content spans several fragments or spans on one page.
1500
+ */
1501
+ declare function unionLayoutBoxes(boxes: readonly LayoutBox[]): LayoutBox | null;
1502
+ /** Collapse raw + ancestor locks into one `ST_Lock` vocabulary value. */
1503
+ declare function effectiveContentControlLock(locks: readonly ContentControlLock[]): ContentControlLock;
1504
+
1505
+ export { type ParagraphBorderStrokeRecord as $, type AnchoredDrawingRecord as A, type BlockFragmentRecord as B, type ContentControlBoundaryRecord as C, type DrawingPoint as D, DEFAULT_TAB_INTERVAL_TWIPS as E, type FieldPageContext as F, EMPTY_TAB_STOPS as G, type HyperlinkProjector as H, type InlineDrawingRecord as I, type HeaderFooterStoryRecord as J, type LineSpacingRule as K, type LineRecord as L, type ModelRange as M, type ListMarkerRecord as N, MAX_BORDER_SPACE_PT as O, type PageGeometry as P, MAX_BORDER_WIDTH_PT as Q, type ResolvedRunStyle as R, type SemanticLayout as S, type TextMeasurer as T, MAX_PARAGRAPH_SPACING_PT as U, MAX_TABLE_BORDER_STROKES as V, MAX_TAB_POSITION_TWIPS as W, MAX_TAB_STOPS as X, PARAGRAPH_BORDER_SIDES as Y, type ParagraphAutoSpacingContext as Z, type ParagraphBorderSide as _, type StyleSpanRecord as a, type ParagraphBottomBorderRecord as a0, type ParagraphIndent as a1, type ResolvedCellBorders as a2, type ResolvedTableBorderEdge as a3, type ResolvedTableBorderEdgeSegment as a4, type ResolvedUnderline as a5, SINGLE_LINE_SPACING as a6, type SourceRange as a7, type SpanLinkRecord as a8, TAB_LEADER_GLYPH as a9, nextTabDestination as aA, paragraphBorderExtentPt as aB, paragraphBorderStrokeWidthPt as aC, paragraphBorders as aD, paragraphBordersFingerprint as aE, paragraphBreaksBefore as aF, paragraphContextualSpacing as aG, paragraphFragmentsOf as aH, paragraphFragmentsOfBlocks as aI, paragraphLineSpacing as aJ, paragraphSpacing as aK, paragraphTabStops as aL, readBorderSide as aM, readCellBorders as aN, readTableBorders as aO, resolveBorderConflict as aP, resolveRunStyle as aQ, resolveTableCellBorderGrid as aR, runStylesEqual as aS, tabAdvanceWidth as aT, tabStopsFingerprint as aU, unionLayoutBoxes as aV, withDefaultTabInterval as aW, type TabAlignment as aa, type TabDestination as ab, type TabLeader as ac, type TabStop as ad, type TableBorderSideName as ae, type TableBorderStrokeRecord as af, type TableFragmentRecord as ag, type VerticalAlign as ah, appliedSpaceBefore as ai, applyLineSpacing as aj, baselineShiftPtOf as ak, borderExtentPt as al, borderWeight as am, bottomBorderExtentPt as an, cascadedParagraphBorders as ao, cascadedTabStops as ap, collapsedSpaceBefore as aq, contentControlsOfLayout as ar, defaultTabIntervalFromSettings as as, displayText as at, effectiveContentControlLock as au, formatPageNumber as av, fragmentsOfParagraph as aw, lineAtPosition as ax, linesOf as ay, measureDisplayText as az, type TableRowFragmentRecord as b, type TableCellFragmentRecord as c, type LayoutBox as d, type DrawingInsets as e, type ThemeFonts as f, type ParagraphSpacing as g, type ParagraphLineSpacing as h, type ParagraphBorderEdge as i, type ParagraphBorders as j, type ResolvedTabStops as k, type TableBorderSide as l, type InlineDrawingLayoutContext as m, type PageRecord as n, type ParagraphFragmentRecord as o, type CellBorderBox as p, type TableBorderBox as q, AUTO_PARAGRAPH_SPACING_PT as r, type BorderGridGeometry as s, type ContentControlGeometryFragment as t, type ContentControlLevel as u, type ContentControlLock as v, type ContentControlMappedType as w, DEFAULT_PAGE_GEOMETRY as x, DEFAULT_RUN_STYLE as y, DEFAULT_TAB_INTERVAL_PT as z };