@docx-editor.dev/core 2.14.0 → 2.15.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 (133) hide show
  1. package/README.md +42 -0
  2. package/dist/automation.cjs +1 -1
  3. package/dist/automation.d.ts +6 -5
  4. package/dist/automation.js +1 -1
  5. package/dist/binding.cjs +1 -1
  6. package/dist/binding.d.ts +16 -10
  7. package/dist/binding.js +1 -1
  8. package/dist/chunk-24APKZAN.cjs +11 -0
  9. package/dist/chunk-2YH4MSHX.cjs +2 -0
  10. package/dist/chunk-5Y7AADI6.js +1 -0
  11. package/dist/chunk-AREFLNUP.js +11 -0
  12. package/dist/chunk-AS7YZNQE.cjs +2 -0
  13. package/dist/chunk-AVGCKH6A.cjs +20 -0
  14. package/dist/chunk-CDYVDQRI.cjs +5 -0
  15. package/dist/chunk-FGBADUSK.cjs +1 -0
  16. package/dist/chunk-HOEWFH5U.cjs +1 -0
  17. package/dist/chunk-KFELX7CN.js +2 -0
  18. package/dist/{chunk-OKEDGR65.js → chunk-KXPVOPR5.js} +1 -1
  19. package/dist/chunk-LGFKIF4M.cjs +1 -0
  20. package/dist/chunk-M52QXFKI.js +1 -0
  21. package/dist/chunk-MNUKSXRO.js +1 -0
  22. package/dist/chunk-PBOIGBDO.js +2 -0
  23. package/dist/chunk-PMHGONET.cjs +1 -0
  24. package/dist/chunk-PXKZUTHX.cjs +2 -0
  25. package/dist/chunk-Q4AB7E4O.cjs +1 -0
  26. package/dist/chunk-QJYH6AYD.js +1 -0
  27. package/dist/chunk-QRC37MWG.cjs +20 -0
  28. package/dist/chunk-RJSB6RJ7.js +20 -0
  29. package/dist/chunk-SB54HLFJ.js +20 -0
  30. package/dist/chunk-VP7SDFCM.js +1 -0
  31. package/dist/chunk-XCESLTJL.js +2 -0
  32. package/dist/{chunk-KIAP7PFQ.cjs → chunk-YL4Q5LZP.cjs} +1 -1
  33. package/dist/chunk-Z4527AXR.js +5 -0
  34. package/dist/collaboration/replication.cjs +1 -1
  35. package/dist/collaboration/replication.d.ts +4 -3
  36. package/dist/collaboration/replication.js +1 -1
  37. package/dist/collaboration.d.ts +4 -3
  38. package/dist/contracts/editor.d.ts +7 -7
  39. package/dist/contracts/interaction.d.ts +7 -7
  40. package/dist/contracts/modules.d.ts +8 -7
  41. package/dist/{custom-node-writes-DntmaAVW.d.ts → custom-node-writes-C9ArIlJh.d.ts} +2 -2
  42. package/dist/{editor-a6VvsfYU.d.ts → editor-sRxrFJd8.d.ts} +29 -21
  43. package/dist/editor.cjs +1 -1
  44. package/dist/editor.d.ts +27 -58
  45. package/dist/editor.js +1 -1
  46. package/dist/export.cjs +2 -0
  47. package/dist/export.d.ts +337 -0
  48. package/dist/export.js +2 -0
  49. package/dist/font-resolver-BYEHQGTw.d.ts +251 -0
  50. package/dist/headless-document-view-DELC4E5v.d.ts +134 -0
  51. package/dist/hf-references-DTrHkktY.d.ts +70 -0
  52. package/dist/{index-C5ZnSRkF.d.ts → index-C3Gvq4jN.d.ts} +55 -246
  53. package/dist/index.cjs +1 -1
  54. package/dist/index.d.ts +18 -16
  55. package/dist/index.js +1 -1
  56. package/dist/{shaped-measurer-CFPnXFs9.d.cts → layout-shaping-DoGR5cf8.d.ts} +86 -12
  57. package/dist/layout.cjs +1 -1
  58. package/dist/layout.d.ts +1106 -729
  59. package/dist/layout.js +1 -1
  60. package/dist/{note-properties-CDFiNXcD.d.ts → note-properties-DSe8igrz.d.ts} +2 -2
  61. package/dist/{omml-equation-BY49UOIF.d.ts → omml-equation-BpRmfDAf.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-gOY5GA8K.d.cts → protocol-DZzKxH5r.d.ts} +18 -12
  66. package/dist/{review-items-CCS1h93f.d.ts → review-items-DYRx187i.d.ts} +2 -2
  67. package/dist/{review-reads-BjZYeMd4.d.ts → review-reads-LbGUdNzn.d.ts} +22 -25
  68. package/dist/{revision-projection-BeXVrSSV.d.ts → revision-projection-DHRJH48u.d.ts} +1 -1
  69. package/dist/{semantic-cell-selection-BK91NCCG.d.ts → semantic-cell-selection-BXVWCBYm.d.ts} +4 -4
  70. package/dist/{semantic-interaction-BmQ7h2cq.d.ts → semantic-interaction-DZsDmPg5.d.ts} +1 -1
  71. package/dist/{semantic-paint-WJD2KeG1.d.ts → semantic-paint-f7SqN4oW.d.ts} +2 -2
  72. package/dist/{semantic-records-BpozY7-j.d.cts → semantic-records-EIdwId-a.d.ts} +301 -205
  73. package/dist/store.cjs +1 -1
  74. package/dist/store.d.ts +15 -65
  75. package/dist/store.js +1 -1
  76. package/dist/{tree-op-types-D6HzlPR4.d.ts → tree-op-types-egdarvH_.d.ts} +7 -3
  77. package/dist/{tree-package-fragment-CWFjgH6a.d.ts → tree-package-fragment-DjkH4ESI.d.ts} +4 -70
  78. package/dist/{tree-session-contract-B2HfGt79.d.ts → tree-session-contract-CGOOgPjW.d.ts} +29 -23
  79. package/dist/{tree-store-APgWx6qn.d.ts → tree-store-DKJB0IcA.d.ts} +2 -2
  80. package/package.json +7 -2
  81. package/dist/automation.d.cts +0 -82
  82. package/dist/binding.d.cts +0 -153
  83. package/dist/chunk-2PIHW2QM.cjs +0 -1
  84. package/dist/chunk-2QTY5S6T.js +0 -1
  85. package/dist/chunk-4Z5MODAE.cjs +0 -2
  86. package/dist/chunk-5TKAPFZT.cjs +0 -2
  87. package/dist/chunk-DRQHIQ4R.cjs +0 -1
  88. package/dist/chunk-F2K6SDHA.cjs +0 -20
  89. package/dist/chunk-FBA57EUJ.js +0 -2
  90. package/dist/chunk-KTAQ7BRQ.js +0 -20
  91. package/dist/chunk-LBAHXZCU.cjs +0 -5
  92. package/dist/chunk-MCDUQYWP.js +0 -5
  93. package/dist/chunk-MQP26KCF.js +0 -1
  94. package/dist/chunk-NDJGSR6M.cjs +0 -19
  95. package/dist/chunk-NJNFJPGB.cjs +0 -11
  96. package/dist/chunk-O5M4CR4P.cjs +0 -2
  97. package/dist/chunk-UKUOZXUD.js +0 -19
  98. package/dist/chunk-VQ4R5LTU.js +0 -2
  99. package/dist/chunk-ZBEJHJN2.js +0 -11
  100. package/dist/chunk-ZVTW7RGD.js +0 -2
  101. package/dist/collaboration/replication.d.cts +0 -216
  102. package/dist/collaboration.d.cts +0 -176
  103. package/dist/contracts/document.d.cts +0 -273
  104. package/dist/contracts/editor.d.cts +0 -10
  105. package/dist/contracts/interaction.d.cts +0 -10
  106. package/dist/contracts/modules.d.cts +0 -130
  107. package/dist/contracts/types.d.cts +0 -562
  108. package/dist/custom-node-writes-C46r3_5O.d.cts +0 -310
  109. package/dist/document-properties-D6Ahr8D6.d.cts +0 -28
  110. package/dist/document-properties-D6Ahr8D6.d.ts +0 -28
  111. package/dist/editor-AJ06hyyg.d.cts +0 -1994
  112. package/dist/editor.d.cts +0 -393
  113. package/dist/failure-BnGjoXf0.d.cts +0 -64
  114. package/dist/index-Cbw4lkOt.d.cts +0 -3391
  115. package/dist/index.d.cts +0 -23
  116. package/dist/layout.d.cts +0 -2486
  117. package/dist/note-properties-Y6qYh2Hv.d.cts +0 -67
  118. package/dist/omml-equation-DB8d-jCq.d.cts +0 -84
  119. package/dist/output.d.cts +0 -74
  120. package/dist/protocol-BsmQgblT.d.ts +0 -1358
  121. package/dist/review-items-CaAkgi5s.d.cts +0 -360
  122. package/dist/review-reads-DEsV0OOW.d.cts +0 -163
  123. package/dist/revision-projection-DXbrfNGD.d.cts +0 -156
  124. package/dist/semantic-cell-selection-BMhku3B1.d.cts +0 -451
  125. package/dist/semantic-interaction-DUAnxbgf.d.cts +0 -160
  126. package/dist/semantic-paint-DufNL1Zr.d.cts +0 -247
  127. package/dist/semantic-records-92UnKeU_.d.ts +0 -2821
  128. package/dist/shaped-measurer-BWbindlI.d.ts +0 -168
  129. package/dist/store.d.cts +0 -2500
  130. package/dist/tree-op-types-BUulYD5y.d.cts +0 -3099
  131. package/dist/tree-package-fragment-Dhyw7oPm.d.cts +0 -732
  132. package/dist/tree-session-contract-BY9xd7s_.d.cts +0 -613
  133. package/dist/tree-store-DEEQ9wz_.d.cts +0 -416
@@ -1,2821 +0,0 @@
1
- import { a as OoxmlNode, s as OoxmlProperty, g as OoxmlElement, O as OoxmlPart, x as ImageResourceState, U as DrawingAccessibility, S as SourceCrop, W as DrawingTransform, X as VectorShapeProjection, z as DrawingHorizontalReferenceFrame, A as DrawingVerticalReferenceFrame, u as ImageWrapTarget, Y as DrawingProjection, Z as OoxmlDrawingNode, _ as TableBorderStyle } from './tree-op-types-D6HzlPR4.js';
2
- import { b as RevisionAttribution } from './revision-projection-BeXVrSSV.js';
3
- import { O as OmmlEquationProjection } from './omml-equation-BY49UOIF.js';
4
-
5
- /**
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.
13
- *
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.
21
- */
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;
27
- /**
28
- * A paragraph's resolved space before and after, in points.
29
- *
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.
32
- */
33
- interface ParagraphSpacing {
34
- /** `w:spacing/@before`, in points. */
35
- readonly before: number;
36
- /** `w:spacing/@after`, in points. */
37
- readonly after: number;
38
- }
39
- /**
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).
42
- *
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.
48
- */
49
- declare const AUTO_PARAGRAPH_SPACING_PT = 14;
50
- /**
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}.
53
- *
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.
56
- */
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;
62
- }
63
- /**
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.
70
- *
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.
73
- */
74
- type LineSpacingRule = 'auto' | 'exact' | 'atLeast';
75
- /**
76
- * Resolved line spacing: the rule, and the value it applies.
77
- *
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.
80
- */
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
- /**
89
- * One resolved `w:pBdr` edge: its style, colour, thickness and gap.
90
- *
91
- * `widthPt` and `spacePt` are already converted and CLAMPED — both come from a file, and an
92
- * unbounded border width becomes a layout dimension.
93
- */
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"];
113
- /**
114
- * Which of the six `CT_PBdr` edges.
115
- *
116
- * Four are physical box edges; `between` and `bar` are group-relative, drawn only where
117
- * consecutive paragraphs share a border definition.
118
- */
119
- type ParagraphBorderSide = (typeof PARAGRAPH_BORDER_SIDES)[number];
120
- /**
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.
127
- */
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
- }
136
- /**
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.
141
- *
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}.
144
- */
145
- declare function paragraphSpacing(props: readonly OoxmlProperty[], context?: ParagraphAutoSpacingContext): ParagraphSpacing;
146
- /**
147
- * Resolve `w:line` / `w:lineRule` from flat paragraph properties.
148
- *
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.
152
- */
153
- declare function paragraphLineSpacing(props: readonly OoxmlProperty[]): ParagraphLineSpacing;
154
- /**
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).
262
- *
263
- * Lives with the type rather than with the painter because LAYOUT has to measure it: a
264
- * leader is the same character typed over and over, and the only way to space it the way
265
- * typing it would is to ask the measurer how wide it actually is.
266
- */
267
- declare const TAB_LEADER_GLYPH: ReadonlyMap<TabLeader, string>;
268
- /** One authored `w:tab`: where it sits, how it aligns, and what fills the gap. */
269
- interface TabStop {
270
- /** Position from the paragraph content origin, in points. */
271
- readonly positionPt: number;
272
- readonly alignment: TabAlignment;
273
- /** Absent for `none` — the schema default and the overwhelming majority of stops. */
274
- readonly leader?: TabLeader;
275
- }
276
- /**
277
- * A paragraph's tab stops after the style cascade, with the default interval that applies past
278
- * the last explicit one.
279
- */
280
- interface ResolvedTabStops {
281
- /** Custom stops sorted by ascending position. */
282
- readonly stops: readonly TabStop[];
283
- /** Default-tab interval in points (always positive and bounded). */
284
- readonly defaultIntervalPt: number;
285
- }
286
- /** The frozen "no custom stops" value, so a paragraph without tabs mints no object. */
287
- declare const EMPTY_TAB_STOPS: ResolvedTabStops;
288
- /**
289
- * Resolve tab stops from cascaded `w:pPr` nodes (docDefaults → style chain → direct).
290
- *
291
- * Each `w:tabs` merges with `clear` support; absence inherits. The leader travels with the
292
- * stop that declared it — a `clear` at the same position discards both together.
293
- */
294
- declare function cascadedTabStops(paragraphPropertyNodes: readonly OoxmlNode[]): ResolvedTabStops;
295
- /** Direct `w:pPr` only — used when no style cascade table is present. */
296
- declare function paragraphTabStops(pPr: OoxmlNode | undefined): ResolvedTabStops;
297
- /**
298
- * Republish stops under a document-wide default-tab interval (`w:defaultTabStop`).
299
- *
300
- * The cascade resolves stops from the paragraph's own property chain, which cannot see
301
- * `settings.xml`; the interval is a document constant that arrives from the session. Returns
302
- * the input unchanged when nothing moves, so a cache-key fingerprint stays stable.
303
- */
304
- declare function withDefaultTabInterval(tabs: ResolvedTabStops, defaultIntervalPt: number | undefined): ResolvedTabStops;
305
- /**
306
- * Read `w:settings/w:defaultTabStop` (ECMA-376 §17.15.1.25), in points.
307
- *
308
- * Word's own interval, not a constant: a metric-locale template writes `w:val="1134"` (2cm)
309
- * and every default-interval tab in the document lands on that grid instead of the 0.5"
310
- * one. The value is FILE-DERIVED, so a non-integer, non-positive or out-of-range `val` falls
311
- * back to the schema default rather than being trusted into layout arithmetic.
312
- *
313
- * `ST_TwipsMeasure` also admits a universal measure (`"2cm"`); Word writes plain twips, and
314
- * the spelled form falls back to the default rather than being parsed here.
315
- */
316
- declare function defaultTabIntervalFromSettings(settings: OoxmlNode | null | undefined): number;
317
- /** Where one tab character lands: the resolved x, and the stop that decided it. */
318
- interface TabDestination {
319
- readonly positionPt: number;
320
- readonly alignment: TabAlignment;
321
- /** Leader of the stop that was reached; absent for `none` and for default-interval tabs. */
322
- readonly leader?: TabLeader;
323
- }
324
- /**
325
- * Next tab destination strictly past `currentX`, preferring custom stops then the default
326
- * interval. Destination is clamped to `rightEdge` so stops cannot escape the content box.
327
- */
328
- declare function nextTabDestination(tabs: ResolvedTabStops, currentX: number, rightEdge: number): TabDestination;
329
- /**
330
- * Width of a tab glyph so the following segment lands on the destination.
331
- *
332
- * `segmentWidth` / `decimalOffset` are already measured in points. Decimal offset is the
333
- * advance from the segment start to the decimal point (0 when none — treated like right).
334
- */
335
- declare function tabAdvanceWidth(alignment: TabAlignment, currentX: number, destinationX: number, segmentWidth: number, decimalOffset: number): number;
336
- /**
337
- * Stable fingerprint for layout cache keys — nested `w:tabs` are not in flat `OoxmlProperty`
338
- * bags, so style-inherited stops must be named explicitly or breaks would collide.
339
- */
340
- declare function tabStopsFingerprint(tabs: ResolvedTabStops): string;
341
-
342
- /**
343
- * UAX #9 embedding levels, one per UTF-16 code unit, plus the paragraph ranges they were resolved
344
- * within.
345
- *
346
- * The level is EXACT, not a direction: its parity gives direction, but the numeric value is what
347
- * reordering needs, and collapsing it early loses nested isolates.
348
- */
349
- interface BidiEmbeddingLevels {
350
- readonly levels: Uint8Array;
351
- readonly paragraphs: readonly {
352
- readonly start: number;
353
- readonly end: number;
354
- readonly level: number;
355
- }[];
356
- }
357
-
358
- /**
359
- * A face, as something asks for it: family plus the two axes this engine admits.
360
- *
361
- * Only static weights and slants. Variable-font axes are deliberately outside this vocabulary —
362
- * the shaper refuses variation axes, and a variable file admitted here would render bold at
363
- * regular weight.
364
- */
365
- interface FontRequest {
366
- readonly family: string;
367
- readonly weight: number;
368
- readonly style: 'normal' | 'italic';
369
- }
370
- /**
371
- * A request that was answered by a DIFFERENT face than the one asked for.
372
- *
373
- * Recorded rather than silently applied, because a substitution changes measurement: it is part
374
- * of the shaping fingerprint, so a cached run shaped against a substitute is never reused for the
375
- * real face.
376
- */
377
- interface FontSubstitution {
378
- readonly requested: FontRequest;
379
- readonly resolved: FontRequest;
380
- readonly lineMetrics?: {
381
- readonly heightEm: number;
382
- readonly baselineEm: number;
383
- };
384
- }
385
- declare const RESOLVED_FONT_BRAND: unique symbol;
386
- /**
387
- * A face that passed admission: the bytes are present, within limits, hash-verified, and parse as
388
- * a font.
389
- *
390
- * Branded, so a `ResolvedFont` cannot be constructed by an object literal. Holding one is proof
391
- * the checks ran — which is what lets shaping skip re-validating on every call.
392
- */
393
- interface ResolvedFont {
394
- readonly [RESOLVED_FONT_BRAND]: true;
395
- readonly id: string;
396
- readonly identity: string;
397
- readonly request: FontRequest;
398
- readonly family: string;
399
- /** Owned byte length, available without creating a defensive byte copy. */
400
- readonly byteLength: number;
401
- readonly bytes: Uint8Array;
402
- readonly hash: string;
403
- readonly faceIndex: number;
404
- readonly substitution: FontSubstitution | null;
405
- }
406
- /**
407
- * Largest single face this engine will ever admit, whatever a caller configures.
408
- *
409
- * A CEILING, not a default: a host may set a smaller `maxFontBytes`, but nothing can raise it
410
- * past this. Font bytes come from files, and an unbounded face is a memory-exhaustion vector.
411
- */
412
- declare const HARD_MAX_FONT_BYTES: number;
413
- /** Most faces one snapshot may hold, whatever a caller configures. */
414
- declare const HARD_MAX_FONT_SOURCES = 256;
415
- /** Most bytes all admitted faces may total, whatever a caller configures. */
416
- declare const HARD_MAX_AGGREGATE_FONT_BYTES: number;
417
- /**
418
- * Why a face was not admitted.
419
- *
420
- * `forbidden` and `hashMismatch` are adversarial signals rather than ordinary failures: the first
421
- * is a face the host declared off-limits, the second is bytes that are not what their source
422
- * claimed.
423
- */
424
- type FontResolutionErrorCode = 'missing' | 'forbidden' | 'overLimit' | 'malformed' | 'hashMismatch';
425
- /**
426
- * A face that could not be admitted, carrying whatever evidence the refusal produced.
427
- *
428
- * RETURNED rather than thrown by `FontResourceSnapshot.resolve`, because a missing face is an
429
- * ordinary condition — layout falls back and carries on. It extends `Error` so a caller that
430
- * would rather throw can.
431
- */
432
- declare class FontResolutionError extends Error {
433
- readonly name = "FontResolutionError";
434
- readonly request: FontRequest;
435
- readonly code: FontResolutionErrorCode;
436
- readonly limit?: number;
437
- readonly actual?: number;
438
- readonly diagnostic?: string;
439
- readonly expectedHash?: string;
440
- readonly actualHash?: string;
441
- constructor(code: FontResolutionErrorCode, request: FontRequest, details?: {
442
- readonly limit?: number;
443
- readonly actual?: number;
444
- readonly diagnostic?: string;
445
- readonly expectedHash?: string;
446
- readonly actualHash?: string;
447
- });
448
- }
449
- /**
450
- * One face offered to a snapshot, before admission.
451
- *
452
- * `hash` is checked against the bytes, so a source cannot claim a face it did not supply. Setting
453
- * `availability` to `forbidden` declares a face that exists but must not be used, which resolves
454
- * as a typed refusal rather than as "missing".
455
- */
456
- interface FontResourceDefinition {
457
- readonly request: FontRequest;
458
- readonly id: string;
459
- readonly bytes: Uint8Array;
460
- readonly hash: string;
461
- readonly faceIndex: number;
462
- readonly availability?: 'available' | 'forbidden';
463
- }
464
- /**
465
- * A host-declared redirect: requests for `from` resolve to `to`.
466
- *
467
- * How a metric-compatible substitute is wired in — a document naming Calibri resolves to Carlito
468
- * without the document being rewritten.
469
- */
470
- interface DeclaredFontSubstitution {
471
- readonly from: FontRequest;
472
- readonly to: FontRequest;
473
- readonly lineMetrics?: {
474
- readonly heightEm: number;
475
- readonly baselineEm: number;
476
- };
477
- }
478
- /** Whether bytes parse as a usable font, with a diagnostic when they do not. */
479
- type FontValidationResult = {
480
- readonly valid: true;
481
- } | {
482
- readonly valid: false;
483
- readonly diagnostic: string;
484
- };
485
- /**
486
- * The injected check that bytes really are a font.
487
- *
488
- * Injected because it is the shaper that knows — HarfBuzz can open a face and read its tables,
489
- * and this layer must not duplicate that judgement. Every byte reaching it is file input.
490
- */
491
- type FontByteValidator = (bytes: Uint8Array, faceIndex: number) => FontValidationResult;
492
- /**
493
- * An immutable set of admitted faces, and the only way to reach one.
494
- *
495
- * `epoch` identifies the snapshot: fonts change by REPLACING it, never by mutation, so a layout
496
- * pass holds one snapshot for its whole run and cannot observe a face appearing or vanishing
497
- * midway.
498
- */
499
- interface FontResourceSnapshot {
500
- readonly epoch: number;
501
- /** The admitted face, or a typed refusal. Never throws. */
502
- resolve(request: FontRequest): ResolvedFont | FontResolutionError;
503
- }
504
- /**
505
- * How a snapshot is built: which faces, under what limits, validated how.
506
- *
507
- * `maxFontBytes` is clamped to {@link HARD_MAX_FONT_BYTES} — a caller may tighten the budget but
508
- * never widen it past the engine's own ceiling.
509
- */
510
- interface FontResourceSnapshotOptions {
511
- readonly epoch: number;
512
- readonly maxFontBytes: number;
513
- readonly resources: readonly FontResourceDefinition[];
514
- readonly substitutions?: readonly DeclaredFontSubstitution[];
515
- readonly validateFont: FontByteValidator;
516
- readonly instrumentation?: FontResourceInstrumentation;
517
- }
518
- /**
519
- * Optional counters for the operations font admission is expected to do RARELY.
520
- *
521
- * Exists so tests can assert the absence of work: hashing and byte-copying a 64 MB face on every
522
- * resolve would not be visible in output, only in a stalled document, so the checks assert these
523
- * fire once rather than per call.
524
- */
525
- interface FontResourceInstrumentation {
526
- readonly onOwnedByteCopy?: () => void;
527
- readonly onHash?: () => void;
528
- readonly onTableScan?: () => void;
529
- readonly onAdmission?: () => void;
530
- }
531
- /**
532
- * The canonical string identifying one face request.
533
- *
534
- * Case- and whitespace-normalized, so `"Times New Roman"` and `"times new roman"` are one key —
535
- * a document may name a family either way and both must reach the same face.
536
- */
537
- declare const fontRequestKey: (request: FontRequest) => string;
538
- /** Bounded minimum sfnt/TTC check; Task 4 supplies full parser-backed validation. */
539
- declare const boundedStructuralFontValidator: FontByteValidator;
540
- /**
541
- * Admit a set of faces and return the immutable snapshot layout resolves against.
542
- *
543
- * Admission is the trust boundary for font bytes: counts and sizes are checked against the hard
544
- * ceilings, each face's hash is re-derived and compared, and the bytes are handed to the injected
545
- * validator. A face failing any of these is recorded as a typed refusal rather than dropped, so
546
- * `resolve` can explain itself instead of answering "missing".
547
- */
548
- declare const createFontResourceSnapshot: (options: FontResourceSnapshotOptions) => FontResourceSnapshot;
549
-
550
- declare const FIXED_POINT: unique symbol;
551
- /** Fixed-point coordinates are safe integers in units declared by ShapingEnvironment.fixedPointScale. */
552
- type FixedPoint = number & {
553
- readonly [FIXED_POINT]: true;
554
- };
555
- /**
556
- * Brand a safe integer as a {@link FixedPoint} coordinate.
557
- *
558
- * Fixed point rather than float throughout shaping so that two runs shaped identically compare
559
- * EQUAL — float accumulation would make the same text measure differently depending on how it was
560
- * split, and pagination is decided on those measurements.
561
- *
562
- * @throws RangeError when the value is not a safe integer.
563
- */
564
- declare const fixedPoint: (value: number) => FixedPoint;
565
- /** Which way a run reads. The parity projection of its bidi embedding level. */
566
- type TextDirection = 'ltr' | 'rtl';
567
- /** How fixed-point conversion breaks ties. Part of the shaping fingerprint. */
568
- type FixedPointRoundingMode = 'halfAwayFromZero' | 'halfToEven' | 'towardZero';
569
- /** Which Unicode normalization is applied before shaping, if any. */
570
- type NormalizationPolicy = 'none' | 'NFC' | 'NFD' | 'NFKC' | 'NFKD';
571
- /**
572
- * The shaping library and its exact version.
573
- *
574
- * Versioned because a library upgrade can change glyph positioning, and a cached measurement
575
- * taken under the old one must not be reused under the new one.
576
- */
577
- interface VersionedShapingLibrary {
578
- readonly name: string;
579
- readonly version: string;
580
- }
581
- /**
582
- * Everything that determines how text shapes — the complete input to a
583
- * {@link ShapingEnvironment}.
584
- *
585
- * Exhaustive on purpose. Any field that could change a glyph's position belongs here, because the
586
- * environment's fingerprint is what decides whether a cached shaped run may be reused.
587
- */
588
- interface ShapingEnvironmentInput {
589
- readonly font: ResolvedFont;
590
- readonly variationAxes: Readonly<Record<string, number>>;
591
- readonly shapingLibrary: VersionedShapingLibrary;
592
- readonly unicodeDataVersion: string;
593
- readonly normalization: NormalizationPolicy;
594
- readonly script: string;
595
- readonly language: string;
596
- readonly direction: TextDirection;
597
- readonly features: Readonly<Record<string, number>>;
598
- readonly fallbackOrder: readonly ResolvedFont[];
599
- readonly fixedPointScale: number;
600
- readonly roundingMode: FixedPointRoundingMode;
601
- }
602
- /**
603
- * A validated {@link ShapingEnvironmentInput} — build one with `createShapingEnvironment`.
604
- *
605
- * Structurally identical to its input, but the nominal distinction is the point: holding one
606
- * means the tags, axes and fonts inside it have already been checked.
607
- */
608
- interface ShapingEnvironment extends ShapingEnvironmentInput {
609
- }
610
- /** One shaping call: the text, its size, its bidi level, and the environment to shape it in. */
611
- interface ShapeInput {
612
- readonly text: string;
613
- readonly fontSizeHalfPoints: number;
614
- /** Exact UAX #9 embedding/isolate level; direction is its parity projection. */
615
- readonly bidiLevel: number;
616
- readonly environment: ShapingEnvironment;
617
- }
618
- /**
619
- * One positioned glyph.
620
- *
621
- * `id` is a glyph index in its FACE, not a character — a ligature is one glyph spanning several
622
- * characters, and a single character may produce several glyphs. Use `cluster` to get back to
623
- * text.
624
- */
625
- interface ShapedGlyph {
626
- readonly id: number;
627
- /** UTF-16 text offset identifying the cluster that produced this glyph. */
628
- readonly cluster: number;
629
- /** Pen origin before this glyph's shaping offsets, in fixed-point run coordinates. */
630
- readonly originX: FixedPoint;
631
- readonly originY: FixedPoint;
632
- readonly advanceX: FixedPoint;
633
- readonly advanceY: FixedPoint;
634
- readonly offsetX: FixedPoint;
635
- readonly offsetY: FixedPoint;
636
- /** Exact monochrome outline returned by the admitted HarfBuzz face. */
637
- readonly outline: GlyphOutline;
638
- }
639
- /** A glyph's outline as SVG path data, in font design units. */
640
- interface GlyphOutline {
641
- readonly path: string;
642
- /** Design units per em — divide by this to scale the path to a point size. */
643
- readonly unitsPerEm: number;
644
- }
645
- /**
646
- * One cluster: the smallest indivisible text-to-glyph correspondence.
647
- *
648
- * The unit the CARET moves by. A cluster may be several characters (a ligature) or several glyphs
649
- * (a decomposed mark), so neither a character index nor a glyph index is a valid caret position —
650
- * `caretEdges` is, and it includes both endpoints.
651
- */
652
- interface ShapedCluster {
653
- /** Half-open logical UTF-16 range in ShapedRun.text. */
654
- readonly textStart: number;
655
- readonly textEnd: number;
656
- /** Half-open visual glyph range in ShapedRun.glyphs. */
657
- readonly glyphStart: number;
658
- readonly glyphEnd: number;
659
- readonly advance: FixedPoint;
660
- /** Fixed-point edges from this cluster's visual origin, including both endpoints. */
661
- readonly caretEdges: readonly FixedPoint[];
662
- /** Index into ShapedRun.fontSpans, preserving the exact fallback choice. */
663
- readonly fontSpan: number;
664
- }
665
- /** A run's vertical metrics, from the face that shaped it. Drives line height and baseline. */
666
- interface ShapedVerticalMetrics {
667
- readonly ascent: FixedPoint;
668
- readonly descent: FixedPoint;
669
- readonly lineGap: FixedPoint;
670
- }
671
- /**
672
- * A stretch of glyphs that came from ONE face.
673
- *
674
- * A run whose text needed fallback has several of these. Recording the exact face per span is
675
- * what lets a re-shape reproduce the same result rather than re-running fallback selection and
676
- * possibly choosing differently.
677
- */
678
- interface ShapedFontSpan {
679
- readonly glyphStart: number;
680
- readonly glyphEnd: number;
681
- readonly font: ResolvedFont;
682
- /** null denotes the primary font; otherwise this is the environment fallback-order index. */
683
- readonly fallbackIndex: number | null;
684
- }
685
- /**
686
- * One shaped run: text turned into positioned glyphs, with everything needed to measure it, paint
687
- * it, and put a caret in it.
688
- *
689
- * The engine's measurement unit. Layout never measures characters — it measures these.
690
- */
691
- interface ShapedRun {
692
- readonly text: string;
693
- readonly direction: TextDirection;
694
- readonly bidiLevel: number;
695
- readonly glyphs: readonly ShapedGlyph[];
696
- readonly clusters: readonly ShapedCluster[];
697
- readonly fontSpans: readonly ShapedFontSpan[];
698
- readonly metrics: ShapedVerticalMetrics;
699
- }
700
- /**
701
- * A {@link ShapedRun} reduced to the fields two shaping results must agree on to be considered
702
- * identical.
703
- *
704
- * Fonts appear as {@link FontFingerprintInputs} rather than whole `ResolvedFont` objects, so the
705
- * comparison is over VALUES and does not depend on object identity — which is what makes it work
706
- * across a reload or a worker boundary.
707
- */
708
- interface ShapedRunComparatorInputs {
709
- readonly text: string;
710
- readonly direction: TextDirection;
711
- readonly script: string;
712
- readonly language: string;
713
- readonly bidiLevel: number;
714
- readonly glyphs: readonly ShapedGlyph[];
715
- readonly clusters: readonly ShapedCluster[];
716
- readonly fontSpans: readonly {
717
- readonly glyphStart: number;
718
- readonly glyphEnd: number;
719
- readonly fallbackIndex: number | null;
720
- readonly font: FontFingerprintInputs;
721
- }[];
722
- readonly metrics: ShapedVerticalMetrics;
723
- }
724
- /**
725
- * The one thing layout needs from a shaping backend.
726
- *
727
- * Injected rather than imported, which is what keeps layout DOM-free and testable: a fixed-metric
728
- * shaper measures deterministically in a test, HarfBuzz measures for real in a browser, and
729
- * layout cannot tell the difference.
730
- */
731
- interface TextShaper {
732
- shape(input: ShapeInput): ShapedRun;
733
- }
734
- /**
735
- * A font reduced to the values that identify it for fingerprinting.
736
- *
737
- * Includes the content `hash` and `faceIndex`, so two faces with the same family name but
738
- * different bytes fingerprint differently — which is what stops a cached measurement being reused
739
- * against a substituted face.
740
- */
741
- interface FontFingerprintInputs {
742
- readonly identity: string;
743
- readonly id: string;
744
- readonly family: string;
745
- readonly request: FontRequest;
746
- readonly hash: string;
747
- readonly faceIndex: number;
748
- readonly byteLength: number;
749
- readonly substitution: FontSubstitution | null;
750
- }
751
- /**
752
- * A {@link ShapingEnvironment} reduced to comparable values.
753
- *
754
- * Records and axis maps become SORTED entry arrays, because two environments differing only in
755
- * key insertion order must fingerprint the same — otherwise a cache would miss on a difference
756
- * that changes no glyph.
757
- */
758
- interface ShapingEnvironmentFingerprintInputs {
759
- readonly font: FontFingerprintInputs;
760
- readonly variationAxes: readonly (readonly [string, number])[];
761
- readonly shapingLibrary: VersionedShapingLibrary;
762
- readonly unicodeDataVersion: string;
763
- readonly normalization: NormalizationPolicy;
764
- readonly script: string;
765
- readonly language: string;
766
- readonly direction: TextDirection;
767
- readonly features: readonly (readonly [string, number])[];
768
- readonly fallbackOrder: readonly FontFingerprintInputs[];
769
- readonly fixedPointScale: number;
770
- readonly roundingMode: FixedPointRoundingMode;
771
- }
772
- /**
773
- * Validate and freeze a shaping environment.
774
- *
775
- * Checks every field that could silently corrupt a measurement: OpenType tags must be four ASCII
776
- * bytes, script and language must be non-blank and control-character free, fonts must already be
777
- * validated. Throws rather than coercing — a bad tag that shapes anyway produces a document that
778
- * measures wrong everywhere and looks fine.
779
- *
780
- * @throws TypeError on a malformed tag, name, or axis value.
781
- */
782
- declare const createShapingEnvironment: (input: ShapingEnvironmentInput) => ShapingEnvironment;
783
- /**
784
- * Reduce an environment to its comparable form, with records sorted so key order cannot affect
785
- * the result.
786
- */
787
- declare const shapingEnvironmentFingerprintInputs: (input: ShapingEnvironmentInput) => ShapingEnvironmentFingerprintInputs;
788
- /** Canonical serialization of every shaping variable, including byte hash and provenance. */
789
- declare const shapingEnvironmentFingerprint: (environment: ShapingEnvironmentInput) => string;
790
- /**
791
- * Validate and freeze a shaped run against the environment that produced it.
792
- *
793
- * Checks the internal consistency a shaper must satisfy: direction matches the environment,
794
- * cluster ranges tile the text without gaps or overlap, glyph ranges stay in bounds, font spans
795
- * cover every glyph, and caret edges are monotonic. A shaper that violates any of these produces
796
- * a caret that lands in the wrong place, which is far harder to diagnose downstream than here.
797
- *
798
- * @throws TypeError when the run and environment disagree, or the run is internally inconsistent.
799
- */
800
- declare const createShapedRun: (input: ShapedRun, environmentInput: ShapingEnvironmentInput) => ShapedRun;
801
- /**
802
- * Reduce a shaped run to its comparable form, validating it on the way through.
803
- *
804
- * What the D9 determinism oracles compare: two shaping runs of the same text in the same
805
- * environment must produce byte-identical structures here.
806
- */
807
- declare const shapedRunComparatorInputs: (input: ShapedRun, environment: ShapingEnvironmentInput) => ShapedRunComparatorInputs;
808
-
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;
817
- }
818
- /**
819
- * A run's character properties after the full cascade, in the units layout works in.
820
- *
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.
949
- */
950
- declare class UnsupportedScriptError extends Error {
951
- readonly name = "UnsupportedScriptError";
952
- readonly code = "unsupportedScript";
953
- readonly codePoint: number;
954
- constructor(codePoint: number);
955
- }
956
- /**
957
- * Split text into shapeable runs by script, bidi level and font slot.
958
- *
959
- * Consumes the bidi levels rather than re-deriving direction, so itemization and reordering agree
960
- * by construction instead of by two implementations happening to match.
961
- */
962
- declare function itemizeScriptFontSlots(text: string, paragraphOffset: number, embedding: BidiEmbeddingLevels): readonly ScriptItem[];
963
- /**
964
- * The style a piece's text is MEASURED AND PAINTED in, given the font slot it carries.
965
- *
966
- * This is the measurer-boundary resolution: pieces and spans keep the run's real resolved
967
- * style — with `fontFamily` and `fontFamilyEastAsia` both intact, so formatting readback
968
- * and the format painter see the run as authored — and every consumer that turns text into
969
- * geometry or ink resolves the face through here. The derived object is memoized per
970
- * (style, family) by {@link withFontFamily}, so measurers amortizing over style identity
971
- * keep their caches.
972
- */
973
- declare function styleForFontSlot(style: ResolvedRunStyle, slot: FontSlot | undefined): ResolvedRunStyle;
974
-
975
- type AllowlistedPageField = 'PAGE' | 'NUMPAGES' | 'SECTIONPAGES';
976
- /**
977
- * Which allowlisted page fields a header/footer story actually contains.
978
- *
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).
983
- */
984
- interface StoryPageFieldNeeds {
985
- readonly hasPage: boolean;
986
- readonly hasNumPages: boolean;
987
- readonly hasSectionPages: boolean;
988
- }
989
-
990
- /** One recognized REF instruction: the target name and the supported switches, nothing else. */
991
- interface RefFieldSpec {
992
- readonly bookmark: string;
993
- /** `r` / `w` paint the target's number; `n` the same without context; null the range text. */
994
- readonly numberSwitch: 'r' | 'w' | 'n' | null;
995
- /** `\h` parsed and inert — the reference paints; navigation is a follow-up. */
996
- readonly hyperlink: boolean;
997
- }
998
- /**
999
- * Recognize `REF <bookmark> [\r|\w|\n] [\t] [\h] [\* MERGEFORMAT]` or
1000
- * `NOTEREF <bookmark> [\h] [\* MERGEFORMAT]`, or null for anything else.
1001
- *
1002
- * The keyword matches case-insensitively; the bookmark name keeps its authored case (it is a
1003
- * lookup key into a Map, never an object property, so hostile names like `__proto__` are just
1004
- * names that resolve to nothing). Any unrecognized switch fails the parse so the field falls
1005
- * back to its cached result — never the raw instruction, never a guess. NOTEREF's `\p`
1006
- * (above/below position text) and `\f` (note-style formatting) are unrecognized on purpose.
1007
- */
1008
- declare function parseRefInstruction(raw: string): RefFieldSpec | null;
1009
-
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
- /**
1019
- * One level's indent, plus which parts the level actually AUTHORED.
1020
- *
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.
1023
- */
1024
- interface NumberingLevelIndent {
1025
- readonly left: number;
1026
- readonly right: number;
1027
- readonly hanging: number;
1028
- readonly firstLine: number;
1029
- /**
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.
1036
- */
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;
1053
- /**
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.
1056
- */
1057
- readonly lvlRestart?: number;
1058
- /**
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.
1061
- */
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;
1067
- }
1068
- /**
1069
- * One `w:lvlOverride` on a `w:num`: a restart value, a replacement level, or both.
1070
- *
1071
- * How two lists share an abstract definition while numbering independently.
1072
- */
1073
- interface LevelOverride {
1074
- readonly startOverride?: number;
1075
- /** Full level replacement when `w:lvl` is present under the override. */
1076
- readonly level?: NumberingLevel;
1077
- }
1078
- /**
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.
1083
- */
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;
1099
- }
1100
- /**
1101
- * One `w:num` — the thing a paragraph's `w:numId` actually names.
1102
- *
1103
- * Points at an {@link AbstractNumDefinition} and may override any of its levels.
1104
- */
1105
- interface NumDefinition {
1106
- readonly numId: string;
1107
- readonly abstractNumId: string;
1108
- readonly overrides: ReadonlyMap<number, LevelOverride>;
1109
- }
1110
- /**
1111
- * The bounded projection of `numbering.xml` that list layout resolves against.
1112
- *
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.
1115
- */
1116
- interface NumberingIndex {
1117
- readonly abstractNums: ReadonlyMap<string, AbstractNumDefinition>;
1118
- readonly nums: ReadonlyMap<string, NumDefinition>;
1119
- }
1120
- /**
1121
- * Build a numbering index from the root of a numbering part (`w:numbering`).
1122
- *
1123
- * Empty / missing roots yield an empty index. Duplicate ids keep the first definition.
1124
- */
1125
- /**
1126
- * Project `numbering.xml` into the bounded index.
1127
- *
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.
1131
- */
1132
- declare function buildNumberingIndex(root: OoxmlElement | null | undefined): NumberingIndex;
1133
- /** Resolve the effective level for a `numId` + `ilvl`, applying overrides. */
1134
- /**
1135
- * The effective level for one paragraph's numbering reference, after overrides and style links.
1136
- *
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.
1139
- */
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;
1147
-
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;
1157
- }
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
-
1172
- /** Soft ceiling on `basedOn` chain length — enough for real templates, refuses hostile graphs. */
1173
- declare const MAX_STYLE_BASED_ON_DEPTH = 32;
1174
- /** Soft ceiling on style definitions read from one styles part. */
1175
- declare const MAX_STYLE_DEFINITIONS = 4096;
1176
- /** One `w:style` as the cascade reads it: its properties, and the style it is based on. */
1177
- interface StyleDefinition {
1178
- readonly styleId: string;
1179
- readonly type: string;
1180
- readonly basedOn: string | null;
1181
- /**
1182
- * `w:next` — the style Word gives the paragraph that FOLLOWS one in this style.
1183
- *
1184
- * Nothing in layout reads it: it is a rule about authoring, not about painting. It is
1185
- * read here because this is the one place `styles.xml` is parsed, and a second reader
1186
- * would be a second answer to what a style id means.
1187
- */
1188
- readonly next: string | null;
1189
- readonly paragraphProperties: readonly OoxmlProperty[];
1190
- readonly runProperties: readonly OoxmlProperty[];
1191
- /** The style's `w:pPr` node, when present — needed for nested `w:pBdr`. */
1192
- readonly paragraphPropertiesNode: OoxmlElement | undefined;
1193
- /** The style's `w:tblPr`, for a `w:type="table"` style. */
1194
- readonly tablePropertiesNode: OoxmlElement | undefined;
1195
- /**
1196
- * `w:tblStylePr` conditional formats by `w:type` (`firstRow`, `band1Horz`, …).
1197
- *
1198
- * Word puts a table's real appearance here: `Table Grid` carries its grid in the style's
1199
- * `w:tblBorders`, and the banded/​header looks live in these. A document states only
1200
- * `<w:tblStyle w:val="TableGrid"/>`.
1201
- */
1202
- readonly conditionalTableFormats: ReadonlyMap<string, OoxmlElement>;
1203
- }
1204
- /**
1205
- * The whole styles part, indexed and ready to resolve against.
1206
- *
1207
- * `cacheToken` is load-bearing: it folds into layout cache producers so breaks measured under one
1208
- * styles part are never reused under another.
1209
- */
1210
- interface StyleCascadeTable {
1211
- /**
1212
- * Bounded fingerprint folded into layout cache producers so a different styles part cannot
1213
- * reuse breaks measured under another cascade. Computed once per table (FNV-1a hex).
1214
- */
1215
- readonly cacheToken: string;
1216
- readonly docDefaultsRun: readonly OoxmlProperty[];
1217
- readonly docDefaultsParagraph: readonly OoxmlProperty[];
1218
- readonly docDefaultsParagraphNode: OoxmlElement | undefined;
1219
- /** `w:style[@w:default='1'][@w:type='paragraph']` — last wins among defaults of that type. */
1220
- readonly defaultParagraphStyleId: string | null;
1221
- /** `w:style[@w:default='1'][@w:type='character']` — last wins among defaults of that type. */
1222
- readonly defaultCharacterStyleId: string | null;
1223
- /**
1224
- * `w:style[@w:default='1'][@w:type='table']` — last wins among defaults of that type.
1225
- *
1226
- * A `w:tbl` with no `w:tblStyle` still resolves against this one. Word's own
1227
- * `TableNormal` is where the 0/108/0/108 twip cell margins live, so skipping it gives
1228
- * every unstyled table padding the document says it does not have.
1229
- */
1230
- readonly defaultTableStyleId: string | null;
1231
- /**
1232
- * The theme part's typefaces, for `w:rFonts` theme references.
1233
- *
1234
- * Lives on the cascade because it is document-level style material with the same
1235
- * lifetime, and because every site that resolves run properties already holds this table.
1236
- */
1237
- readonly themeFonts: ThemeFonts;
1238
- readonly styles: ReadonlyMap<string, StyleDefinition>;
1239
- }
1240
- /**
1241
- * A paragraph's properties after the cascade, plus the same list WITHOUT its own `w:pPr`.
1242
- *
1243
- * Both, because a writer needs to know what a paragraph INHERITS to decide whether setting a
1244
- * value is a change or a no-op — and writing back an inherited value freezes it into the
1245
- * paragraph as though the author had chosen it.
1246
- */
1247
- interface CascadedParagraphFormatting {
1248
- /** Flat paragraph properties in cascade order (defaults → bases → style → direct). */
1249
- readonly paragraphProperties: readonly OoxmlProperty[];
1250
- /**
1251
- * The same list WITHOUT the paragraph's own `w:pPr` — everything it inherits.
1252
- *
1253
- * Numbering needs the two tiers apart: a level's `w:pPr/w:ind` outranks the style's and is
1254
- * outranked by the paragraph's own, and a flattened list cannot say which is which.
1255
- */
1256
- readonly inheritedParagraphProperties: readonly OoxmlProperty[];
1257
- /** Matching `w:pPr` nodes for nested border resolution. */
1258
- readonly paragraphPropertyNodes: readonly OoxmlNode[];
1259
- /**
1260
- * Inherited run properties for CONTENT runs (before direct run `rPr`).
1261
- *
1262
- * Does NOT include direct `w:pPr/w:rPr` — that formats the paragraph MARK only
1263
- * (ECMA-376 §17.3.1.36). Folding mark `w:sz` into content made BodyText runs with no
1264
- * direct size paint at the mark's size (Selection Notice "or" alternative → 6.5pt).
1265
- */
1266
- readonly runProperties: readonly OoxmlProperty[];
1267
- /**
1268
- * Content cascade plus direct `w:pPr/w:rPr` — empty-line metrics and last-line mark height.
1269
- */
1270
- readonly markRunProperties: readonly OoxmlProperty[];
1271
- /**
1272
- * The style this paragraph resolved to, or null when it names none and there is no
1273
- * document default. `w:contextualSpacing` compares neighbours by exactly this.
1274
- */
1275
- readonly styleId: string | null;
1276
- }
1277
- /** Accepted style ids only — over-long, control-bearing, or dangerous keys are dropped. */
1278
- declare function isValidStyleId(raw: string | undefined): raw is string;
1279
- /**
1280
- * What a table style contributes to the paragraphs of ONE cell: the style's whole-table
1281
- * `w:pPr`/`w:rPr` followed by every `w:tblStylePr` the cell is under (17.7.6.6), weakest
1282
- * first in the caller's condition order (banding, column, row, corner).
1283
- *
1284
- * This is how Word makes a header row bold and centred while the document states nothing
1285
- * but `<w:tblStyle w:val="…"/>` on the table and plain runs in the cells.
1286
- */
1287
- interface TableCellStyleFormatting {
1288
- readonly paragraphProperties: readonly OoxmlProperty[];
1289
- /** Matching `w:pPr` nodes, for nested `w:pBdr` / `w:tabs` resolution. */
1290
- readonly paragraphPropertyNodes: readonly OoxmlElement[];
1291
- /** Inherited run properties for every run in the cell, before the paragraph style. */
1292
- readonly runProperties: readonly OoxmlProperty[];
1293
- }
1294
- /**
1295
- * Build a cascade table from a styles part root.
1296
- *
1297
- * Only direct `w:style` children of the root participate (bounded count). Duplicate
1298
- * `styleId` values keep the last definition, matching Word's reader for this fixture class.
1299
- * Default paragraph/character style ids track `w:default="1"` with the same last-wins rule.
1300
- */
1301
- declare function buildStyleCascadeTable(stylesRoot: OoxmlElement | null, themeFonts?: ThemeFonts): StyleCascadeTable;
1302
- /**
1303
- * Cascade paragraph + inherited run properties for one paragraph's direct `w:pPr`.
1304
- *
1305
- * Order: `docDefaults` → table style → `basedOn` ancestors → paragraph style → direct
1306
- * formatting, which is the style hierarchy of 17.7.2: a table style sits above the document
1307
- * defaults and below the paragraph style a cell paragraph names for itself.
1308
- * When `w:pStyle` is absent, the document's default paragraph style (`w:default="1"`) is used.
1309
- * Direct formatting is last so it overrides inherited values inside the existing resolvers.
1310
- */
1311
- declare function cascadeParagraphFormatting(table: StyleCascadeTable, directPPr: OoxmlNode | undefined, tableCellStyle?: TableCellStyleFormatting): CascadedParagraphFormatting;
1312
- /**
1313
- * Bottom border after cascade: a later `w:pBdr` replaces an earlier one; absence inherits.
1314
- * `nil`/`none` clear the edge via `paragraphBorders`.
1315
- */
1316
- declare function cascadedBottomBorder(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorderEdge | undefined;
1317
- /**
1318
- * Merge inherited paragraph-style run props with a run's direct `rPr` (direct last).
1319
- *
1320
- * When a cascade table is supplied, also resolves `w:rStyle` character styles (basedOn chain,
1321
- * cycle/depth capped). Runs without an explicit `rStyle` pick up the default character style
1322
- * (`w:default="1"`). Precedence: inherited → character style chain → direct formatting.
1323
- *
1324
- * PASS `inheritedRunProperties` BY IDENTITY. It must be an array
1325
- * {@link cascadeParagraphFormatting} returned — `runProperties` or `markRunProperties` — and
1326
- * not a copy of one. A toggle property (ECMA-376 §17.7.3) resolves per level of the style
1327
- * hierarchy, and the levels below the character style resolve to more than a true or a false:
1328
- * whether the document defaults' short circuit is still standing decides what the character
1329
- * style's own toggle does next, and no single `w:b` element can spell that. The paragraph
1330
- * cascade attaches that state to the array it returns, so spreading, filtering or sorting the
1331
- * list drops it. (`Object.freeze` returns the same object, so that one is safe.) A list
1332
- * without it is read as one ordinary level, which is the most a bare property list can say
1333
- * and is what a caller assembling its own list gets.
1334
- */
1335
- declare function cascadeRunProperties(inheritedRunProperties: readonly OoxmlProperty[], directRunProperties: readonly OoxmlProperty[], table?: StyleCascadeTable): readonly OoxmlProperty[];
1336
- /** Everything the line breaker needs about one paragraph, already cascaded and converted. */
1337
- interface ParagraphLayoutInputs {
1338
- readonly props: OoxmlProperty[];
1339
- readonly indent: {
1340
- left: number;
1341
- right: number;
1342
- hanging: number;
1343
- firstLine: number;
1344
- };
1345
- readonly available: number;
1346
- readonly alignment: Alignment;
1347
- readonly spacing: ParagraphSpacing;
1348
- /** Resolved `w:line` / `w:lineRule`; single spacing where the cascade says nothing. */
1349
- readonly lineSpacing: ParagraphLineSpacing;
1350
- /** `w:contextualSpacing`: drop before/after between same-style neighbours. */
1351
- readonly contextualSpacing: boolean;
1352
- /** Resolved paragraph style id, for the `w:contextualSpacing` neighbour comparison. */
1353
- readonly styleId: string | null;
1354
- readonly bottomBorder: ParagraphBorderEdge | undefined;
1355
- /**
1356
- * Every `CT_PBdr` edge after cascade, not just the bottom one.
1357
- *
1358
- * `bottomBorder` stays alongside it because the fragment signature and the table flow
1359
- * read that field by name; this is the whole box, so a cell paragraph gets the same
1360
- * frame a body paragraph does.
1361
- */
1362
- readonly borders: ParagraphBorders;
1363
- /** Validated 6-hex paragraph shading fill from cascaded `w:pPr/w:shd`, absent for none. */
1364
- readonly shading: string | undefined;
1365
- readonly inheritedRunProperties: readonly OoxmlProperty[];
1366
- /**
1367
- * Paragraph-mark cascade (`inheritedRunProperties` + direct `w:pPr/w:rPr`).
1368
- * Empty-line sizing and last-line mark height — never content-run face.
1369
- */
1370
- readonly markRunProperties: readonly OoxmlProperty[];
1371
- /** Cascaded custom tab stops + default interval for paragraph-flow breaking. */
1372
- readonly tabStops: ResolvedTabStops;
1373
- /**
1374
- * Fingerprint folded into the paragraph layout cache key — nested `w:tabs` are absent
1375
- * from flat property bags, so style-inherited stops must be named explicitly.
1376
- */
1377
- readonly tabStopsCacheToken: string;
1378
- /** Resolved list marker inputs when the paragraph participates in numbering. */
1379
- readonly listItem?: ResolvedListItem;
1380
- }
1381
- /**
1382
- * Resolve every paragraph input semantic layout / table cells share: cascaded props when a
1383
- * style table is present, otherwise direct formatting only.
1384
- *
1385
- * When `listItem` is provided, its merged level indent becomes the paragraph indent (list
1386
- * hanging / left from `numbering.xml`), which is what Word uses for fixture list paragraphs
1387
- * that author no direct `w:ind`.
1388
- *
1389
- * `tableCellStyle` carries what the enclosing table's style says about this cell's
1390
- * paragraphs; body paragraphs pass nothing.
1391
- *
1392
- * `inTableCell` is asked for separately because a cell paragraph may have no table style to
1393
- * inherit at all, and `w:beforeAutospacing` still needs to know it is in a cell.
1394
- */
1395
- declare function resolveParagraphLayoutInputs(paragraph: OoxmlElement, contentWidth: number, styleCascade: StyleCascadeTable | undefined, listItem?: ResolvedListItem, tableCellStyle?: TableCellStyleFormatting, inTableCell?: boolean): ParagraphLayoutInputs;
1396
-
1397
- /**
1398
- * A paragraph's list membership fully resolved: definition, level, marker text and geometry.
1399
- *
1400
- * `markerText` is already expanded through the counter state, so it is the string a reader sees
1401
- * rather than the `w:lvlText` template.
1402
- */
1403
- interface ResolvedListItem {
1404
- readonly numId: string;
1405
- readonly ilvl: number;
1406
- readonly abstractNumId: string;
1407
- /** `w:numFmt` of the resolved level — `bullet` or a numbering format. */
1408
- readonly numFmt: string;
1409
- readonly markerText: string;
1410
- readonly markerAlign: ListMarkerAlign;
1411
- readonly suffix: ListSuffix;
1412
- /** Effective indent after merging level + paragraph indents, in points. */
1413
- readonly indent: NumberingLevelIndent;
1414
- readonly markerStyle: ResolvedRunStyle;
1415
- /** Fingerprint for layout cache keys (indent, marker text, marker face). */
1416
- readonly cacheToken: string;
1417
- }
1418
- /**
1419
- * Read `w:numPr` from cascaded paragraph-property nodes (lowest precedence first).
1420
- *
1421
- * Flat `OoxmlProperty[]` bags drop nested `ilvl`/`numId`, so this walks the tree nodes
1422
- * the same way borders and tabs do.
1423
- *
1424
- * `w:ilvl` and `w:numId` inherit INDEPENDENTLY through the style chain and the direct
1425
- * `w:pPr` (§17.3.1.19): a tier stating only the level keeps the id it inherits — Word's
1426
- * standard Heading2–Heading9 shape, where only Heading1 names the `w:num` — and a tier
1427
- * stating only the id keeps the inherited level. Treating each `w:numPr` node as a full
1428
- * replacement dropped the id at every level-only tier and unnumbered the paragraph. A
1429
- * stated `w:numId w:val="0"` (or an invalid value) still switches numbering off at that
1430
- * tier even when a lower tier set one; a higher tier stating a valid id re-enables. A
1431
- * level-only tier with no id inherited from below resolves to no numbering.
1432
- */
1433
- declare function readNumPr(paragraphPropertyNodes: readonly OoxmlNode[]): {
1434
- numId: string;
1435
- ilvl: number;
1436
- } | null;
1437
- /**
1438
- * Resolve `w:numStyleLink` delegation using the document's styles (§17.9.21).
1439
- *
1440
- * Without a style table there is nothing to follow, so the index is returned unchanged —
1441
- * and so it is when nothing delegates, which keeps layout cache identity.
1442
- */
1443
- declare function withNumberingStyleLinks(index: NumberingIndex, styleCascade: StyleCascadeTable | undefined): NumberingIndex;
1444
- /**
1445
- * The effective indent of a list paragraph: STYLE, then the numbering LEVEL, then DIRECT.
1446
- *
1447
- * Word applies a level's `w:pPr/w:ind` between the paragraph style and the paragraph's own
1448
- * formatting, per attribute — and the ordering matters on real documents. A converted
1449
- * agreement numbers its `(a)` items with a level stating `left=1512 hanging=738` under a
1450
- * `ListParagraph` style stating `left=775 hanging=624`, and states only `hanging="737"` on
1451
- * the paragraph itself. Reading the flattened cascade as "the paragraph's indent" gave the
1452
- * STYLE's 775 to a level that had overridden it, so every lettered sub-item hung a full
1453
- * indent step to the left of where Word puts it.
1454
- *
1455
- * `inherited` is the cascade WITHOUT the paragraph's own `w:pPr` (defaults, table cell style,
1456
- * style chain); `direct` is that `w:pPr` alone.
1457
- */
1458
- declare function mergeListIndent(levelIndent: NumberingLevelIndent, inherited: readonly OoxmlProperty[], direct?: readonly OoxmlProperty[]): NumberingLevelIndent;
1459
- /**
1460
- * Collect paragraphs of a block list in document order, descending into tables.
1461
- *
1462
- * Caps nesting so a hostile nested-table document cannot recurse without bound.
1463
- */
1464
- declare function walkStoryParagraphs(blocks: readonly OoxmlElement[], maxTableDepth?: number): OoxmlElement[];
1465
- /**
1466
- * Resolve every list paragraph in a story to a {@link ResolvedListItem}, keyed by node id.
1467
- *
1468
- * Non-list paragraphs are absent from the map. Hostile / missing numbering resolves inertly
1469
- * (paragraph omitted — laid out as ordinary text).
1470
- */
1471
- declare function resolveStoryListItems(blocks: readonly OoxmlElement[], index: NumberingIndex, styleCascade: StyleCascadeTable | undefined, isFontAvailable?: (family: string) => boolean): ReadonlyMap<string, ResolvedListItem>;
1472
- /**
1473
- * Attach a full-story list-item map to layout options.
1474
- *
1475
- * Resolves once over `blocks` (body story including table cells) so counters continue across
1476
- * section boundaries. No-ops when numbering is absent.
1477
- */
1478
- declare function withResolvedListItems<T extends {
1479
- readonly numberingIndex?: NumberingIndex;
1480
- readonly listItems?: ReadonlyMap<string, ResolvedListItem>;
1481
- readonly styleCascade?: StyleCascadeTable;
1482
- readonly isFontAvailable?: (family: string) => boolean;
1483
- }>(options: T, blocks: readonly OoxmlElement[]): T & {
1484
- readonly numberingIndex: NumberingIndex;
1485
- readonly listItems?: ReadonlyMap<string, ResolvedListItem>;
1486
- };
1487
- /**
1488
- * Horizontal marker box inside the first-line indent slot.
1489
- *
1490
- * Coordinates are relative to the same origin as paragraph content (`indent.left` is the
1491
- * text start). Returns null when there is nothing to paint.
1492
- *
1493
- * `w:hanging` and a positive `w:firstLine` are one mutually exclusive slot
1494
- * (§17.3.1.10, §17.3.1.12), so the marker has two placements, not an interaction:
1495
- * a hanging level puts the marker BEFORE the text start (`left - hanging`); a
1496
- * positive-firstLine level puts it AFTER (`left + firstLine`) — the standard legal
1497
- * shape `w:ind w:left="0" w:firstLine="720"` numbers at 0.5" while continuation
1498
- * lines return to the margin. Reading only the hanging model painted every such
1499
- * marker at the left margin.
1500
- */
1501
- declare function listMarkerBox(item: ResolvedListItem, markerWidth: number, lineY: number, lineHeight: number): {
1502
- x: number;
1503
- y: number;
1504
- width: number;
1505
- height: number;
1506
- } | null;
1507
-
1508
- /**
1509
- * The story's resolved REF inputs for one layout pass.
1510
- *
1511
- * Threaded as a runtime rider on the layout options (see `layoutSemanticDocument`) rather
1512
- * than a `SemanticLayoutOptions` member, so the public options surface stays put. Absent
1513
- * means every REF field paints its cached result — the pre-existing degradation, and the one
1514
- * header/footer and text-box stories still take. Note stories receive the body's context
1515
- * through `NotesLayoutInput.refFields`.
1516
- */
1517
- interface RefFieldContext {
1518
- /**
1519
- * Content token over every PAINTED REF output in the story, for the section prepass memo.
1520
- * A renumbering edit can move a REF value in a section whose own blocks and list map are
1521
- * identity-unchanged, and this token is the only validator that sees it. A field that
1522
- * failed calibration contributes its (session-constant) cached text, so the token still
1523
- * moves exactly when painted output moves.
1524
- */
1525
- readonly valuesToken: string;
1526
- /** The paragraph's REF outputs folded for its block cache key; `''` when it holds none. */
1527
- tokenForParagraph(paragraphId: string): string;
1528
- /**
1529
- * The live value ONE field paints, keyed by its begin / `w:fldSimple` node id, or null to
1530
- * keep the cached result (failed calibration, unresolvable, or an anchor this scan never
1531
- * saw). Anchor-keyed so the projection's paint and this context's token fold read the SAME
1532
- * calibration verdict, however each walk collected the field's cached text.
1533
- */
1534
- liveValueOf(anchorId: string, spec: RefFieldSpec): string | null;
1535
- /**
1536
- * The synthesized display of ONE AUTONUM-family field, keyed by its begin / `w:fldSimple`
1537
- * node id, or null to paint nothing (unsupported switches, or an anchor this scan never
1538
- * saw — both the field's historical rendering). These fields carry no cached result at
1539
- * all, so there is no calibration: the sequential value is the only display they have.
1540
- * Optional so hand-built contexts predating it stay valid.
1541
- */
1542
- autonumValueOf?(anchorId: string): string | null;
1543
- /**
1544
- * The deferred projection of ONE `PAGEREF` field, or null to keep the cached result
1545
- * (missing bookmark, unsupported switches, or an anchor this scan never saw).
1546
- *
1547
- * A `PAGEREF` value is a property of pagination, so the scan cannot compute it — it hands
1548
- * the projection the resolved TARGET and the calibration inputs instead, and document
1549
- * finalize (`finalizePageFieldProjection`) substitutes the page number the target's first
1550
- * fragment lands on. Optional so hand-built contexts predating it stay valid.
1551
- */
1552
- pageRefProjectionOf?(anchorId: string, spec: RefFieldSpec): PageRefFieldProjection | null;
1553
- }
1554
- /**
1555
- * Sticky calibration identity for one `PAGEREF` field.
1556
- *
1557
- * An opaque frozen object rather than a mutable verdict holder because span markers are
1558
- * serialized into fragment signatures — a verdict written into the marker would move a
1559
- * signature that no painted output moved. The verdict itself lives beside the finalize pass
1560
- * (`field-page-furniture.ts`), keyed weakly on this object; the registry below carries the
1561
- * object across passes the same way REF verdicts are carried.
1562
- */
1563
- type PageRefCalibrationCell = Readonly<Record<never, never>>;
1564
- /**
1565
- * What a `PAGEREF` span carries to document finalize: the resolved target, the normalized
1566
- * authored cache (the calibration oracle), and the sticky calibration identity.
1567
- */
1568
- interface PageRefFieldProjection {
1569
- /** Canonical id of the paragraph the bookmark names — first declaration wins. */
1570
- readonly targetParagraphId: string;
1571
- /** The authored cached result, whitespace-collapsed — what the computed number must reproduce. */
1572
- readonly cached: string;
1573
- readonly calibration: PageRefCalibrationCell;
1574
- }
1575
- /**
1576
- * Note parts whose stories join the context: their REF fields resolve against the body's
1577
- * bookmarks and numbering (a footnote citing "Section 1.2(c)" targets a body paragraph),
1578
- * and their bookmark declarations become plain-REF targets. Number switches aimed AT a
1579
- * note paragraph still fall back to the cached result — the list map is the body's.
1580
- */
1581
- interface RefNoteParts {
1582
- readonly footnotesPart: OoxmlPart | null;
1583
- readonly endnotesPart: OoxmlPart | null;
1584
- }
1585
- /**
1586
- * Resolve the document's REF fields for one layout pass, or null when it has none.
1587
- *
1588
- * Bookmarks and REF fields resolve across the body story and (when `notes` is given) the
1589
- * footnote/endnote stories, against ONE shared target index — a footnote REF finds the
1590
- * body bookmark it cites. Header/footer and text-box stories are not given a context and
1591
- * keep painting cached results.
1592
- */
1593
- declare function resolveStoryRefFields(blocks: readonly OoxmlElement[], listItems: ReadonlyMap<string, ResolvedListItem> | undefined, notes?: RefNoteParts): RefFieldContext | null;
1594
-
1595
- /**
1596
- * Why textbox story layout stopped short.
1597
- *
1598
- * Every one is a BOUND rather than a bug: nesting depth, fragment counts and the extent all
1599
- * come from a file. Falling back with a reason keeps the drawing rendered (clipped) instead
1600
- * of failing the layout pass.
1601
- */
1602
- type TextboxStoryFallbackReason = 'textbox-nesting-limit' | 'textbox-fragment-limit'
1603
- /** Flowed content is taller than the extent; trailing fragments were dropped. */
1604
- | 'textbox-height-clip';
1605
- /**
1606
- * One text box's story laid out inside its extent, in content-box-relative coordinates.
1607
- *
1608
- * Fragments origin at the content box's top-left; paint places the content box at
1609
- * `drawing origin + contentOffset` and clips to the extent.
1610
- */
1611
- interface TextboxStoryLayout {
1612
- /** Content-box-relative fragments (origin at the content box's top-left). */
1613
- readonly fragments: readonly BlockFragmentRecord[];
1614
- /** Height the blocks flow to (points), before vertical anchoring. */
1615
- readonly flowHeight: number;
1616
- /** Offset of the content box inside the drawing extent: insets plus vertical anchoring. */
1617
- readonly contentOffset: Readonly<{
1618
- x: number;
1619
- y: number;
1620
- }>;
1621
- /** Content box width (extent minus horizontal insets). */
1622
- readonly contentWidth: number;
1623
- /** Content box height (extent minus vertical insets). */
1624
- readonly contentHeight: number;
1625
- /** Solid fill of the hosting shape, painted behind the story; null for no fill. */
1626
- readonly fillHex: string | null;
1627
- /** Solid outline of the hosting shape; null for no outline. */
1628
- readonly strokeHex: string | null;
1629
- /** Outline width in points; 0 when absent. */
1630
- readonly strokeWidthPt: number;
1631
- /** True when layout hit a named bound and returned a truncated / empty story. */
1632
- readonly fallbackReason?: TextboxStoryFallbackReason;
1633
- /**
1634
- * Resource identities of the drawings the clip DROPPED, in flow order.
1635
- *
1636
- * The furniture invalidation token walks laid-out records, and a dropped fragment leaves
1637
- * none — while a `withPageContext` projection of the same story can wrap differently
1638
- * (PAGE digits) and keep the drawing. Without this a picture clipped out of the baseline
1639
- * but painted by a projection never reaches the session context, so its decode settling
1640
- * moves nothing and the unchanged-pass early exit reuses the placeholder pages forever
1641
- * (#467). Absent when the clip dropped no drawings, so the common story keys byte-for-byte
1642
- * as before.
1643
- */
1644
- readonly clippedResourceToken?: string;
1645
- }
1646
-
1647
- interface InlineDrawingRecord {
1648
- readonly kind: 'inlineDrawing';
1649
- readonly drawingNodeId: string;
1650
- readonly paragraphId: string;
1651
- readonly ownerPartName: string;
1652
- readonly start: number;
1653
- /** Left edge of the extent box (slot + distL). */
1654
- readonly x: number;
1655
- readonly y: number;
1656
- readonly width: number;
1657
- readonly height: number;
1658
- readonly distL: number;
1659
- readonly distR: number;
1660
- readonly distT: number;
1661
- readonly distB: number;
1662
- /** Caret/hit advance start (slot left, before distL). */
1663
- readonly advanceStart: number;
1664
- /** Caret/hit advance end (slot + totalWidth). */
1665
- readonly advanceEnd: number;
1666
- readonly baselineOffset: number;
1667
- readonly paintBounds: LayoutBox;
1668
- readonly hitBounds: LayoutBox;
1669
- readonly geometry: DrawingGeometry;
1670
- readonly resource: ImageResourceState;
1671
- readonly accessibility: DrawingAccessibility;
1672
- /** Sanitized external hyperlink projection; inert until an explicit gesture activates it. */
1673
- readonly hyperlinkHref: string | null;
1674
- readonly effects: Readonly<{
1675
- readonly grayscale: boolean;
1676
- readonly brightness: number;
1677
- readonly contrast: number;
1678
- }>;
1679
- readonly crop: SourceCrop;
1680
- readonly transform: DrawingTransform;
1681
- /** Fixed non-picture graphic kind for refusal labels (`chart`, `group`, …); null for pictures. */
1682
- readonly placeholderGraphicKind: string | null;
1683
- /** Typed solid-geometry payload for a renderable `wps:wsp` shape; null otherwise. */
1684
- readonly vectorShape: VectorShapeProjection | null;
1685
- /**
1686
- * The revision wrappers enclosing the owning run, outermost first — the same stack spans
1687
- * carry, so paint and review chrome give a tracked picture the same cues as tracked text.
1688
- * Absent when the drawing is untracked.
1689
- */
1690
- readonly revisions?: readonly RevisionAttribution[];
1691
- }
1692
- interface InlineDrawingLayoutContext {
1693
- readonly ownerPartName: string;
1694
- /** Precomputed run-level atom id (`w:drawing` or MC wrapper) → projection. */
1695
- readonly projectionForAtom?: (atomNodeId: string) => DrawingProjection | null;
1696
- readonly project: (drawing: OoxmlDrawingNode) => DrawingProjection | null;
1697
- readonly resourceOf: (projection: DrawingProjection) => ImageResourceState;
1698
- }
1699
- /** Named fallback when a positioning frame cannot be resolved (OpenSpec 4.6). */
1700
- type AnchoredDrawingLayoutFallback = 'unresolvable-frame' | 'page-defer-exhausted';
1701
- interface AnchoredDrawingRecord extends Omit<InlineDrawingRecord, 'kind' | 'baselineOffset' | 'advanceStart' | 'advanceEnd' | 'distL' | 'distR' | 'distT' | 'distB'> {
1702
- readonly kind: 'anchoredDrawing';
1703
- readonly anchorParagraphId: string;
1704
- readonly horizontalFrame: DrawingHorizontalReferenceFrame;
1705
- readonly verticalFrame: DrawingVerticalReferenceFrame;
1706
- readonly horizontalFrameOrigin: number;
1707
- readonly verticalFrameOrigin: number;
1708
- readonly behindDocument: boolean;
1709
- readonly allowOverlap: boolean;
1710
- readonly layoutInCell: boolean;
1711
- readonly relativeHeight: number;
1712
- readonly wrap: Exclude<ImageWrapTarget, 'inline'>;
1713
- readonly layoutFallback?: AnchoredDrawingLayoutFallback;
1714
- /** Canonical document traversal index within the owner story part. */
1715
- readonly sourceOrder?: number;
1716
- /**
1717
- * Laid-out textbox story for a `wps:txbx` drawing; paint renders it clipped inside the
1718
- * extent instead of a placeholder. Absent when the drawing carries no story or the host
1719
- * did not thread story layout (the record then degrades to the placeholder path).
1720
- */
1721
- readonly textboxStory?: TextboxStoryLayout;
1722
- }
1723
-
1724
- /**
1725
- * One parsed `HYPERLINK` instruction: the raw, unsanitized pieces.
1726
- *
1727
- * `target` and `anchor` are verbatim from the instruction — the projector at the trust
1728
- * boundary decides what, if anything, they become in a DOM sink.
1729
- */
1730
- interface HyperlinkFieldSpec {
1731
- /** The target as authored (first quoted or bare non-switch token), or null. */
1732
- readonly target: string | null;
1733
- /** `\l` — a bookmark name in this document, or null. */
1734
- readonly anchor: string | null;
1735
- /** `\o` — the hover tooltip, or null. */
1736
- readonly tooltip: string | null;
1737
- }
1738
-
1739
- /**
1740
- * What a piece says about the FIELD result it came from, for Word's shading.
1741
- *
1742
- * Carried from layout rather than decided at paint time because only the walk knows an atom was
1743
- * a field at all — by paint the result is just text. Whether the shading is actually drawn is a
1744
- * view decision made downstream, so this states the fact and nothing about the appearance.
1745
- */
1746
- interface FieldAtomMarker {
1747
- /**
1748
- * A legacy form field: `w:fldChar/w:ffData` (FORMTEXT, FORMCHECKBOX, FORMDROPDOWN).
1749
- *
1750
- * Word shades these on a different rule from ordinary fields — always, unless the document
1751
- * turns it off — because they mark the blanks somebody is meant to fill in.
1752
- */
1753
- readonly formField: boolean;
1754
- /**
1755
- * A BODY PAGE / NUMPAGES / SECTIONPAGES atom whose value depends on pagination.
1756
- *
1757
- * The paragraph walk cannot know which page the field lands on — layout runs before the page
1758
- * count — so it paints a placeholder and records the field's kind here. Document finalize
1759
- * (`substituteBodyPageFields`) reads this marker and substitutes the real value per page.
1760
- * Absent in headers/footers, which evaluate live through their own per-page projector.
1761
- */
1762
- readonly pageField?: {
1763
- readonly kind: AllowlistedPageField;
1764
- /** The field's `\#` numeric picture, applied when finalize substitutes the value. */
1765
- readonly picture?: string;
1766
- };
1767
- /**
1768
- * A BODY `PAGEREF` atom whose value is the page number its bookmark target lands on.
1769
- *
1770
- * Deferred exactly like {@link pageField}: the paragraph walk paints the field's cached
1771
- * result (or the placeholder digit when the file cached none) and records the resolved
1772
- * target here; document finalize substitutes the number of the page hosting the target's
1773
- * first fragment, gated per field on the calibration verdict.
1774
- */
1775
- readonly pageRef?: PageRefFieldProjection;
1776
- }
1777
- /** A half-open model-offset range, in the paragraph's own UTF-16 offset space. */
1778
- interface ModelRange {
1779
- readonly start: number;
1780
- readonly end: number;
1781
- }
1782
- /**
1783
- * How layout turns a typed `w:hyperlink` node into the sanitized record spans carry.
1784
- *
1785
- * Injected rather than computed here because resolving `r:id` needs the PACKAGE's
1786
- * relationships and this module only ever sees one part's tree. `null` means the caller
1787
- * declined to project — the runs still measure and paint, they simply carry no link, which is
1788
- * the right degradation: text is never lost for want of a target.
1789
- */
1790
- type HyperlinkProjector = (link: OoxmlNode) => SpanLinkRecord | null;
1791
- /**
1792
- * How layout turns a parsed HYPERLINK field instruction into the sanitized record spans carry.
1793
- *
1794
- * Injected for the same reason as {@link HyperlinkProjector}: the spec's raw target must cross
1795
- * the surface's ONE href trust boundary, and layout owns no sanitization policy. `null` means
1796
- * no link — the cached result still paints as plain text, which is the right degradation.
1797
- */
1798
- type FieldLinkProjector = (spec: HyperlinkFieldSpec) => SpanLinkRecord | null;
1799
-
1800
- /** Test-only mutable counters; omitted by production call sites. Not package-public. */
1801
- interface TableBorderGridResolveWork {
1802
- /** Sparse intervals emitted (not dense column slots). */
1803
- ownershipSlotsWritten: number;
1804
- columnLookups: number;
1805
- }
1806
- /** Shared remaining interval budget for nested table finalization in one layout. */
1807
- interface TableBorderOwnershipBudget {
1808
- intervalsRemaining: number;
1809
- }
1810
-
1811
- /** Which physical edge of a box a border sits on. */
1812
- type TableBorderSideName = 'top' | 'right' | 'bottom' | 'left';
1813
- /**
1814
- * One border edge in one of three states.
1815
- *
1816
- * `omitted` and `none` are NOT the same: an omitted edge inherits from the table style or the
1817
- * neighbouring cell, while an explicit `none` wins the conflict and draws nothing. Collapsing
1818
- * them would let a style's border reappear where the author removed it.
1819
- */
1820
- type TableBorderSide = {
1821
- readonly state: 'omitted';
1822
- } | {
1823
- readonly state: 'none';
1824
- } | {
1825
- readonly state: 'edge';
1826
- readonly style: TableBorderStyle;
1827
- /** Validated RRGGBB, or null for auto/missing (paint defaults to black). */
1828
- readonly color: string | null;
1829
- /** Thickness in points (`w:sz` is eighths of a point). */
1830
- readonly widthPt: number;
1831
- };
1832
- /** A table's six authored border edges: four outer, plus the two interior intervals. */
1833
- interface TableBorderBox {
1834
- readonly top: TableBorderSide;
1835
- readonly left: TableBorderSide;
1836
- readonly bottom: TableBorderSide;
1837
- readonly right: TableBorderSide;
1838
- readonly insideH: TableBorderSide;
1839
- readonly insideV: TableBorderSide;
1840
- }
1841
- /** The four resolved edges of one cell, after conflict resolution against its neighbours. */
1842
- interface CellBorderBox {
1843
- readonly top: TableBorderSide;
1844
- readonly left: TableBorderSide;
1845
- readonly bottom: TableBorderSide;
1846
- readonly right: TableBorderSide;
1847
- }
1848
- /** Final edge winner; absent means the side/interval is not drawn. */
1849
- interface ResolvedTableBorderEdge {
1850
- readonly style: TableBorderStyle;
1851
- readonly color: string | null;
1852
- readonly widthPt: number;
1853
- }
1854
- /**
1855
- * Conflict winner over one grid interval of a cell side.
1856
- *
1857
- * `gridStart`/`gridEnd` are absolute grid column indices for horizontal sides and absolute
1858
- * row indices for vertical sides (half-open). `startPt`/`endPt` are cell-local along-axis
1859
- * positions in layout points.
1860
- */
1861
- interface ResolvedTableBorderEdgeSegment {
1862
- readonly side: TableBorderSideName;
1863
- readonly gridStart: number;
1864
- readonly gridEnd: number;
1865
- readonly startPt: number;
1866
- readonly endPt: number;
1867
- readonly edge: ResolvedTableBorderEdge;
1868
- }
1869
- /**
1870
- * One published stroke rectangle in cell-local layout points.
1871
- *
1872
- * Paint multiplies x/y/width/height by scale and draws — no metrics, gaps, or corner math.
1873
- */
1874
- interface TableBorderStrokeRecord {
1875
- readonly side: TableBorderSideName;
1876
- readonly role: 'outer' | 'inner' | 'middle' | 'edge';
1877
- readonly color: string | null;
1878
- /** CSS keyword for this stroke (compound strokes are always solid). */
1879
- readonly cssStyle: 'solid' | 'dashed' | 'dotted';
1880
- readonly x: number;
1881
- readonly y: number;
1882
- readonly width: number;
1883
- readonly height: number;
1884
- }
1885
- /**
1886
- * Layout-owned cell borders after conflict resolution and compound expansion.
1887
- *
1888
- * Convenience `top`/`left`/`bottom`/`right` are set when that side has a single uniform
1889
- * full-span winner (existing consumers / CSS simple edges). Multi-interval winners live
1890
- * only on `edgeSegments`. Compound geometry is always on `strokes`.
1891
- */
1892
- interface ResolvedCellBorders {
1893
- readonly top?: ResolvedTableBorderEdge;
1894
- readonly left?: ResolvedTableBorderEdge;
1895
- readonly bottom?: ResolvedTableBorderEdge;
1896
- readonly right?: ResolvedTableBorderEdge;
1897
- readonly edgeSegments?: readonly ResolvedTableBorderEdgeSegment[];
1898
- readonly strokes?: readonly TableBorderStrokeRecord[];
1899
- }
1900
- /** Soft cap on published stroke rectangles per cell (security / pathological spans). */
1901
- declare const MAX_TABLE_BORDER_STROKES = 256;
1902
- /** Read one OOXML border child into the three-state model. */
1903
- declare function readBorderSide(node: OoxmlElement | undefined): TableBorderSide;
1904
- /**
1905
- * Read a table's `w:tblBorders`, dropping or clamping hostile values.
1906
- *
1907
- * Widths and colours come from a file: an out-of-range `w:sz` becomes a layout dimension, so it is
1908
- * bounded here rather than downstream.
1909
- */
1910
- declare function readTableBorders(tblPr: OoxmlElement | undefined): TableBorderBox;
1911
- /** Read one cell's `w:tcBorders`, under the same bounds {@link readTableBorders} applies. */
1912
- declare function readCellBorders(tcPr: OoxmlElement | undefined): CellBorderBox;
1913
- /**
1914
- * Conflict weight: the authored width in eighths of a point, and nothing else.
1915
- *
1916
- * Word-matching, not conformance — §17.4.39 (`w:tblBorders`) and §17.4.66 (`w:tcBorders`)
1917
- * describe the elements and specify no conflict algorithm. Word picks the heavier RULE, so
1918
- * a 6pt dashed rule beats a hairline single. Folding the style rank into the weight (an
1919
- * `sz × border-number` product) made a 0.5pt double outrank a 1pt single and made every
1920
- * dashed or dotted rule weigh 1 regardless of `w:sz`, which erased its width entirely.
1921
- */
1922
- declare function borderWeight(side: TableBorderSide): number;
1923
- /**
1924
- * Pick the winner between two candidates on a shared grid line (zero cell spacing).
1925
- *
1926
- * `none` loses to any edge; two `none`/omitted yield omitted (no paint). Width decides;
1927
- * equal widths rank by style, then prefer the darker color, then `preferFirst`
1928
- * (reading-order / first candidate).
1929
- */
1930
- declare function resolveBorderConflict(first: TableBorderSide, second: TableBorderSide, preferFirst?: boolean): TableBorderSide;
1931
- interface BorderGridCell {
1932
- readonly gridColumn: number;
1933
- readonly gridSpan: number;
1934
- readonly vMergeContinue: boolean;
1935
- readonly borders: CellBorderBox;
1936
- /** Set on restart cells that visually span into later rows. */
1937
- readonly mergeRowSpan?: number;
1938
- }
1939
- /**
1940
- * The absolute grid a table's borders are drawn on: column widths, and per-row tops and heights.
1941
- *
1942
- * Shared by every edge so adjacent cells resolve to the SAME line, rather than each computing its
1943
- * own and leaving a hairline gap between them.
1944
- */
1945
- interface BorderGridGeometry {
1946
- /** Absolute column widths for the whole table (points). */
1947
- readonly columnWidthsPt: readonly number[];
1948
- /**
1949
- * Per laid-out row: absolute top and height in the same coordinate space as cell boxes
1950
- * (only relative differences matter for vertical edge segmentation).
1951
- */
1952
- readonly rowBands: readonly {
1953
- readonly y: number;
1954
- readonly height: number;
1955
- }[];
1956
- /** Per laid-out cell: width/height after vMerge expansion (cell-local stroke space). */
1957
- readonly cellBoxes: readonly (readonly {
1958
- readonly width: number;
1959
- readonly height: number;
1960
- }[])[];
1961
- }
1962
- /**
1963
- * Resolve borders for every cell in a laid-out table fragment.
1964
- *
1965
- * Shared vertical edges: conflict(left.right, right.left) → assigned to the left cell,
1966
- * segmented per row when a vMerge restart faces differing neighbors.
1967
- * Shared horizontal edges: conflict(above.bottom, below.top) → assigned to the above cell,
1968
- * segmented per grid column when a gridSpan cell faces differing below neighbors; vMerge
1969
- * interior seams are suppressed per interval.
1970
- *
1971
- * When `geometry` is provided, compound edges expand into explicit stroke records with
1972
- * corner-adjusted endpoints in cell-local points.
1973
- */
1974
- declare function resolveTableCellBorderGrid(rows: readonly (readonly BorderGridCell[])[], table: TableBorderBox, columnCount: number, geometry?: BorderGridGeometry, work?: TableBorderGridResolveWork, ownershipBudget?: TableBorderOwnershipBudget): ResolvedCellBorders[][];
1975
- /** Width contribution of a resolved edge for content inset / row sizing. */
1976
- declare function borderExtentPt(edge: ResolvedTableBorderEdge | TableBorderSide | undefined): number;
1977
-
1978
- interface EquationGeometryBase {
1979
- readonly box: LayoutBox;
1980
- /** Baseline measured from this node's top edge. */
1981
- readonly baseline: number;
1982
- }
1983
- interface EquationTextGeometry extends EquationGeometryBase {
1984
- readonly kind: 'text' | 'fallback';
1985
- readonly text: string;
1986
- readonly fontSizePt: number;
1987
- }
1988
- interface EquationRowGeometry extends EquationGeometryBase {
1989
- readonly kind: 'row';
1990
- readonly items: readonly EquationGeometry[];
1991
- }
1992
- interface EquationFractionGeometry extends EquationGeometryBase {
1993
- readonly kind: 'fraction';
1994
- readonly numerator: EquationGeometry;
1995
- readonly denominator: EquationGeometry;
1996
- readonly bar: LayoutBox;
1997
- }
1998
- interface EquationRadicalGeometry extends EquationGeometryBase {
1999
- readonly kind: 'radical';
2000
- readonly sign: EquationTextGeometry;
2001
- readonly radicand: EquationGeometry;
2002
- readonly degree?: EquationGeometry;
2003
- readonly bar: LayoutBox;
2004
- }
2005
- interface EquationScriptGeometry extends EquationGeometryBase {
2006
- readonly kind: 'script';
2007
- readonly base: EquationGeometry;
2008
- readonly subscript?: EquationGeometry;
2009
- readonly superscript?: EquationGeometry;
2010
- }
2011
- interface EquationNaryGeometry extends EquationGeometryBase {
2012
- readonly kind: 'nary';
2013
- readonly operator: EquationTextGeometry;
2014
- readonly body: EquationGeometry;
2015
- readonly lowerLimit?: EquationGeometry;
2016
- readonly upperLimit?: EquationGeometry;
2017
- }
2018
- type EquationGeometry = EquationTextGeometry | EquationRowGeometry | EquationFractionGeometry | EquationRadicalGeometry | EquationScriptGeometry | EquationNaryGeometry;
2019
- /** Equation metadata carried by one atomic semantic span. */
2020
- interface EquationSpanRecord {
2021
- readonly sourceNodeId: string;
2022
- readonly geometry: EquationGeometry;
2023
- readonly fallbackText: string;
2024
- readonly truncated: boolean;
2025
- }
2026
- /** Compose one bounded OMML projection into paint-ready point geometry. */
2027
- declare function layoutEquation(projection: OmmlEquationProjection, measurer: TextMeasurer, style: ResolvedRunStyle): EquationSpanRecord;
2028
-
2029
- /** A half-open UTF-16 range inside one paragraph, addressed by its canonical node id. */
2030
- interface SourceRange {
2031
- readonly paragraphId: string;
2032
- readonly start: number;
2033
- readonly end: number;
2034
- }
2035
- /**
2036
- * A rectangle in layout POINTS.
2037
- *
2038
- * Points everywhere in this layer — twips convert at property-read boundaries and CSS pixels at
2039
- * paint. A box carrying either of those would eventually be added to one carrying the other.
2040
- */
2041
- interface LayoutBox {
2042
- readonly x: number;
2043
- readonly y: number;
2044
- readonly width: number;
2045
- readonly height: number;
2046
- }
2047
- /**
2048
- * A bottom paragraph border as layout published it.
2049
- *
2050
- * `box` is the rule's geometry in the same coordinate space as the fragment (page-content
2051
- * relative). Paint positions from this box and MUST NOT remeasure the border.
2052
- */
2053
- interface ParagraphBottomBorderRecord {
2054
- readonly edge: ParagraphBorderEdge;
2055
- readonly box: LayoutBox;
2056
- }
2057
- /**
2058
- * One `w:pBdr` rule as layout published it.
2059
- *
2060
- * `box` is the STROKE rectangle in the same coordinate space as the fragment, so paint sets a
2061
- * position and a colour and nothing else. It matters that paint cannot derive these itself:
2062
- * Word draws the side rules OUTSIDE the text column, so `box.x` on a `left`/`bar` stroke is
2063
- * left of the fragment box and a painter reasoning from the fragment alone would put it
2064
- * inside the text.
2065
- */
2066
- interface ParagraphBorderStrokeRecord {
2067
- readonly side: ParagraphBorderSide;
2068
- readonly edge: ParagraphBorderEdge;
2069
- readonly box: LayoutBox;
2070
- }
2071
- /**
2072
- * The hyperlink a span sits inside, as layout resolved it.
2073
- *
2074
- * Already SANITIZED: `href` is the runtime projection produced once at the trust boundary,
2075
- * and `null` means the link is inert — a refused scheme, or a relationship the package does
2076
- * not declare. Paint, hit-testing and the popover consume this and never the authored target,
2077
- * so there is exactly one place a file-derived URL becomes something a browser can follow.
2078
- *
2079
- * `id` is the `w:hyperlink` node's canonical id, which is what makes the spans of one link
2080
- * recognisable as one link across the several lines it wraps onto — and what an unlink or a
2081
- * retarget addresses.
2082
- */
2083
- interface SpanLinkRecord {
2084
- readonly id: string;
2085
- readonly kind: 'external' | 'internal' | 'unresolved';
2086
- /** Sanitized runtime projection: an absolute URL, `#anchor`, or null when inert. */
2087
- readonly href: string | null;
2088
- /** Bookmark name for an internal link, so navigation need not re-parse the fragment. */
2089
- readonly anchor?: string;
2090
- /** `w:tooltip` — paint puts it on the anchor's `title`. */
2091
- readonly tooltip?: string;
2092
- }
2093
- /** A run of text on one line sharing identical resolved formatting. */
2094
- interface StyleSpanRecord {
2095
- readonly range: SourceRange;
2096
- readonly text: string;
2097
- /** The run's authored properties, retained as evidence. */
2098
- readonly props: readonly OoxmlProperty[];
2099
- /**
2100
- * The same properties RESOLVED — one unit system, defaults applied.
2101
- *
2102
- * Carried on the span so the measurer, the span and the painter all read one resolution
2103
- * rather than each deriving its own. Two derivations that disagree by a fraction of a
2104
- * point put the caret where no glyph is.
2105
- */
2106
- readonly style: ResolvedRunStyle;
2107
- /**
2108
- * The `w:rFonts` slot this span's text resolves its face through; absent means the base
2109
- * (ascii/hAnsi) slots. {@link style} stays the run's full resolution — with both
2110
- * `fontFamily` and `fontFamilyEastAsia` — so formatting readback and the format painter
2111
- * see the run as authored; measurement, paint and hit-testing resolve the effective face
2112
- * with `styleForFontSlot(span.style, span.fontSlot)`.
2113
- */
2114
- readonly fontSlot?: FontSlot;
2115
- readonly box: LayoutBox;
2116
- /**
2117
- * Cumulative advances from {@link box}.x to each UTF-16 caret boundary in {@link text}.
2118
- *
2119
- * Length is `text.length + 1` (both endpoints). When PRESENT, these are the interaction
2120
- * authority: hit-testing and the caret read them instead of re-measuring. Layout does not
2121
- * publish them eagerly — measuring every character of every laid span dominated page
2122
- * repagination — so interactions measure prefixes on demand through the same measurer the
2123
- * span was laid with (memoized per span; see `prefixWidth` in semantic-hit-test.ts), and
2124
- * only interpolate across {@link box}.width when no measurer is supplied.
2125
- */
2126
- readonly caretEdges?: readonly number[];
2127
- /**
2128
- * `w:tab/@w:leader` of the stop a `\t` span advanced to (ECMA-376 §17.3.1.38).
2129
- *
2130
- * Only ever set on a tab span, and only for a non-`none` leader. Paint repeats the glyph
2131
- * across the advance THIS box already reserved — the leader adds no width of its own, so
2132
- * it can never move the text that follows it.
2133
- */
2134
- readonly tabLeader?: TabLeader;
2135
- /**
2136
- * Advance of ONE leader glyph in this run's face, in points, measured by layout.
2137
- *
2138
- * Paint cannot ask a font how wide a character is, so without this it had to guess and
2139
- * deliberately overfill, leaving the dots at whatever spacing an over-long string
2140
- * happened to produce. Measured, the leader is spaced exactly as the same character
2141
- * typed there would be — which is what Word draws.
2142
- */
2143
- readonly tabLeaderAdvancePt?: number;
2144
- /**
2145
- * The hyperlink this span belongs to, or absent for ordinary text.
2146
- *
2147
- * Carried on the SPAN rather than looked up at paint time because a link that wraps
2148
- * produces one set of spans per line, and paint has no paragraph to walk — only the line
2149
- * it was handed.
2150
- */
2151
- readonly link?: SpanLinkRecord;
2152
- /**
2153
- * Horizontal jump, in points, that a floating object's wrap zone forced before this span.
2154
- *
2155
- * A line that resumes on the far side of a float is still ONE line: its spans keep running
2156
- * model offsets and share a selection band. Only layout knows the jump is an obstacle
2157
- * rather than justification slack, so it publishes it here. Paint reserves it with an inert
2158
- * advance and excludes it from the word spacing it reconstructs — inferring the difference
2159
- * from the gap alone spread the float's whole width across every space on the line.
2160
- */
2161
- readonly wrapAdvanceBefore?: number;
2162
- /**
2163
- * The revision wrappers this text sits inside, outermost first, absent when untracked.
2164
- *
2165
- * Carried on the span for the same reason `style` is: paint and the review surface must read
2166
- * ONE attribution. A sidebar that re-derived which revision covers a span by walking the tree
2167
- * could disagree with what was painted, and the card would point at the wrong text.
2168
- */
2169
- readonly revisions?: readonly RevisionAttribution[];
2170
- /**
2171
- * Present when this span is a field's displayed RESULT, for the shading Word draws under one.
2172
- *
2173
- * `projected` cannot answer this: it is also set for note marks and inline drawings, so it
2174
- * says "layout owns these glyphs" rather than "this is a field". Word shades legacy form
2175
- * fields on a different rule from ordinary ones, which is why the marker distinguishes them.
2176
- *
2177
- * States the FACT, never the appearance — whether shading is drawn is a view decision, made
2178
- * downstream from a host option and the document's own `w:doNotShadeFormData`. Deciding it
2179
- * here would put the caret into layout's cache key and repaginate on every arrow press.
2180
- */
2181
- readonly fieldAtom?: FieldAtomMarker;
2182
- /**
2183
- * Live PAGE/NUMPAGES/SECTIONPAGES projection (layout-time evaluated text).
2184
- *
2185
- * Computed substitutions are not model-editable: paint treats these as atomic furniture
2186
- * and selection mapping refuses them the way it refuses markers.
2187
- */
2188
- readonly projected?: boolean;
2189
- /** Paint-ready geometry for one atomic Office Math equation. */
2190
- readonly equation?: EquationSpanRecord;
2191
- /**
2192
- * Footnote/endnote navigation metadata for projected note atoms.
2193
- *
2194
- * Paint tags body citations (`to-note`) and note-body marks (`to-body`) so React chrome
2195
- * can jump without owning layout logic.
2196
- */
2197
- readonly noteNav?: {
2198
- readonly scopeId: string;
2199
- readonly direction: 'to-note' | 'to-body';
2200
- };
2201
- }
2202
- /**
2203
- * One laid-out line: its geometry, its baseline, and the styled spans it renders.
2204
- *
2205
- * The unit hit-testing and caret placement resolve against. Geometry comes from HERE, never from
2206
- * the DOM, which is what lets an empty paragraph still get a caret.
2207
- */
2208
- interface LineRecord {
2209
- readonly id: string;
2210
- readonly range: SourceRange;
2211
- readonly spans: readonly StyleSpanRecord[];
2212
- readonly box: LayoutBox;
2213
- /**
2214
- * Where the line's content actually starts, after alignment and the first-line indent.
2215
- *
2216
- * {@link box} is the content BAND the line was broken against — its `x` is the indented
2217
- * column edge and its `width` the available measure, neither of which moves with
2218
- * `w:jc`. Alignment is otherwise expressed only as x offsets on the span boxes, so a line
2219
- * with no spans (an empty paragraph) had no aligned origin at all: paint, hit testing and
2220
- * the caret each fell back to `box.x` and drew a centred empty paragraph's caret hard
2221
- * against the left margin, where it stayed until the first character was typed.
2222
- *
2223
- * Equal to the first span's x whenever there is one, so it is the single origin every
2224
- * consumer can read without a spans-or-box fallback of its own.
2225
- */
2226
- readonly contentX: number;
2227
- /** Distance from the line box top to the text baseline. */
2228
- readonly baseline: number;
2229
- /**
2230
- * Space ABOVE the glyph band inside {@link box} (exact centering, not auto/atLeast).
2231
- *
2232
- * `auto` / `atLeast` extras grow the box BELOW the glyphs — paint puts that depth in
2233
- * padding-bottom. {@link baseline} is measured from the line top and already includes
2234
- * this above-band when present.
2235
- *
2236
- * Published by layout rather than recovered by paint: three paint sites each deriving it
2237
- * separately is how they came to disagree about where a line's text sits.
2238
- */
2239
- readonly leading: number;
2240
- /**
2241
- * Auto/atLeast line-spacing depth BELOW the glyph band, inside {@link box}.
2242
- *
2243
- * The complement of {@link leading}: exact spacing centres the glyphs and moves the
2244
- * baseline down, while auto/atLeast leave the band at the top and grow the box beneath it.
2245
- * So the band a consumer needs is `box.height - trailingSpacing - leading`, and
2246
- * subtracting `leading` alone is right only under the exact rule.
2247
- *
2248
- * A line WITH spans carries its band in the span heights too. An empty paragraph carries
2249
- * nothing, which is why the caret, paint's `padding-bottom` and the content-control
2250
- * boundary all need this published rather than recovered from the box.
2251
- *
2252
- * Zero under the exact rule and on lines holding drawings, where the box is authored.
2253
- * Absent on lines published before this was measured; treat as zero.
2254
- */
2255
- readonly trailingSpacing?: number;
2256
- /**
2257
- * Model ranges on this line covering DELETED content, absent when there is none.
2258
- *
2259
- * The caret steps over these rather than entering them: text typed inside a deletion exists
2260
- * in neither the original nor the proposal, and there is no valid tree for the result.
2261
- *
2262
- * Recorded even in display modes that lay the deletion out invisibly, because the offsets
2263
- * exist in the model in every mode and an offset-by-offset walk would otherwise stop at
2264
- * positions with no glyph.
2265
- */
2266
- readonly deletedRanges?: readonly ModelRange[];
2267
- /**
2268
- * Inline drawings on this line, absent when there are none.
2269
- *
2270
- * Each occupies one UTF-16 model unit at {@link InlineDrawingRecord.start}. Hidden drawings
2271
- * are omitted — they remain in the tree and projection but publish no geometry.
2272
- */
2273
- readonly drawings?: readonly InlineDrawingRecord[];
2274
- /**
2275
- * Revision attributions of tracked ANCHORED drawings whose anchor sits on this line,
2276
- * absent when there are none.
2277
- *
2278
- * An anchored drawing paints from the page layer and leaves no span or line drawing at its
2279
- * anchor offset, so this is the only carrier the margin change bar can read for it. Only
2280
- * attributions the display mode lays out are recorded, matching the published records.
2281
- */
2282
- readonly anchorRevisions?: readonly RevisionAttribution[];
2283
- }
2284
- /**
2285
- * A paragraph's resolved indent in points, in the vocabulary `w:ind` uses.
2286
- *
2287
- * `left`/`right` are signed; `hanging` is not (`ST_TwipsMeasure`). `firstLine` is signed
2288
- * even though the schema declares it unsigned, because Word's model keeps one signed
2289
- * first-line indent and this engine follows it.
2290
- */
2291
- interface ParagraphIndent {
2292
- readonly left: number;
2293
- readonly right: number;
2294
- readonly firstLine: number;
2295
- readonly hanging: number;
2296
- }
2297
- /**
2298
- * The part of one paragraph that sits on one page.
2299
- *
2300
- * A paragraph that crosses a page boundary produces several fragments that all name the SAME
2301
- * `paragraphId`, which is what lets selection and hit-testing treat it as one paragraph while
2302
- * pagination treats it as two boxes.
2303
- */
2304
- interface ParagraphFragmentRecord {
2305
- readonly kind: 'paragraph';
2306
- readonly id: string;
2307
- readonly paragraphId: string;
2308
- /** 0 for the first fragment of the paragraph, 1 for its continuation, and so on. */
2309
- readonly fragmentIndex: number;
2310
- readonly range: SourceRange;
2311
- readonly props: readonly OoxmlProperty[];
2312
- /**
2313
- * Before/after spacing applied to THIS fragment, in points.
2314
- *
2315
- * Continuations carry `before: 0`; only the final fragment carries `after`. The numbers
2316
- * already reflect Word's adjacent-collapse against the previous paragraph's after.
2317
- */
2318
- readonly spacing: ParagraphSpacing;
2319
- /**
2320
- * The paragraph's EFFECTIVE indent in points, cascade and numbering merge included.
2321
- *
2322
- * Published because it is not recoverable from anything else here. `props` carries the
2323
- * cascaded `w:ind`, but a list paragraph's indent comes from `numbering.xml` and is merged
2324
- * in after the cascade, so a numbered item that authors no `w:ind` reads zero there while
2325
- * its text sits indented. The geometry is no better an answer: `box.x` is cell-relative
2326
- * inside a table and is displaced by float zones.
2327
- *
2328
- * `firstLine` and `hanging` are kept as OOXML spells them. A consumer wanting the one
2329
- * signed first-line offset Word models takes `hanging > 0 ? -hanging : firstLine` —
2330
- * hanging WINS, it is not summed (ECMA-376 §17.3.1.12).
2331
- */
2332
- readonly indent: ParagraphIndent;
2333
- /** Bottom rule on the final fragment when `w:pBdr/w:bottom` resolves; absent otherwise. */
2334
- readonly bottomBorder?: ParagraphBottomBorderRecord;
2335
- /**
2336
- * Every `w:pBdr` rule this fragment draws, in paint order.
2337
- *
2338
- * A paragraph split across pages opens and closes exactly once: the `top` stroke rides the
2339
- * first fragment, the closing stroke the last. `bottomBorder` remains the bottom rule alone
2340
- * — a `between` rule closing a grouped paragraph is not one.
2341
- */
2342
- readonly borders?: readonly ParagraphBorderStrokeRecord[];
2343
- /**
2344
- * Validated 6-hex paragraph shading fill (`w:pPr/w:shd`), absent for none/auto.
2345
- *
2346
- * Paint fills {@link shadingBox}. Measurement ignores it.
2347
- */
2348
- readonly shading?: string;
2349
- /**
2350
- * Geometry of the paragraph shading band when {@link shading} is set. Absent when there
2351
- * is no fill.
2352
- *
2353
- * TWO SHAPES, because Word fills two different things. An UNBORDERED paragraph is filled
2354
- * across its line boxes (indent-aware width), and before/after spacing stays unfilled. A
2355
- * paragraph that publishes any {@link borders} stroke is filled across the rectangle those
2356
- * strokes draw instead — `w:space` padding and rule extent included — so a callout's fill
2357
- * reaches its frame rather than leaving a pale stripe floating inside an empty box. Paint
2358
- * draws the strokes after the fill, so the frame is never covered.
2359
- */
2360
- readonly shadingBox?: LayoutBox;
2361
- /**
2362
- * The revisions on this paragraph's own MARK (`w:pPr/w:rPr/w:ins|w:del`), absent when there
2363
- * are none.
2364
- *
2365
- * Carried on the fragment rather than on a span because they decorate no characters: the
2366
- * pilcrow was inserted or deleted, which is how a split or a merge is recorded. Only the
2367
- * FINAL fragment carries them — the mark lives at the end of the paragraph, so a paragraph
2368
- * split across pages must not draw two of them.
2369
- *
2370
- * A LIST because one mark can hold two decisions at once — an insertion by one author and
2371
- * a deletion of that insertion by the next. Published only in `all-markup`: the other two
2372
- * display modes answer what the document WOULD be, and a resolved view draws no
2373
- * attribution.
2374
- *
2375
- * Those two modes are ATTRIBUTION-resolved, not STRUCTURE-resolved. Taking a deleted mark
2376
- * in `proposed` merges the paragraph into the next one, and taking an inserted mark in
2377
- * `original` un-splits it, but `revision-visibility.ts` does that only for a paragraph that
2378
- * renders no text. So a resolved view still shows the break — it just no longer draws a
2379
- * coloured pilcrow beside it. The merge is the fix for that; suppressing the glyph is not,
2380
- * and must not be read as it.
2381
- */
2382
- readonly markRevisions?: readonly RevisionAttribution[];
2383
- /**
2384
- * The one decision a single-field reader sees, absent when there are none.
2385
- *
2386
- * Derived from {@link markRevisions} at publish time, never authored beside it, so the two
2387
- * cannot disagree: a deletion when the mark carries one, because that is where the pair
2388
- * lands once every decision is taken, and it is the face paint draws for the same reason.
2389
- *
2390
- * @deprecated Shows one of the decisions a mark can carry. Read {@link markRevisions}.
2391
- */
2392
- readonly markRevision?: RevisionAttribution;
2393
- /**
2394
- * The tracked FORMAT change on this paragraph's mark (`w:pPr/w:rPr/w:rPrChange`), absent
2395
- * when there is none. Final fragment only, and `all-markup` only, like the decisions above.
2396
- *
2397
- * Published without a glyph of its own on purpose. Word draws no pilcrow for a mark whose
2398
- * only change is its own formatting — the decision belongs to the review pane, which lists
2399
- * it either way. What was missing was GEOMETRY: with nothing on the fragment, a card had
2400
- * no place on the page to point at.
2401
- */
2402
- readonly markFormatRevision?: RevisionAttribution;
2403
- /**
2404
- * List marker painted in the hanging-indent slot of the FIRST fragment only.
2405
- *
2406
- * Not part of model text: no UTF-16 range, never contributes to caret/selection offsets,
2407
- * and must not be serialised back into the paragraph.
2408
- */
2409
- readonly marker?: ListMarkerRecord;
2410
- /**
2411
- * The paragraph's resolved tab stops — cascade included — and the default interval past
2412
- * the last of them.
2413
- *
2414
- * Published because it is not recoverable from `props`: `w:tabs` carries its meaning in
2415
- * `w:tab` CHILDREN and the flat property projection has none, so a control reading the
2416
- * cascade sees the element and not a single stop. A tab-stop editor that showed only what
2417
- * the paragraph authors would hide a style's stops and then leave them in force.
2418
- */
2419
- readonly tabStops: ResolvedTabStops;
2420
- readonly lines: readonly LineRecord[];
2421
- readonly box: LayoutBox;
2422
- }
2423
- /**
2424
- * A numbering marker as layout published it.
2425
- *
2426
- * Geometry is in the same coordinate space as the fragment (page-content or cell-content
2427
- * relative). Paint positions from this box and MUST NOT remeasure the marker.
2428
- */
2429
- interface ListMarkerRecord {
2430
- readonly text: string;
2431
- readonly style: ResolvedRunStyle;
2432
- readonly box: LayoutBox;
2433
- /**
2434
- * The `w:ilvl` this marker was resolved at, 0..8.
2435
- *
2436
- * Published because the level, not the geometry, is what Increase/Decrease Indent moves
2437
- * on a list paragraph — demoting an item re-resolves its format from `numbering.xml`,
2438
- * which is why a bullet becomes a hollow circle and a `1.` becomes an `a.`.
2439
- */
2440
- readonly level: number;
2441
- /**
2442
- * The `w:numId` this marker resolved through.
2443
- *
2444
- * Published with the level because the two together are what identifies a list: whether
2445
- * a demote is even possible depends on which levels THIS definition declares, and a
2446
- * document may hold several lists whose level 0 looks identical.
2447
- */
2448
- readonly numId: string;
2449
- /** `w:numFmt` of the resolved level — `bullet` or a numbering format. */
2450
- readonly numFmt: string;
2451
- }
2452
- /**
2453
- * The part of one table that sits on one page.
2454
- *
2455
- * A table that crosses a page boundary produces one fragment per page it spans, which is
2456
- * what lets it checkpoint like a paragraph: the flow loop places whole fragments, and
2457
- * resuming after a table needs no knowledge of its interior.
2458
- */
2459
- interface TableFragmentRecord {
2460
- readonly kind: 'table';
2461
- readonly id: string;
2462
- /** Canonical node id of the `w:tbl`. */
2463
- readonly tableId: string;
2464
- /** 0 for the first page the table touches, 1 for its continuation, and so on. */
2465
- readonly fragmentIndex: number;
2466
- /** Nesting depth: 0 for body-level tables, increasing for nested tables. */
2467
- readonly nestingDepth: number;
2468
- /**
2469
- * Resolved column boundary x positions in table-local points, left edge through right edge.
2470
- * Length is column count + 1.
2471
- */
2472
- readonly columnEdges: readonly number[];
2473
- readonly rows: readonly TableRowFragmentRecord[];
2474
- readonly box: LayoutBox;
2475
- }
2476
- /**
2477
- * One table row on one page.
2478
- *
2479
- * A FRAGMENT, not the row: a row split across a page break appears once per page it touches, and
2480
- * a repeated header row appears on every page of its table.
2481
- */
2482
- interface TableRowFragmentRecord {
2483
- /** Canonical node id of the `w:tr`. */
2484
- readonly id: string;
2485
- /** Pending tracked row insertion/deletion, when authored in `w:trPr`. */
2486
- readonly revisionKind?: 'insert' | 'delete';
2487
- /**
2488
- * The `w:trPr/w:ins|w:del` attribution — the `(id, author, date)` triple the review model
2489
- * addresses this decision by, so painted rows can carry it the way revision spans do.
2490
- */
2491
- readonly revisionId?: string;
2492
- readonly revisionAuthor?: string;
2493
- readonly revisionDate?: string;
2494
- /** Authored row ordinal within the table; repeats share the original row's index. */
2495
- readonly rowIndex: number;
2496
- /**
2497
- * True for a `w:tblHeader` row RE-EMITTED at the top of a continuation page. Painted,
2498
- * but excluded from interaction walks so each caret stop exists exactly once.
2499
- */
2500
- readonly isHeaderRepeat: boolean;
2501
- /**
2502
- * True when this record continues a row that already emitted content on a prior page
2503
- * (cell content fragmented at a paragraph/line boundary). Same `id` as the lead fragment.
2504
- */
2505
- readonly isContinuation?: boolean;
2506
- readonly cells: readonly TableCellFragmentRecord[];
2507
- readonly box: LayoutBox;
2508
- }
2509
- /**
2510
- * One table cell on one page, already resolved against the grid.
2511
- *
2512
- * `gridSpan` is clamped at read time, because it comes from a file and an unclamped span is a
2513
- * loop bound an attacker controls. A vertical-merge continuation paints its box but holds no
2514
- * blocks — its content belongs to the cell that started the merge.
2515
- */
2516
- interface TableCellFragmentRecord {
2517
- /** Canonical node id of the `w:tc`. */
2518
- readonly id: string;
2519
- /** First grid column this cell occupies. */
2520
- readonly gridColumn: number;
2521
- /** Canonical `w:gridCol` node id for this cell's start column, when authored. */
2522
- readonly gridColumnId?: string;
2523
- /** Grid columns spanned, already clamped at read time. */
2524
- readonly gridSpan: number;
2525
- /** A vertical-merge continuation paints its box but holds no blocks. */
2526
- readonly vMergeContinue: boolean;
2527
- /**
2528
- * When true, paint skips borders/fill/content for this cell (vMerge continue). Grid
2529
- * bookkeeping and the box remain so selection/geometry walks stay consistent.
2530
- */
2531
- readonly paintInert?: boolean;
2532
- /** Number of rows this restart cell visually spans (1 when not a vertical merge). */
2533
- readonly rowSpan?: number;
2534
- /** Validated 6-hex cell shading fill, absent for none/auto. */
2535
- readonly shading?: string;
2536
- /**
2537
- * Layout-owned resolved borders after collapsed conflict resolution.
2538
- *
2539
- * Includes convenience per-side edges, per-grid-interval winners, and explicit compound
2540
- * stroke rectangles in cell-local points. Paint only scales and draws — it must not
2541
- * invent stroke/gap/corner geometry.
2542
- */
2543
- readonly borders?: ResolvedCellBorders;
2544
- /** Nested blocks in reading order; recursion carries nested tables. */
2545
- readonly blocks: readonly BlockFragmentRecord[];
2546
- readonly box: LayoutBox;
2547
- }
2548
- /** A top-level (or cell-level) block fragment, discriminated by `kind`. */
2549
- type BlockFragmentRecord = ParagraphFragmentRecord | TableFragmentRecord;
2550
- /**
2551
- * One header or footer story as it sits on one page.
2552
- *
2553
- * `box` is absolute (sheet coordinates) and sized to the story's FLOW height — never to
2554
- * any anchored-object extent, which is the rule that keeps a decorated header's hit area
2555
- * from covering the body. `fragments` are story-relative (origin at the box's top-left).
2556
- * Baseline furniture may be shared across pages of the same variant when the story has no
2557
- * allowlisted PAGE/NUMPAGES/SECTIONPAGES fields; after page-field finalize, projections are
2558
- * per page (or per distinct field values for count-only stories).
2559
- */
2560
- interface HeaderFooterStoryRecord {
2561
- readonly kind: 'header' | 'footer';
2562
- readonly variant: 'default' | 'first' | 'even';
2563
- readonly partName: string;
2564
- /**
2565
- * The part this story was laid out from. See {@link HeaderFooterStoryLayout.part}.
2566
- *
2567
- * Present when the furniture source supplied it. A pass that needs the story's TREE — the
2568
- * content-control boundary derivation is the one — reads it here rather than resolving a
2569
- * name against a package it does not have.
2570
- */
2571
- readonly part?: OoxmlPart;
2572
- /**
2573
- * Main-document relationship id that resolves to this part (`EditorScope.rId`).
2574
- *
2575
- * Present when the furniture source could name the relationship; scoped editing binds
2576
- * on this id so shared parts stay one story across pages/sections.
2577
- */
2578
- readonly rId?: string;
2579
- readonly box: LayoutBox;
2580
- readonly fragments: readonly BlockFragmentRecord[];
2581
- /** Anchored drawings owned by this story, in story-relative coordinates. */
2582
- readonly anchoredDrawings?: readonly AnchoredDrawingRecord[];
2583
- /**
2584
- * Transient projector used between furniture attach and document-level page-field
2585
- * finalize. Absent on published layout records after finalize — but not dead: finalize
2586
- * retains it (with the context it last ran under) in a side table keyed on the published
2587
- * record (`strippedStoryProjections` in `field-page-furniture.ts`), so a reused sheet can
2588
- * re-project when the page count moves (#441). The retained closure keeps its minting
2589
- * pass's scope alive for as long as the sheet is reused, and anything that clones a
2590
- * published field-bearing story must carry the entry onto the clone
2591
- * (`carryStrippedPageFieldProjection`).
2592
- */
2593
- readonly pageFieldProjector?: (context: {
2594
- readonly pageNumber: number;
2595
- readonly pageCount: number;
2596
- readonly sectionPageCount?: number;
2597
- readonly format?: string;
2598
- }) => HeaderFooterStoryRecord;
2599
- }
2600
- /**
2601
- * One footnote or endnote story as it sits on one page (or continuation page).
2602
- *
2603
- * Notes are ordinary editable stories — NOT `[data-docx-hf]` furniture. `box` is absolute
2604
- * (sheet coordinates). `fragments` are story-relative. Separators are nonselectable paint
2605
- * geometry owned by the parent {@link NoteAreaRecord}.
2606
- */
2607
- interface NoteStoryRecord {
2608
- readonly noteKind: 'footnote' | 'endnote';
2609
- readonly noteId: number;
2610
- /** `footnote:N` / `endnote:N` — EditorScope note id. */
2611
- readonly scopeId: string;
2612
- /** Derived display mark for this occurrence; null when customMarkFollows / continuation. */
2613
- readonly mark: string | null;
2614
- /** True when this is a continuation fragment (no leading mark). */
2615
- readonly continuation?: boolean;
2616
- readonly box: LayoutBox;
2617
- readonly fragments: readonly BlockFragmentRecord[];
2618
- }
2619
- /**
2620
- * Footnote / endnote area on one page: separator + stacked note stories.
2621
- *
2622
- * `placement` records how the area was positioned. `fallbackReason` is set when the
2623
- * bounded reflow loop exhausted and layout kept the reference with its note on a later
2624
- * page (D12 named fallback).
2625
- */
2626
- interface NoteAreaRecord {
2627
- readonly kind: 'footnotes' | 'endnotes';
2628
- readonly placement: 'pageBottom' | 'beneathText' | 'sectEnd' | 'docEnd';
2629
- readonly box: LayoutBox;
2630
- /** Separator rule / authored separator story; absent when no notes on this page. */
2631
- readonly separator?: {
2632
- readonly kind: 'separator' | 'continuationSeparator';
2633
- readonly box: LayoutBox;
2634
- readonly fragments: readonly BlockFragmentRecord[];
2635
- readonly synthetic: boolean;
2636
- /** Layout-owned single/double rule when marker-only or synthetic; absent for authored stories. */
2637
- readonly ruleStyle?: 'single' | 'double';
2638
- };
2639
- readonly notes: readonly NoteStoryRecord[];
2640
- readonly fallbackReason?: string;
2641
- }
2642
- /**
2643
- * Note-stream ownership for overflow sheets created by note pagination.
2644
- *
2645
- * - `footnote-drain`: continuation pages that exist only to finish pageBottom footnotes —
2646
- * not free body hosts for sectEnd/docEnd endnotes.
2647
- * - `endnote-overflow`: sheets inserted to finish sectEnd/docEnd collections.
2648
- */
2649
- type PageNoteStream = 'footnote-drain' | 'endnote-overflow';
2650
- /**
2651
- * Raw `w:lock/@w:val` on one control, or `unlocked` when absent / unrecognised.
2652
- *
2653
- * Effective permissions across a nesting chain are the union of every ancestor's lock on two
2654
- * axes (content edit / removal); see {@link ContentControlBoundaryRecord.effectiveLock}.
2655
- */
2656
- type ContentControlLock = 'unlocked' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked';
2657
- /**
2658
- * Mapped control type for layout / chrome — same members as the shipped public
2659
- * `ContentControlType`. Untyped and preserved-only kinds report as `richText`.
2660
- */
2661
- type ContentControlMappedType = 'richText' | 'plainText' | 'checkbox' | 'dropdown' | 'comboBox' | 'date' | 'picture' | 'repeatingSection';
2662
- /** Where the control sits in the tree relative to its content. */
2663
- type ContentControlLevel = 'block' | 'inline' | 'row' | 'cell';
2664
- /**
2665
- * One piece of a control's content geometry.
2666
- *
2667
- * A block control that crosses a page break publishes one fragment per page rather than a
2668
- * single rectangle covering the inter-page gap. An inline control publishes one fragment per
2669
- * LINE it touches, covering the text's vertical extent (line-spacing leading excluded), so a
2670
- * wrapped control never claims the words beside it. Coordinates match fragment boxes
2671
- * (page-content space).
2672
- */
2673
- interface ContentControlGeometryFragment {
2674
- readonly pageIndex: number;
2675
- readonly box: LayoutBox;
2676
- }
2677
- /**
2678
- * Layout-published boundary for one content control (`w:sdt` / typed `contentControl`).
2679
- *
2680
- * Chrome, lock feedback, and hit resolution read this record — never painted DOM. The wrapper
2681
- * itself is not a layout box; `fragments` cover the content that already flowed in place.
2682
- */
2683
- interface ContentControlBoundaryRecord {
2684
- /** Canonical node id of the control wrapper — not `w:id`. */
2685
- readonly id: string;
2686
- readonly alias?: string;
2687
- readonly tag?: string;
2688
- readonly controlType: ContentControlMappedType;
2689
- /** This control's own `w:lock`, before ancestor union. */
2690
- readonly lock: ContentControlLock;
2691
- /**
2692
- * Nested lock union with every ancestor control, collapsed back to a single `ST_Lock`
2693
- * vocabulary value (both axes locked → `sdtContentLocked`).
2694
- */
2695
- readonly effectiveLock: ContentControlLock;
2696
- /** `w:showingPlcHdr` is present on the control's properties. */
2697
- readonly placeholder: boolean;
2698
- /** `w:dataBinding` is present — content edits are refused as bound. */
2699
- readonly bound: boolean;
2700
- /** 0 for a top-level control; increments through nested wrappers under the shared nesting bound. */
2701
- readonly nestingDepth: number;
2702
- readonly level: ContentControlLevel;
2703
- readonly fragments: readonly ContentControlGeometryFragment[];
2704
- }
2705
- /**
2706
- * One laid-out page: the sheet, the content area, and everything that landed on it.
2707
- *
2708
- * Page identity is REUSED across incremental passes — a pass that changes nothing returns the
2709
- * previous records by reference, which is what lets paint skip untouched pages entirely.
2710
- */
2711
- interface PageRecord {
2712
- readonly id: string;
2713
- readonly index: number;
2714
- /** The whole sheet. */
2715
- readonly box: LayoutBox;
2716
- /** The area inside the margins that content flows into. */
2717
- readonly contentBox: LayoutBox;
2718
- readonly fragments: readonly BlockFragmentRecord[];
2719
- /** Layout-owned vertical rules requested by `w:cols/@w:sep`, content-box relative. */
2720
- readonly columnSeparators?: readonly LayoutBox[];
2721
- /** Page-content anchored drawings on this sheet, absent when there are none. */
2722
- readonly anchoredDrawings?: readonly AnchoredDrawingRecord[];
2723
- /** Page furniture for this page's variant, absent when the document declares none. */
2724
- readonly header?: HeaderFooterStoryRecord;
2725
- readonly footer?: HeaderFooterStoryRecord;
2726
- /** Footnotes reserved on this page (pageBottom / beneathText / continuations). */
2727
- readonly footnotes?: NoteAreaRecord;
2728
- /** Endnotes collected on this page (sectEnd / docEnd). */
2729
- readonly endnotes?: NoteAreaRecord;
2730
- /**
2731
- * Ownership of note-only overflow sheets. Absent on ordinary body pages.
2732
- * Layout pagination sets this so endnote hosting does not treat footnote drain
2733
- * pages as free body space.
2734
- */
2735
- readonly noteStream?: PageNoteStream;
2736
- /**
2737
- * Section-local PAGE/SECTIONPAGES inputs for finalize. Absent → physical page index and
2738
- * document-wide section count (empty `w:pgNumType` behaviour).
2739
- */
2740
- readonly pageFieldSource?: {
2741
- readonly pageNumber: number;
2742
- readonly sectionPageCount: number;
2743
- readonly format?: string;
2744
- };
2745
- /**
2746
- * `true` when this page's body flow (or a body table) carries a PAGE/NUMPAGES/SECTIONPAGES
2747
- * placeholder that document finalize must substitute. Set when the page is assembled, so it
2748
- * rides the record through incremental reuse. `false` lets `finalizePageFieldProjection` skip
2749
- * the substitution walk; `undefined` (a page built by a path that does not stamp it) still
2750
- * walks, which is safe.
2751
- */
2752
- readonly hasBodyPageFields?: boolean;
2753
- /**
2754
- * Content-control boundaries whose geometry intersects this page.
2755
- *
2756
- * Carried on the page so a consumer that only holds a page record still sees current
2757
- * metadata; identity reuse of an unchanged page keeps the same array only when the
2758
- * control-context token matches.
2759
- */
2760
- readonly contentControls?: readonly ContentControlBoundaryRecord[];
2761
- }
2762
- /**
2763
- * A complete layout pass: every page, plus the document-wide indexes derived alongside them.
2764
- *
2765
- * Stamped with the store `revision` it was laid out from, so anything holding geometry can tell
2766
- * whether the document has moved underneath it — which is how stale pointer gestures and
2767
- * overlays are refused rather than applied to coordinates that no longer describe anything.
2768
- */
2769
- interface SemanticLayout {
2770
- /** The store revision these records were laid out from. */
2771
- readonly revision: number;
2772
- readonly pages: readonly PageRecord[];
2773
- /**
2774
- * Every content-control boundary in document order, including multi-page fragment lists.
2775
- *
2776
- * Always recomputed when a document layout pass finishes, so incremental page-identity
2777
- * reuse cannot publish stale alias/tag/lock metadata.
2778
- */
2779
- readonly contentControls?: readonly ContentControlBoundaryRecord[];
2780
- /**
2781
- * Fingerprint of wrapper-only control metadata (alias, tag, lock, type, placeholder, binding).
2782
- *
2783
- * Folded into the layout producer / session context so a metadata-only edit invalidates
2784
- * reuse paths that would otherwise return previous pages by identity with stale boundaries.
2785
- */
2786
- readonly controlContextToken?: string;
2787
- }
2788
- /** Page geometry, in points. */
2789
- interface PageGeometry {
2790
- readonly width: number;
2791
- readonly height: number;
2792
- readonly margin: {
2793
- readonly top: number;
2794
- readonly right: number;
2795
- readonly bottom: number;
2796
- readonly left: number;
2797
- };
2798
- /** `w:pgMar/@header` — sheet edge to header top, in points. Defaults to 36 (720 twips). */
2799
- readonly headerDistance?: number;
2800
- /** `w:pgMar/@footer` — sheet edge to footer bottom, in points. Defaults to 36. */
2801
- readonly footerDistance?: number;
2802
- }
2803
- /** US Letter with one-inch margins, in points. */
2804
- declare const DEFAULT_PAGE_GEOMETRY: PageGeometry;
2805
- /**
2806
- * Text measurement, injected.
2807
- *
2808
- * A real implementation shapes with the resolved font; the tests supply a deterministic one.
2809
- * Layout never reads the DOM, so this is the only way width and height enter it.
2810
- */
2811
- interface TextMeasurer {
2812
- /** Advance width of `text` in the resolved style. */
2813
- measure(text: string, style: ResolvedRunStyle): number;
2814
- /** Line height and baseline for the resolved style. */
2815
- lineMetrics(style: ResolvedRunStyle): {
2816
- height: number;
2817
- baseline: number;
2818
- };
2819
- }
2820
-
2821
- export { type DeclaredFontSubstitution as $, type AnchoredDrawingRecord as A, type BlockFragmentRecord as B, type ContentControlBoundaryRecord as C, type DrawingPoint as D, AUTO_PARAGRAPH_SPACING_PT as E, type FontResourceSnapshot as F, type AbstractNumDefinition as G, type HyperlinkProjector as H, type InlineDrawingRecord as I, type BidiEmbeddingLevels as J, type BorderGridGeometry as K, type LineRecord as L, type ModelRange as M, type NormalizationPolicy as N, type CascadedParagraphFormatting as O, type PageGeometry as P, type ContentControlGeometryFragment as Q, type ResolvedRunStyle as R, type SemanticLayout as S, type TextMeasurer as T, type ContentControlLevel as U, type VersionedShapingLibrary as V, type ContentControlMappedType as W, DEFAULT_PAGE_GEOMETRY as X, DEFAULT_RUN_STYLE as Y, DEFAULT_TAB_INTERVAL_PT as Z, DEFAULT_TAB_INTERVAL_TWIPS as _, type StyleSpanRecord as a, type ResolvedUnderline as a$, EMPTY_NUMBERING_INDEX as a0, EMPTY_TAB_STOPS as a1, type EquationFractionGeometry as a2, type EquationGeometry as a3, type EquationNaryGeometry as a4, type EquationRadicalGeometry as a5, type EquationRowGeometry as a6, type EquationScriptGeometry as a7, type EquationSpanRecord as a8, type EquationTextGeometry as a9, MAX_PARAGRAPH_SPACING_PT as aA, MAX_STYLE_BASED_ON_DEPTH as aB, MAX_STYLE_DEFINITIONS as aC, MAX_TABLE_BORDER_STROKES as aD, MAX_TAB_POSITION_TWIPS as aE, MAX_TAB_STOPS as aF, type NumDefinition as aG, type NumberingLevelIndent as aH, PARAGRAPH_BORDER_SIDES as aI, type PageRefCalibrationCell as aJ, type PageRefFieldProjection as aK, type ParagraphAutoSpacingContext as aL, type ParagraphBorderEdge as aM, type ParagraphBorderSide as aN, type ParagraphBorderStrokeRecord as aO, type ParagraphBorders as aP, type ParagraphBottomBorderRecord as aQ, type ParagraphIndent as aR, type ParagraphLayoutInputs as aS, type ParagraphLineSpacing as aT, type ParagraphSpacing as aU, type RefFieldSpec as aV, type RefNoteParts as aW, type ResolvedCellBorders as aX, type ResolvedTabStops as aY, type ResolvedTableBorderEdge as aZ, type ResolvedTableBorderEdgeSegment as a_, type FieldAtomMarker as aa, type FontFingerprintInputs as ab, type FontRequest as ac, FontResolutionError as ad, type FontResolutionErrorCode as ae, type FontResourceDefinition as af, type FontResourceInstrumentation as ag, type FontResourceSnapshotOptions as ah, type FontSlot as ai, type FontSubstitution as aj, type FontValidationResult as ak, type GlyphOutline as al, HARD_MAX_AGGREGATE_FONT_BYTES as am, HARD_MAX_FONT_BYTES as an, HARD_MAX_FONT_SOURCES as ao, type HeaderFooterStoryRecord as ap, type HyperlinkFieldSpec as aq, type LevelOverride as ar, type LineSpacingRule as as, type ListMarkerAlign as at, type ListMarkerRecord as au, type ListSuffix as av, MAX_BORDER_SPACE_PT as aw, MAX_BORDER_WIDTH_PT as ax, MAX_LVL_OVERRIDES as ay, MAX_NUMBERING_DEFINITIONS as az, type TextShaper as b, parseRefInstruction as b$, SINGLE_LINE_SPACING as b0, type ScriptItem as b1, type ShapeInput as b2, type ShapedCluster as b3, type ShapedFontSpan as b4, type ShapedGlyph as b5, type ShapedRunComparatorInputs as b6, type ShapedVerticalMetrics as b7, type ShapingEnvironment as b8, type ShapingEnvironmentFingerprintInputs as b9, cascadedBottomBorder as bA, cascadedParagraphBorders as bB, cascadedTabStops as bC, collapsedSpaceBefore as bD, createFontResourceSnapshot as bE, createShapedRun as bF, createShapingEnvironment as bG, defaultTabIntervalFromSettings as bH, displayText as bI, fixedPoint as bJ, fontRequestKey as bK, isValidStyleId as bL, itemizeScriptFontSlots as bM, layoutEquation as bN, listMarkerBox as bO, measureDisplayText as bP, mergeListIndent as bQ, nextTabDestination as bR, paragraphBorderExtentPt as bS, paragraphBorderStrokeWidthPt as bT, paragraphBorders as bU, paragraphBordersFingerprint as bV, paragraphBreaksBefore as bW, paragraphContextualSpacing as bX, paragraphLineSpacing as bY, paragraphSpacing as bZ, paragraphTabStops as b_, type ShapingEnvironmentInput as ba, type SourceRange as bb, type SpanLinkRecord as bc, type StyleDefinition as bd, TAB_LEADER_GLYPH as be, type TabAlignment as bf, type TabDestination as bg, type TabLeader as bh, type TabStop as bi, type TableBorderSideName as bj, type TableBorderStrokeRecord as bk, type TableFragmentRecord as bl, type TextDirection as bm, UnsupportedScriptError as bn, type VerticalAlign as bo, appliedSpaceBefore as bp, applyLineSpacing as bq, baselineShiftPtOf as br, borderExtentPt as bs, borderWeight as bt, bottomBorderExtentPt as bu, boundedStructuralFontValidator as bv, buildNumberingIndex as bw, buildStyleCascadeTable as bx, cascadeParagraphFormatting as by, cascadeRunProperties as bz, type FixedPointRoundingMode as c, readBorderSide as c0, readCellBorders as c1, readNumPr as c2, readTableBorders as c3, resolveBorderConflict as c4, resolveNumberingLevel as c5, resolveParagraphLayoutInputs as c6, resolveRunStyle as c7, resolveStoryListItems as c8, resolveStoryRefFields as c9, resolveTableCellBorderGrid as ca, runStylesEqual as cb, shapedRunComparatorInputs as cc, shapingEnvironmentFingerprint as cd, shapingEnvironmentFingerprintInputs as ce, styleForFontSlot as cf, tabAdvanceWidth as cg, tabStopsFingerprint as ch, walkStoryParagraphs as ci, withDefaultTabInterval as cj, withNumberingStyleLinks as ck, withResolvedListItems as cl, type ResolvedFont as d, type TableRowFragmentRecord as e, type TableCellFragmentRecord as f, type NumberingLevel as g, type NumberingIndex as h, type LayoutBox as i, type DrawingInsets as j, type PageRecord as k, type TableBorderSide as l, type ContentControlLock as m, type ParagraphFragmentRecord as n, type FontByteValidator as o, type FixedPoint as p, type ShapedRun as q, type InlineDrawingLayoutContext as r, type StyleCascadeTable as s, type FieldLinkProjector as t, type RefFieldContext as u, type StoryPageFieldNeeds as v, type ResolvedListItem as w, type CellBorderBox as x, type TableCellStyleFormatting as y, type TableBorderBox as z };