@docx-editor.dev/core 0.0.1-placeholder → 2.0.1
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.
- package/LICENSE +201 -0
- package/README.md +92 -2
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/dist/automation.cjs +1 -0
- package/dist/automation.d.cts +74 -0
- package/dist/automation.d.ts +74 -0
- package/dist/automation.js +1 -0
- package/dist/binding.cjs +1 -0
- package/dist/binding.d.cts +82 -0
- package/dist/binding.d.ts +82 -0
- package/dist/binding.js +1 -0
- package/dist/chunk-5KSCB2OZ.js +2 -0
- package/dist/chunk-BSC44TIT.js +5 -0
- package/dist/chunk-DLPL5DAG.js +12 -0
- package/dist/chunk-DOEIARE2.js +2 -0
- package/dist/chunk-G7YCQBZE.cjs +5 -0
- package/dist/chunk-HBL3NNH5.cjs +1 -0
- package/dist/chunk-HR7K3W6A.js +1 -0
- package/dist/chunk-HVAV2JSN.js +11 -0
- package/dist/chunk-MYH2HCUJ.cjs +2 -0
- package/dist/chunk-NSM75TN6.cjs +1 -0
- package/dist/chunk-PHDVKUWZ.js +1 -0
- package/dist/chunk-QADYGK5L.cjs +12 -0
- package/dist/chunk-REZUWYB7.js +1 -0
- package/dist/chunk-RNBHTPW7.js +10 -0
- package/dist/chunk-UD2ZXLFI.cjs +2 -0
- package/dist/chunk-VYAVK2AH.cjs +1 -0
- package/dist/chunk-YUK46A2N.cjs +11 -0
- package/dist/chunk-ZMIX42J7.cjs +10 -0
- package/dist/chunk-ZT55Q52J.cjs +2 -0
- package/dist/chunk-ZUALAR4M.js +2 -0
- package/dist/content-control-walk-CbTqHq6O.d.cts +56 -0
- package/dist/content-control-walk-D06GijR0.d.ts +56 -0
- package/dist/contracts/document.cjs +1 -0
- package/dist/contracts/document.d.cts +246 -0
- package/dist/contracts/document.d.ts +246 -0
- package/dist/contracts/document.js +0 -0
- package/dist/contracts/editor.cjs +1 -0
- package/dist/contracts/editor.d.cts +11 -0
- package/dist/contracts/editor.d.ts +11 -0
- package/dist/contracts/editor.js +1 -0
- package/dist/contracts/interaction.cjs +1 -0
- package/dist/contracts/interaction.d.cts +11 -0
- package/dist/contracts/interaction.d.ts +11 -0
- package/dist/contracts/interaction.js +0 -0
- package/dist/contracts/modules.cjs +1 -0
- package/dist/contracts/modules.d.cts +90 -0
- package/dist/contracts/modules.d.ts +90 -0
- package/dist/contracts/modules.js +1 -0
- package/dist/contracts/types.cjs +1 -0
- package/dist/contracts/types.d.cts +452 -0
- package/dist/contracts/types.d.ts +452 -0
- package/dist/contracts/types.js +0 -0
- package/dist/editor-C1QRi1v4.d.ts +1671 -0
- package/dist/editor-D0UDock4.d.cts +1671 -0
- package/dist/editor.cjs +1 -0
- package/dist/editor.css +6017 -0
- package/dist/editor.d.cts +271 -0
- package/dist/editor.d.ts +271 -0
- package/dist/editor.js +1 -0
- package/dist/hf-references-BdjsQNE_.d.ts +71 -0
- package/dist/hf-references-CgEOSw3i.d.cts +71 -0
- package/dist/image-resources-BJnTpG66.d.ts +195 -0
- package/dist/image-resources-BpIydXtU.d.cts +195 -0
- package/dist/index-CrW-IxXt.d.cts +2515 -0
- package/dist/index-ZqBEOrTV.d.ts +2515 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +1 -0
- package/dist/layout.cjs +1 -0
- package/dist/layout.d.cts +2316 -0
- package/dist/layout.d.ts +2316 -0
- package/dist/layout.js +1 -0
- package/dist/note-nodes-BPSTVDMr.d.ts +108 -0
- package/dist/note-nodes-C6DLxlvj.d.cts +108 -0
- package/dist/note-properties-CnmCoory.d.cts +68 -0
- package/dist/note-properties-m75hmP70.d.ts +68 -0
- package/dist/ooxml-package-CR5us6ad.d.cts +354 -0
- package/dist/ooxml-package-D_CtywTW.d.ts +354 -0
- package/dist/ooxml-tree-BU0e3DVi.d.cts +871 -0
- package/dist/ooxml-tree-BU0e3DVi.d.ts +871 -0
- package/dist/output.cjs +1 -0
- package/dist/output.d.cts +158 -0
- package/dist/output.d.ts +158 -0
- package/dist/output.js +1 -0
- package/dist/protocol-CAkOBBGE.d.cts +1201 -0
- package/dist/protocol-oxHaWmt4.d.ts +1201 -0
- package/dist/review-support-DS8DYX2a.d.cts +326 -0
- package/dist/review-support-Fy3vdIea.d.ts +326 -0
- package/dist/revision-projection-72ZApe-g.d.cts +84 -0
- package/dist/revision-projection-DHAkcD_L.d.ts +84 -0
- package/dist/semantic-cell-selection-CZjBSmMv.d.cts +427 -0
- package/dist/semantic-cell-selection-DsmHmLJ5.d.ts +427 -0
- package/dist/semantic-interaction-D-Dszxrn.d.cts +187 -0
- package/dist/semantic-interaction-Dy-dCUFF.d.ts +187 -0
- package/dist/semantic-paint-drawings-BA3z9jl8.d.ts +21 -0
- package/dist/semantic-paint-drawings-D7qbB0E2.d.cts +21 -0
- package/dist/semantic-records-CJR3BvHs.d.cts +1432 -0
- package/dist/semantic-records-CWodBJsz.d.ts +1432 -0
- package/dist/shaped-measurer-B-TAtcQU.d.ts +613 -0
- package/dist/shaped-measurer-CNIT_svZ.d.cts +613 -0
- package/dist/store.cjs +1 -0
- package/dist/store.d.cts +2247 -0
- package/dist/store.d.ts +2247 -0
- package/dist/store.js +1 -0
- package/dist/tree-op-types-CwOeiaQ8.d.ts +1431 -0
- package/dist/tree-op-types-DMiW6nIH.d.cts +1431 -0
- package/dist/tree-package-store-D5S7VKs5.d.ts +708 -0
- package/dist/tree-package-store-DOKMtCnh.d.cts +708 -0
- package/dist/tree-session-CiUel5Kz.d.ts +542 -0
- package/dist/tree-session-D47_imvR.d.cts +542 -0
- package/dist/tree-store-CxifqwIv.d.cts +273 -0
- package/dist/tree-store-hLtjeyWE.d.ts +273 -0
- package/package.json +105 -15
- package/index.js +0 -1
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,2316 @@
|
|
|
1
|
+
import { V as VersionedShapingLibrary, T as TextShaper, F as FontByteValidator, a as FixedPointRoundingMode, b as FixedPoint, c as TextDirection, S as ShapedRun } from './shaped-measurer-B-TAtcQU.js';
|
|
2
|
+
export { C as CacheLookup, d as CacheMiss, e as CacheProvenance, D as DeclaredFontSubstitution, f as FontFingerprintInputs, g as FontRequest, h as FontResolutionError, i as FontResolutionErrorCode, j as FontResourceDefinition, k as FontResourceInstrumentation, l as FontResourceSnapshot, m as FontResourceSnapshotOptions, n as FontSubstitution, o as FontValidationResult, G as GlyphOutline, H as HARD_MAX_AGGREGATE_FONT_BYTES, p as HARD_MAX_FONT_BYTES, q as HARD_MAX_FONT_SOURCES, L as LayoutShapingOptions, N as NormalizationPolicy, O as OperationSnapshot, r as OperationSnapshotField, s as OperationSnapshotGuard, R as ResolvedCache, t as ResolvedFont, u as ResourceDependencyProvenance, v as ShapeInput, w as ShapedCluster, x as ShapedFontSpan, y as ShapedGlyph, z as ShapedMeasurerOptions, A as ShapedRunComparatorInputs, B as ShapedVerticalMetrics, E as ShapingEnvironment, I as ShapingEnvironmentFingerprintInputs, J as ShapingEnvironmentInput, K as boundedStructuralFontValidator, M as captureOperationSnapshot, P as createFontResourceSnapshot, Q as createShapedMeasurer, U as createShapedRun, W as createShapingEnvironment, X as fixedPoint, Y as fontRequestKey, Z as guardOperationSnapshot, _ as sha256FontBytes, $ as shapedRunComparatorInputs, a0 as shapingEnvironmentFingerprint, a1 as shapingEnvironmentFingerprintInputs } from './shaped-measurer-B-TAtcQU.js';
|
|
3
|
+
import { d as LayoutBox, D as DrawingPoint, e as DrawingInsets, f as TableBorderSide, a as StyleSpanRecord, I as InlineDrawingRecord, M as ModelRange, R as ResolvedRunStyle, g as ThemeFonts, h as ParagraphSpacing, i as ParagraphLineSpacing, j as ParagraphBorderEdge, k as ParagraphBorders, l as ResolvedTabStops, B as BlockFragmentRecord, T as TextMeasurer, S as SemanticLayout, m as PageRecord, n as ParagraphFragmentRecord, A as AnchoredDrawingRecord, F as FieldPageContext, o as InlineDrawingLayoutContext, P as PageGeometry, H as HyperlinkProjector, p as CellBorderBox, q as TableBorderBox } from './semantic-records-CWodBJsz.js';
|
|
4
|
+
export { r as BorderGridGeometry, C as ContentControlBoundaryRecord, s as ContentControlGeometryFragment, t as ContentControlLevel, u as ContentControlLock, v as ContentControlMappedType, w as DEFAULT_PAGE_GEOMETRY, x as DEFAULT_RUN_STYLE, y as DEFAULT_TAB_INTERVAL_PT, z as DEFAULT_TAB_INTERVAL_TWIPS, E as EMPTY_TAB_STOPS, G as HeaderFooterStoryRecord, L as LineRecord, J as LineSpacingRule, K as ListMarkerRecord, N as MAX_BORDER_SPACE_PT, O as MAX_BORDER_WIDTH_PT, Q as MAX_PARAGRAPH_SPACING_PT, U as MAX_TABLE_BORDER_STROKES, V as MAX_TAB_POSITION_TWIPS, W as MAX_TAB_STOPS, X as PARAGRAPH_BORDER_SIDES, Y as ParagraphBorderSide, Z as ParagraphBorderStrokeRecord, _ as ParagraphBottomBorderRecord, $ as ParagraphIndent, a0 as ResolvedCellBorders, a1 as ResolvedTableBorderEdge, a2 as ResolvedTableBorderEdgeSegment, a3 as ResolvedUnderline, a4 as SINGLE_LINE_SPACING, a5 as SourceRange, a6 as SpanLinkRecord, a7 as TAB_LEADER_GLYPH, a8 as TabAlignment, a9 as TabDestination, aa as TabLeader, ab as TabStop, ac as TableBorderSideName, ad as TableBorderStrokeRecord, c as TableCellFragmentRecord, ae as TableFragmentRecord, b as TableRowFragmentRecord, af as VerticalAlign, ag as appliedSpaceBefore, ah as applyLineSpacing, ai as baselineShiftPtOf, aj as borderExtentPt, ak as borderWeight, al as bottomBorderExtentPt, am as cascadedParagraphBorders, an as cascadedTabStops, ao as collapsedSpaceBefore, ap as contentControlsOfLayout, aq as defaultTabIntervalFromSettings, ar as displayText, as as effectiveContentControlLock, at as formatPageNumber, au as fragmentsOfParagraph, av as lineAtPosition, aw as linesOf, ax as measureDisplayText, ay as nextTabDestination, az as paragraphBorderExtentPt, aA as paragraphBorderStrokeWidthPt, aB as paragraphBorders, aC as paragraphBordersFingerprint, aD as paragraphBreaksBefore, aE as paragraphContextualSpacing, aF as paragraphFragmentsOf, aG as paragraphFragmentsOfBlocks, aH as paragraphLineSpacing, aI as paragraphSpacing, aJ as paragraphTabStops, aK as readBorderSide, aL as readCellBorders, aM as readTableBorders, aN as resolveBorderConflict, aO as resolveRunStyle, aP as resolveTableCellBorderGrid, aQ as runStylesEqual, aR as tabAdvanceWidth, aS as tabStopsFingerprint, aT as unionLayoutBoxes, aU as withDefaultTabInterval } from './semantic-records-CWodBJsz.js';
|
|
5
|
+
import { O as OoxmlNode, a as OoxmlElement, e as OoxmlPart } from './ooxml-tree-BU0e3DVi.js';
|
|
6
|
+
import { O as OoxmlProperty, C as ContentControlKind, n as ContentControlLevel, o as ContentControlLock, p as ContentControlDataBinding, I as ImpactClass } from './tree-op-types-CwOeiaQ8.js';
|
|
7
|
+
export { l as TableBorderStyle } from './tree-op-types-CwOeiaQ8.js';
|
|
8
|
+
import { N as NoteKind, n as noteTypeOf } from './note-nodes-BPSTVDMr.js';
|
|
9
|
+
import { R as ResolvedFootnoteProperties, a as ResolvedEndnoteProperties } from './note-properties-m75hmP70.js';
|
|
10
|
+
import { R as RevisionDisplayMode } from './revision-projection-DHAkcD_L.js';
|
|
11
|
+
export { D as DEFAULT_REVISION_DISPLAY_MODE, a as RevisionAttribution, b as RevisionKind, f as formatRevisionOf, p as paragraphMarkRevisionOf, r as revisionsAreDeletion, c as revisionsVisible } from './revision-projection-DHAkcD_L.js';
|
|
12
|
+
import { a as SectionColumns } from './semantic-cell-selection-DsmHmLJ5.js';
|
|
13
|
+
export { C as CellSelection, D as DEFAULT_SECTION_PROPERTIES, b as DEFAULT_VERTICAL_WEIGHT, c as DocumentSection, d as DocumentSectionsEnumeration, e as DrawingOverlayFrame, H as HitPoint, f as HitTestOptions, M as MAX_DOCUMENT_SECTIONS, P as PlacedCell, g as SectionBreakType, h as SectionColumnDefinition, i as SectionMargins, j as SectionPageNumbering, S as SectionProperties, k as SemanticHit, l as SemanticHitDrawing, T as TableCellAddress, m as TableCellContext, n as caretBoxOnLine, o as cellSelectionBetween, p as cellSelectionRects, q as cellSelectionText, r as contentControlAtPoint, s as enumerateDocumentSections, t as enumerateDocumentSectionsBounded, u as findDrawingOverlayFrameInLayout, v as geometryOfSection, w as hitTestPage, x as hitTestSheet, y as isFurniturePoint, z as lineEndOffset, A as pageAtY, B as paragraphSectionNode, E as paragraphsInCells, F as parsePageNumbering, G as parseSectionProperties, I as readSectionProperties, J as spanOffsetX, K as spansInCells, L as tableContextAt } from './semantic-cell-selection-DsmHmLJ5.js';
|
|
14
|
+
export { M as MAX_SDT_NESTING, c as collectFlowBlocks, a as contentControlContentChildren, i as isContentControl, b as isContentControlContent } from './content-control-walk-D06GijR0.js';
|
|
15
|
+
export { h as CommentAnchor, i as CommentPosition, C as CommentRecord, j as CommentThreadState, c as ReviewCommentItem, d as ReviewCustomItem, a as ReviewItem, R as ReviewModelInput, k as ReviewParagraphAnchor, e as ReviewPosition, f as ReviewRange, b as ReviewRevisionItem, g as ReviewRevisionKind, W as W15_NAMESPACE_URI, l as activeReviewItem, m as commentBodyText, n as commentInitials, o as firstReviewRange, p as paragraphOrderOfPart, r as reviewAnchorIndex, q as reviewItemGeometry, s as reviewItemKey, t as reviewItemPositionRank, u as reviewItemRanges, v as reviewItemsAt, w as reviewThreadRootOf } from './review-support-Fy3vdIea.js';
|
|
16
|
+
import { T as TreeModelChange } from './tree-store-hLtjeyWE.js';
|
|
17
|
+
export { b as CaretAtOptions, C as CaretGeometry, K as KeyedRange, M as MoveCaretOptions, N as NavigationCommand, c as SelectionRect, S as SemanticPosition, a as SemanticSelection, d as caretAt, e as caretStops, f as caretStopsForBlocks, g as compositionAnchor, h as contentControlAtSemantic, i as contentControlsInLayout, j as documentOrder, k as hitTestSemantic, l as keyedRangeRects, m as moveCaret, p as paragraphTextFromLayout, s as selectionRects, n as spansInSelection, w as wordBoundary } from './semantic-interaction-Dy-dCUFF.js';
|
|
18
|
+
import './image-resources-BJnTpG66.js';
|
|
19
|
+
import './ooxml-package-D_CtywTW.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Which allowlisted complex page fields a header/footer story actually contains.
|
|
23
|
+
*
|
|
24
|
+
* Drives layout reuse: no fields → one baseline; NUMPAGES only → one layout per page count;
|
|
25
|
+
* SECTIONPAGES only → one layout per section page count; PAGE (alone or combined) → per
|
|
26
|
+
* distinct evaluated values with a bounded cache. `w:fldSimple` never counts — it stays
|
|
27
|
+
* layout-inert.
|
|
28
|
+
*/
|
|
29
|
+
interface StoryPageFieldNeeds {
|
|
30
|
+
readonly hasPage: boolean;
|
|
31
|
+
readonly hasNumPages: boolean;
|
|
32
|
+
readonly hasSectionPages: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type WrapTextSide = 'bothSides' | 'left' | 'right' | 'largest';
|
|
36
|
+
interface WrapExclusionInput {
|
|
37
|
+
readonly mode: 'square' | 'tight' | 'through' | 'topAndBottom';
|
|
38
|
+
readonly contentBounds: LayoutBox;
|
|
39
|
+
readonly polygon: readonly DrawingPoint[] | null;
|
|
40
|
+
readonly clipPolygon: readonly DrawingPoint[] | null;
|
|
41
|
+
readonly wrapDistances: DrawingInsets;
|
|
42
|
+
readonly effectInsets: DrawingInsets;
|
|
43
|
+
readonly textSide: WrapTextSide;
|
|
44
|
+
readonly contentLeft: number;
|
|
45
|
+
readonly contentRight: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lookup of derived display marks keyed by {@link formatNoteScopeId}.
|
|
50
|
+
*
|
|
51
|
+
* `null` mark = customMarkFollows (no automatic digits). Absent key = dangling / unknown
|
|
52
|
+
* — fail-open with an empty display (model atom preserved).
|
|
53
|
+
*/
|
|
54
|
+
interface NoteMarkContext {
|
|
55
|
+
/** scopeId → formatted mark (or null when suppressed). */
|
|
56
|
+
readonly marks: ReadonlyMap<string, string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* When set, every automatic mark measures at least this string's width (eachPage
|
|
59
|
+
* reservation). The string is the widest-measuring candidate under the effective mark
|
|
60
|
+
* style (actual marks plus a bounded per-section value window) — not merely the longest
|
|
61
|
+
* by codepoint count. Display text may still be the real mark; measurement uses the wider
|
|
62
|
+
* of the two so digit-width / proportional-glyph feedback cannot oscillate.
|
|
63
|
+
*/
|
|
64
|
+
readonly reservedMarkText?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Scope id of the note story currently being laid out (`footnote:N` / `endnote:N`).
|
|
67
|
+
*
|
|
68
|
+
* Body `noteReference` atoms carry `@w:id`; note-body `noteRef` atoms do not — they
|
|
69
|
+
* inherit identity from the enclosing note via this key.
|
|
70
|
+
*/
|
|
71
|
+
readonly activeNoteKey?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The key one note's mark is stored under — `footnote:N` / `endnote:N`.
|
|
75
|
+
*
|
|
76
|
+
* The same encoding `EditorScope` uses for note ids, so a mark context and a scope address name
|
|
77
|
+
* the same note without a translation step.
|
|
78
|
+
*/
|
|
79
|
+
declare function noteMarkKey(noteKind: NoteKind, noteId: number): string;
|
|
80
|
+
/**
|
|
81
|
+
* Navigation role for a projected note atom:
|
|
82
|
+
* - `to-note` — body `noteReference` jumps into the note story
|
|
83
|
+
* - `to-body` — note-body `noteRef` jumps back to the body citation
|
|
84
|
+
*/
|
|
85
|
+
type NoteNavDirection = 'to-note' | 'to-body';
|
|
86
|
+
interface ProjectedNoteMark {
|
|
87
|
+
readonly text: string;
|
|
88
|
+
readonly measureText?: string;
|
|
89
|
+
readonly projected: boolean;
|
|
90
|
+
readonly kind: NoteKind | null;
|
|
91
|
+
readonly noteId: number | null;
|
|
92
|
+
readonly nav?: NoteNavDirection;
|
|
93
|
+
readonly scopeId?: string;
|
|
94
|
+
}
|
|
95
|
+
/** Resolve display text for a noteReference / noteRef node under a mark context. */
|
|
96
|
+
declare function projectedNoteMarkText(node: OoxmlNode, context: NoteMarkContext | undefined): ProjectedNoteMark | null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* What one cell side is before any ADJACENT cell has its say.
|
|
100
|
+
*
|
|
101
|
+
* - omitted → the table's own rule for that position (`tblBorders`, `insideH`/`insideV`)
|
|
102
|
+
* - edge → the cell wins outright; no weight fight with the table
|
|
103
|
+
* - none → an explicit `w:val="nil"`. Suppresses a matching table border on interior
|
|
104
|
+
* and perimeter sides alike, so a table whose cells all declare none paints borderless
|
|
105
|
+
* like Word even when `tblBorders` still carry `single` rules.
|
|
106
|
+
*/
|
|
107
|
+
declare function effectiveBorderSide(authored: TableBorderSide, tableSide: TableBorderSide, _options?: {
|
|
108
|
+
readonly interior?: boolean;
|
|
109
|
+
}): TableBorderSide;
|
|
110
|
+
|
|
111
|
+
/** Minimum stroke width for a compound (double/triple) band, in points. */
|
|
112
|
+
declare const COMPOUND_BORDER_MIN_STROKE_PT = 1;
|
|
113
|
+
/** Minimum gap between compound strokes, in points. */
|
|
114
|
+
declare const COMPOUND_BORDER_MIN_GAP_PT = 1;
|
|
115
|
+
/**
|
|
116
|
+
* How a multi-line border style (double, triple) is drawn: stroke width, gap, and total extent.
|
|
117
|
+
*
|
|
118
|
+
* The band is CENTRED on the authored width, so a double border occupies the space Word gives it
|
|
119
|
+
* rather than growing the cell it surrounds.
|
|
120
|
+
*/
|
|
121
|
+
interface CompoundBorderMetrics {
|
|
122
|
+
readonly strokePt: number;
|
|
123
|
+
readonly gapPt: number;
|
|
124
|
+
readonly extentPt: number;
|
|
125
|
+
/** Centers the compound band on the authored width; negative extends outward. */
|
|
126
|
+
readonly insetPt: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Deterministic double stroke / gap / extent in layout points (scale-independent).
|
|
130
|
+
*
|
|
131
|
+
* Thin authored widths inflate to a 1+1+1 point compound so a `w:sz="3"` double remains
|
|
132
|
+
* visible at paint scale 1 — matching Word's hairline-double floor.
|
|
133
|
+
*/
|
|
134
|
+
declare function computeDoubleBorderMetricsPt(widthPt: number): CompoundBorderMetrics;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The exact HarfBuzz build this engine shapes against.
|
|
138
|
+
*
|
|
139
|
+
* Pinned and verified at load: a runtime reporting a different version is REFUSED rather than
|
|
140
|
+
* used, because glyph positioning can change between releases and a cached measurement taken
|
|
141
|
+
* under one build must not be trusted under another.
|
|
142
|
+
*/
|
|
143
|
+
declare const HARFBUZZ_SHAPING_LIBRARY: VersionedShapingLibrary;
|
|
144
|
+
/** Load and verify the HarfBuzz WASM runtime without adding top-level await to import graphs. */
|
|
145
|
+
declare function initializeHarfBuzz(): Promise<void>;
|
|
146
|
+
/** Whether the WASM runtime is loaded and shaping can proceed synchronously. */
|
|
147
|
+
declare function isHarfBuzzInitialized(): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Why shaping refused.
|
|
150
|
+
*
|
|
151
|
+
* Mostly RESOURCE limits, because every input here derives from a file: text length, codepoint
|
|
152
|
+
* count, glyph count and outline size are all attacker-influenced, and an unbounded shape call is
|
|
153
|
+
* a denial-of-service vector rather than a rendering bug.
|
|
154
|
+
*/
|
|
155
|
+
type HarfBuzzShapingErrorCode = 'notInitialized' | 'fontOverLimit' | 'malformedFont' | 'textOverLimit' | 'codepointsOverLimit' | 'glyphOverLimit' | 'outlineOverLimit' | 'shapedRunOverLimit' | 'unsupportedVariationAxes' | 'unsupportedFallback' | 'unsupportedColorFont' | 'unsupportedNormalization' | 'invalidBidiLevel' | 'shapingLibraryMismatch' | 'disposed';
|
|
156
|
+
/**
|
|
157
|
+
* A shaping call that was refused, carrying the limit it exceeded where there was one.
|
|
158
|
+
*
|
|
159
|
+
* Thrown rather than returned: unlike a missing font, a run that cannot be shaped has no sensible
|
|
160
|
+
* fallback measurement, and continuing would lay text out at made-up widths.
|
|
161
|
+
*/
|
|
162
|
+
declare class HarfBuzzShapingError extends Error {
|
|
163
|
+
readonly name = "HarfBuzzShapingError";
|
|
164
|
+
readonly code: HarfBuzzShapingErrorCode;
|
|
165
|
+
readonly limit?: number;
|
|
166
|
+
readonly actual?: number;
|
|
167
|
+
readonly diagnostic?: string;
|
|
168
|
+
constructor(code: HarfBuzzShapingErrorCode, details?: {
|
|
169
|
+
readonly limit?: number;
|
|
170
|
+
readonly actual?: number;
|
|
171
|
+
readonly diagnostic?: string;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Resource budgets and cache sizes for one shaper. Every field optional.
|
|
176
|
+
*
|
|
177
|
+
* The caches exist because shaping is the expensive step: a face is opened once and reused, and
|
|
178
|
+
* identical runs return their previous result. The caps exist because file-derived input decides
|
|
179
|
+
* how much work is asked for.
|
|
180
|
+
*/
|
|
181
|
+
interface HarfBuzzTextShaperOptions {
|
|
182
|
+
readonly maxFontBytes?: number;
|
|
183
|
+
readonly maxInputUtf16?: number;
|
|
184
|
+
readonly maxCodepoints?: number;
|
|
185
|
+
readonly maxGlyphs?: number;
|
|
186
|
+
readonly maxCachedFaces?: number;
|
|
187
|
+
readonly maxCachedShapes?: number;
|
|
188
|
+
readonly maxOutlineBytes?: number;
|
|
189
|
+
readonly maxCachedOutlineBytes?: number;
|
|
190
|
+
readonly maxShapedRunBytes?: number;
|
|
191
|
+
readonly maxCachedShapeBytes?: number;
|
|
192
|
+
readonly instrumentation?: HarfBuzzTextShaperInstrumentation;
|
|
193
|
+
}
|
|
194
|
+
/** One face-cache transition, for instrumentation. */
|
|
195
|
+
interface HarfBuzzFaceCacheEvent {
|
|
196
|
+
readonly kind: 'created' | 'hit' | 'evicted';
|
|
197
|
+
readonly identity: string;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Optional counters for the work a shaper is expected to do rarely.
|
|
201
|
+
*
|
|
202
|
+
* Exists so tests can assert the ABSENCE of work — re-opening a face or re-copying its bytes per
|
|
203
|
+
* shape call would not change any output, only make typing slow, which no rendering assertion
|
|
204
|
+
* would catch.
|
|
205
|
+
*/
|
|
206
|
+
interface HarfBuzzTextShaperInstrumentation {
|
|
207
|
+
readonly onFaceCacheEvent?: (event: HarfBuzzFaceCacheEvent) => void;
|
|
208
|
+
readonly onShapeCacheEvent?: (event: HarfBuzzShapeCacheEvent) => void;
|
|
209
|
+
readonly onByteCopy?: () => void;
|
|
210
|
+
readonly onTableScan?: () => void;
|
|
211
|
+
readonly onShapeCall?: () => void;
|
|
212
|
+
readonly onOutlinePathCall?: () => void;
|
|
213
|
+
readonly onOutlineCacheEvent?: (event: HarfBuzzOutlineCacheEvent) => void;
|
|
214
|
+
}
|
|
215
|
+
/** One shape-cache transition, for instrumentation. */
|
|
216
|
+
interface HarfBuzzShapeCacheEvent {
|
|
217
|
+
readonly kind: 'hit' | 'miss' | 'stored' | 'evicted' | 'skipped' | 'cleared';
|
|
218
|
+
readonly retainedBytes: number;
|
|
219
|
+
}
|
|
220
|
+
/** One outline-cache transition, for instrumentation. */
|
|
221
|
+
interface HarfBuzzOutlineCacheEvent {
|
|
222
|
+
readonly kind: 'created' | 'hit' | 'evicted' | 'skipped' | 'cleared';
|
|
223
|
+
readonly retainedBytes: number;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* A {@link TextShaper} backed by HarfBuzz, holding WASM resources.
|
|
227
|
+
*
|
|
228
|
+
* {@link HarfBuzzTextShaper.dispose} is not optional housekeeping: the cached faces are WASM
|
|
229
|
+
* allocations that garbage collection cannot reclaim, so a shaper outliving its editor leaks
|
|
230
|
+
* until the page goes away.
|
|
231
|
+
*/
|
|
232
|
+
interface HarfBuzzTextShaper extends TextShaper {
|
|
233
|
+
dispose(): void;
|
|
234
|
+
}
|
|
235
|
+
/** Structural sfnt validation that does not construct native HarfBuzz objects. */
|
|
236
|
+
declare const harfBuzzFontValidator: FontByteValidator;
|
|
237
|
+
/** Convert signed font units by an exact rational multiplier using the declared tie rule. */
|
|
238
|
+
declare const roundFontUnitToFixedPoint: (fontUnits: number, denominator: number, numerator: number, mode: FixedPointRoundingMode) => FixedPoint;
|
|
239
|
+
/**
|
|
240
|
+
* Build a HarfBuzz-backed shaper.
|
|
241
|
+
*
|
|
242
|
+
* Requires `initializeHarfBuzz()` to have resolved — shaping is synchronous, so the WASM runtime
|
|
243
|
+
* must already be loaded. Dispose the result when the editor goes away, or its cached faces leak.
|
|
244
|
+
*/
|
|
245
|
+
declare const createHarfBuzzTextShaper: (options?: HarfBuzzTextShaperOptions) => HarfBuzzTextShaper;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* UAX #9 embedding levels, one per UTF-16 code unit, plus the paragraph ranges they were resolved
|
|
249
|
+
* within.
|
|
250
|
+
*
|
|
251
|
+
* The level is EXACT, not a direction: its parity gives direction, but the numeric value is what
|
|
252
|
+
* reordering needs, and collapsing it early loses nested isolates.
|
|
253
|
+
*/
|
|
254
|
+
interface BidiEmbeddingLevels {
|
|
255
|
+
readonly levels: Uint8Array;
|
|
256
|
+
readonly paragraphs: readonly {
|
|
257
|
+
readonly start: number;
|
|
258
|
+
readonly end: number;
|
|
259
|
+
readonly level: number;
|
|
260
|
+
}[];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Which `w:rFonts` slot a character resolves its face through.
|
|
265
|
+
*
|
|
266
|
+
* OOXML gives a run up to four faces and picks between them by SCRIPT, so one run of mixed Latin
|
|
267
|
+
* and CJK text uses two different fonts without saying so anywhere in its properties.
|
|
268
|
+
*/
|
|
269
|
+
type FontSlot = 'ascii' | 'hAnsi' | 'eastAsia' | 'cs';
|
|
270
|
+
/**
|
|
271
|
+
* A run of text sharing one script, direction and font slot — the unit handed to the shaper.
|
|
272
|
+
*
|
|
273
|
+
* Shaping cannot span a script change: Arabic and Latin in one call would produce wrong joining
|
|
274
|
+
* behaviour, so a run is itemized into these first.
|
|
275
|
+
*/
|
|
276
|
+
interface ScriptItem {
|
|
277
|
+
readonly from: number;
|
|
278
|
+
readonly to: number;
|
|
279
|
+
readonly direction: TextDirection;
|
|
280
|
+
readonly bidiLevel: number;
|
|
281
|
+
readonly script: 'Zyyy' | 'Latn' | 'Grek' | 'Cyrl' | 'Hani' | 'Hebr' | 'Arab' | 'Deva' | 'Beng' | 'Thai' | 'Khmr';
|
|
282
|
+
readonly slot: FontSlot;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* A code point whose script this engine does not itemize.
|
|
286
|
+
*
|
|
287
|
+
* Carries the offending `codePoint` so the caller can report which character stopped it, rather
|
|
288
|
+
* than failing anonymously somewhere in the middle of a paragraph.
|
|
289
|
+
*/
|
|
290
|
+
declare class UnsupportedScriptError extends Error {
|
|
291
|
+
readonly name = "UnsupportedScriptError";
|
|
292
|
+
readonly code = "unsupportedScript";
|
|
293
|
+
readonly codePoint: number;
|
|
294
|
+
constructor(codePoint: number);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Split text into shapeable runs by script, bidi level and font slot.
|
|
298
|
+
*
|
|
299
|
+
* Consumes the bidi levels rather than re-deriving direction, so itemization and reordering agree
|
|
300
|
+
* by construction instead of by two implementations happening to match.
|
|
301
|
+
*/
|
|
302
|
+
declare function itemizeScriptFontSlots(text: string, paragraphOffset: number, embedding: BidiEmbeddingLevels): readonly ScriptItem[];
|
|
303
|
+
|
|
304
|
+
/** UTF-16 boundaries that are both whole-grapheme and HarfBuzz cluster edges. */
|
|
305
|
+
declare function shapedHorizontalBoundaries(run: ShapedRun): readonly number[];
|
|
306
|
+
/**
|
|
307
|
+
* Whether an offset is a real caret position: both a grapheme boundary and a shaped cluster edge.
|
|
308
|
+
*
|
|
309
|
+
* Both conditions, because they disagree. A ligature is one cluster spanning two graphemes, and
|
|
310
|
+
* an offset inside it has no geometry the shaper can answer for — placing a caret there would
|
|
311
|
+
* mean inventing an x coordinate.
|
|
312
|
+
*/
|
|
313
|
+
declare function isWholeGraphemeHorizontalBoundary(run: ShapedRun, utf16Offset: number): boolean;
|
|
314
|
+
/** Every published shaped boundary has exact geometry from the shaping result. */
|
|
315
|
+
declare function isGeometryTrustedCaretOffset(run: ShapedRun, utf16Offset: number): boolean;
|
|
316
|
+
/**
|
|
317
|
+
* Whether the distance from a line's start to an offset can be trusted as exact.
|
|
318
|
+
*
|
|
319
|
+
* Requires BOTH ends to be shaped boundaries: accumulating advances across an offset the shaper
|
|
320
|
+
* cannot place would produce a caret x that drifts further along the line.
|
|
321
|
+
*/
|
|
322
|
+
declare function isCumulativeGeometryTrustedFromLineOrigin(run: ShapedRun, lineStartUtf16Offset: number, utf16Offset: number): boolean;
|
|
323
|
+
/** Grapheme offsets corresponding to exact shaped boundaries. */
|
|
324
|
+
declare function semanticHorizontalBoundaries(run: ShapedRun): readonly number[];
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* One user-perceived character, and the UTF-16 range that encodes it.
|
|
328
|
+
*
|
|
329
|
+
* The unit a caret moves by. An emoji with a skin-tone modifier is ONE segment spanning several
|
|
330
|
+
* UTF-16 code units, so stepping by code unit would put the caret inside it.
|
|
331
|
+
*/
|
|
332
|
+
interface GraphemeSegment {
|
|
333
|
+
readonly index: number;
|
|
334
|
+
readonly text: string;
|
|
335
|
+
readonly utf16From: number;
|
|
336
|
+
readonly utf16To: number;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* The replaceable segmentation strategy.
|
|
340
|
+
*
|
|
341
|
+
* An explicit seam rather than a direct `Intl.Segmenter` call, so tests can install a
|
|
342
|
+
* deterministic boundary and a runtime without `Intl.Segmenter` can be given one.
|
|
343
|
+
*/
|
|
344
|
+
interface GraphemeBoundary {
|
|
345
|
+
segment(text: string): readonly GraphemeSegment[];
|
|
346
|
+
}
|
|
347
|
+
/** Invariant locale for deterministic cross-runtime grapheme boundaries. */
|
|
348
|
+
declare const GRAPHEME_SEGMENTER_LOCALE: "und";
|
|
349
|
+
/** Whether this runtime provides `Intl.Segmenter`, which the default boundary requires. */
|
|
350
|
+
declare function isIntlSegmenterAvailable(): boolean;
|
|
351
|
+
/**
|
|
352
|
+
* The default boundary, over `Intl.Segmenter` at the invariant `und` locale.
|
|
353
|
+
*
|
|
354
|
+
* Locale-invariant on purpose: grapheme boundaries must not vary with the user's locale, or the
|
|
355
|
+
* same document would paginate differently for different readers.
|
|
356
|
+
*/
|
|
357
|
+
declare const intlGraphemeBoundary: GraphemeBoundary;
|
|
358
|
+
/** Current boundary generation, for callers that cache segmentation-derived answers. */
|
|
359
|
+
declare function graphemeBoundaryEpoch(): number;
|
|
360
|
+
/**
|
|
361
|
+
* Split text into graphemes through the active boundary.
|
|
362
|
+
*
|
|
363
|
+
* Memoized on the last texts seen, because paragraph layout asks about the same string
|
|
364
|
+
* repeatedly and a full segmentation pass per call made layout quadratic in paragraph length.
|
|
365
|
+
*/
|
|
366
|
+
declare function segmentGraphemes(text: string): readonly GraphemeSegment[];
|
|
367
|
+
/**
|
|
368
|
+
* Install a different segmentation strategy, invalidating the memo.
|
|
369
|
+
*
|
|
370
|
+
* For tests and for runtimes lacking `Intl.Segmenter`. Call {@link resetGraphemeBoundary} to
|
|
371
|
+
* restore the default.
|
|
372
|
+
*/
|
|
373
|
+
declare function setGraphemeBoundary(boundary: GraphemeBoundary): void;
|
|
374
|
+
/** Restore the default `Intl.Segmenter` boundary and clear the memo. */
|
|
375
|
+
declare function resetGraphemeBoundary(): void;
|
|
376
|
+
/** How many user-perceived characters the text holds. */
|
|
377
|
+
declare function graphemeCount(text: string): number;
|
|
378
|
+
/**
|
|
379
|
+
* The grapheme index containing a UTF-16 offset. Clamps rather than throwing.
|
|
380
|
+
*
|
|
381
|
+
* Backed by a single-entry index cache: this runs once per character during paragraph layout, and
|
|
382
|
+
* re-segmenting per call is what made a 20,000-character paragraph take minutes to open. One
|
|
383
|
+
* entry rather than a map, because the keys are file-derived strings of unbounded size.
|
|
384
|
+
*/
|
|
385
|
+
declare function utf16OffsetToGrapheme(text: string, utf16Offset: number): number;
|
|
386
|
+
/** The UTF-16 offset a grapheme index starts at. Clamps rather than throwing. */
|
|
387
|
+
declare function graphemeOffsetToUtf16(text: string, graphemeOffset: number): number;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* One word-segmentation span. `wordLike` separates words from the whitespace and punctuation
|
|
391
|
+
* between them, which is what double-click selection needs to skip.
|
|
392
|
+
*/
|
|
393
|
+
interface WordSegment {
|
|
394
|
+
readonly utf16From: number;
|
|
395
|
+
readonly utf16To: number;
|
|
396
|
+
readonly wordLike: boolean;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The replaceable word-segmentation strategy.
|
|
400
|
+
*
|
|
401
|
+
* Falls back to a BOUNDED grapheme-safe splitter where `Intl.Segmenter` is absent — bounded
|
|
402
|
+
* because the input is file-derived, and grapheme-safe so a fallback never splits inside an
|
|
403
|
+
* emoji.
|
|
404
|
+
*/
|
|
405
|
+
interface WordBoundary {
|
|
406
|
+
segment(text: string): readonly WordSegment[];
|
|
407
|
+
}
|
|
408
|
+
/** Invariant locale for deterministic cross-runtime word boundaries. */
|
|
409
|
+
declare const WORD_SEGMENTER_LOCALE: "und";
|
|
410
|
+
/** Whether this runtime provides word-granularity `Intl.Segmenter`. */
|
|
411
|
+
declare function isIntlWordSegmenterAvailable(): boolean;
|
|
412
|
+
/** Intl.Segmenter word boundary for the invariant locale. */
|
|
413
|
+
declare function createIntlWordBoundary(): WordBoundary;
|
|
414
|
+
/** Bounded deterministic fallback — not full UAX #29 word conformance. */
|
|
415
|
+
declare function boundedFallbackWordSegments(text: string): readonly WordSegment[];
|
|
416
|
+
/** Explicit bounded fallback boundary (grapheme-safe, narrower than Intl). */
|
|
417
|
+
declare function createBoundedFallbackWordBoundary(): WordBoundary;
|
|
418
|
+
/** Injection points for {@link createDefaultWordBoundary}, so tests can force either path. */
|
|
419
|
+
interface WordBoundaryResolverDeps {
|
|
420
|
+
readonly isIntlAvailable?: () => boolean;
|
|
421
|
+
readonly createIntlBoundary?: () => WordBoundary;
|
|
422
|
+
readonly createFallbackBoundary?: () => WordBoundary;
|
|
423
|
+
}
|
|
424
|
+
/** Resolve production word boundary: Intl when available/construction succeeds, else bounded fallback. */
|
|
425
|
+
declare function createDefaultWordBoundary(deps?: WordBoundaryResolverDeps): WordBoundary;
|
|
426
|
+
/** Cached immutable production boundary (first resolved instance only). */
|
|
427
|
+
declare function resolveDefaultWordBoundary(): WordBoundary;
|
|
428
|
+
/** Split text into word segments through the given boundary, or the resolved default. */
|
|
429
|
+
declare function segmentWords(text: string, boundary?: WordBoundary): readonly WordSegment[];
|
|
430
|
+
/**
|
|
431
|
+
* A word span expressed in GRAPHEME offsets rather than UTF-16 ones.
|
|
432
|
+
*
|
|
433
|
+
* What selection actually uses: a range whose ends are UTF-16 offsets could land inside a
|
|
434
|
+
* grapheme, and selecting half an emoji is not a word.
|
|
435
|
+
*/
|
|
436
|
+
interface GraphemeWordSegmentRecord {
|
|
437
|
+
readonly graphemeFrom: number;
|
|
438
|
+
readonly graphemeTo: number;
|
|
439
|
+
readonly wordLike: boolean;
|
|
440
|
+
}
|
|
441
|
+
/** Map UTF-16 word segments to grapheme-safe half-open ranges within one paragraph. */
|
|
442
|
+
declare function wordSegmentsToGraphemeRecords(text: string, segments: readonly WordSegment[]): readonly GraphemeWordSegmentRecord[];
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Strict hex fill: exactly six hex digits. Rejects `auto`, `nil`, and any non-hex payload
|
|
446
|
+
* (CSS functions, URLs, short hex, theme tokens).
|
|
447
|
+
*/
|
|
448
|
+
declare function resolveStrictHexFill(raw: string | undefined): string | undefined;
|
|
449
|
+
/**
|
|
450
|
+
* Resolve a `w:shd` attribute bag to a validated RRGGBB fill, or undefined.
|
|
451
|
+
*
|
|
452
|
+
* `w:themeFill` is a REFERENCE, and Word always writes the value it resolved to alongside
|
|
453
|
+
* it: `<w:shd w:val="clear" w:fill="D9E2F3" w:themeFill="accent1" w:themeFillTint="33"/>`.
|
|
454
|
+
* Reading `w:fill` in that case is not inventing a colour from the theme — it is reading
|
|
455
|
+
* the colour the producer computed. Dropping the fill because a theme reference sat next
|
|
456
|
+
* to it left every accent-shaded cell and paragraph unpainted.
|
|
457
|
+
*
|
|
458
|
+
* `val="nil"` clears shading. Pattern vals are not rendered; a valid solid `fill` still
|
|
459
|
+
* paints as a clear fill until pattern support lands. A theme reference with no usable
|
|
460
|
+
* `w:fill` still resolves to nothing rather than a guess.
|
|
461
|
+
*/
|
|
462
|
+
declare function resolveOoxmlShadingFill(attributes: Readonly<Record<string, string>> | undefined): string | undefined;
|
|
463
|
+
/** Read `w:shd` from a typed/generic element (table `tcPr`, nested `pPr`, …). */
|
|
464
|
+
declare function shadingFillFromElement(shd: OoxmlElement | undefined): string | undefined;
|
|
465
|
+
/**
|
|
466
|
+
* Resolve paragraph shading from cascaded flat `w:pPr` properties.
|
|
467
|
+
*
|
|
468
|
+
* Later `w:shd` entries win (defaults → style → direct), matching spacing/border cascade.
|
|
469
|
+
*/
|
|
470
|
+
declare function paragraphShading(props: readonly OoxmlProperty[]): string | undefined;
|
|
471
|
+
/**
|
|
472
|
+
* Page-content box for paragraph shading: union of this fragment's line boxes.
|
|
473
|
+
*
|
|
474
|
+
* Excludes collapsed before/after spacing and bottom-border extent so the painted band
|
|
475
|
+
* matches Word's content-area fill (character shading height on a single line).
|
|
476
|
+
*/
|
|
477
|
+
declare function paragraphShadingBox(lines: readonly {
|
|
478
|
+
readonly box: LayoutBox;
|
|
479
|
+
}[], x: number, width: number): LayoutBox | undefined;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Where one note is referenced from — what per-page and per-section restart rules are computed
|
|
483
|
+
* against.
|
|
484
|
+
*/
|
|
485
|
+
interface NoteReferenceSite {
|
|
486
|
+
/** Stable note id (`w:id`). */
|
|
487
|
+
readonly noteId: number;
|
|
488
|
+
/** Section index of the reference (0-based). */
|
|
489
|
+
readonly sectionIndex: number;
|
|
490
|
+
/**
|
|
491
|
+
* Page index of the reference when known (0-based). Required for `eachPage` restart;
|
|
492
|
+
* when omitted, `eachPage` behaves like `continuous` for that site.
|
|
493
|
+
*/
|
|
494
|
+
readonly pageIndex?: number;
|
|
495
|
+
/** When true, consumes no automatic number (`customMarkFollows`). */
|
|
496
|
+
readonly customMarkFollows?: boolean;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* The mark a note reference paints.
|
|
500
|
+
*
|
|
501
|
+
* `null` where `w:customMarkFollows` suppresses it: the document supplies its own glyph, and
|
|
502
|
+
* painting an automatic number too would show the note twice.
|
|
503
|
+
*/
|
|
504
|
+
interface NoteDisplayMark {
|
|
505
|
+
readonly noteId: number;
|
|
506
|
+
/** Formatted mark, or `null` when suppressed by customMarkFollows. */
|
|
507
|
+
readonly mark: string | null;
|
|
508
|
+
/** 1-based automatic sequence number when assigned; absent when suppressed. */
|
|
509
|
+
readonly displayNumber?: number;
|
|
510
|
+
}
|
|
511
|
+
type ResolvedNoteProperties = ResolvedFootnoteProperties | ResolvedEndnoteProperties;
|
|
512
|
+
/**
|
|
513
|
+
* Derive display marks for references of one note kind in document order.
|
|
514
|
+
*
|
|
515
|
+
* Restart rules:
|
|
516
|
+
* - `continuous` — single sequence across the document from `numStart`
|
|
517
|
+
* - `eachSect` — restart at `numStart` when `sectionIndex` changes
|
|
518
|
+
* - `eachPage` — restart when `pageIndex` changes (falls back to continuous if unknown)
|
|
519
|
+
*
|
|
520
|
+
* IDs are stable; only display numbers change. Non-mutating.
|
|
521
|
+
*/
|
|
522
|
+
declare function deriveNoteDisplayMarks(noteKind: NoteKind, references: readonly NoteReferenceSite[], properties: ResolvedNoteProperties): readonly NoteDisplayMark[];
|
|
523
|
+
/**
|
|
524
|
+
* Derive marks using per-reference-section resolved properties (`numFmt` / `numStart` /
|
|
525
|
+
* `numRestart`). Restart rules consult each site's own section props.
|
|
526
|
+
*/
|
|
527
|
+
declare function deriveNoteDisplayMarksResolved(_noteKind: NoteKind, references: readonly NoteReferenceSite[], resolveProps: (sectionIndex: number) => ResolvedNoteProperties): readonly NoteDisplayMark[];
|
|
528
|
+
/** Map noteId → formatted mark for quick lookup (last site wins if duplicated). */
|
|
529
|
+
declare function noteDisplayMarkMap(marks: readonly NoteDisplayMark[]): ReadonlyMap<number, string | null>;
|
|
530
|
+
|
|
531
|
+
/** A fingerprint over one paragraph's layout inputs. */
|
|
532
|
+
type ParagraphLayoutKey = string;
|
|
533
|
+
/** Cache counters, for asserting that incremental layout is actually reusing work. */
|
|
534
|
+
interface LayoutCacheStats {
|
|
535
|
+
readonly hits: number;
|
|
536
|
+
readonly misses: number;
|
|
537
|
+
readonly evictions: number;
|
|
538
|
+
readonly size: number;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The per-paragraph measurement cache.
|
|
542
|
+
*
|
|
543
|
+
* Caches the BREAK only — where a paragraph's lines fall at a given width — never its placement.
|
|
544
|
+
* An edit high in a document still repaginates everything below it, while paragraphs nobody
|
|
545
|
+
* touched are never measured again.
|
|
546
|
+
*/
|
|
547
|
+
interface ParagraphLayoutCache<T> {
|
|
548
|
+
get(key: ParagraphLayoutKey): T | undefined;
|
|
549
|
+
set(key: ParagraphLayoutKey, value: T): void;
|
|
550
|
+
/** Drop entries for paragraphs a commit removed, so the cache cannot grow without bound. */
|
|
551
|
+
retain(keys: ReadonlySet<ParagraphLayoutKey>): void;
|
|
552
|
+
clear(): void;
|
|
553
|
+
readonly stats: LayoutCacheStats;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Everything that decides whether a cached paragraph break is still valid.
|
|
557
|
+
*
|
|
558
|
+
* `producer` is in the key because a font arriving after first paint changes every advance in the
|
|
559
|
+
* document while no content changes — without it the cache would serve the pre-font layout
|
|
560
|
+
* forever.
|
|
561
|
+
*/
|
|
562
|
+
interface ParagraphKeyInputs {
|
|
563
|
+
readonly paragraph: OoxmlNode;
|
|
564
|
+
readonly properties: readonly OoxmlProperty[];
|
|
565
|
+
/** Available width, which decides where the lines break. */
|
|
566
|
+
readonly width: number;
|
|
567
|
+
/**
|
|
568
|
+
* Who produced the measurements.
|
|
569
|
+
*
|
|
570
|
+
* Fonts loading after first paint change every advance while no content changes, so a
|
|
571
|
+
* cache keyed on content alone would serve the pre-font layout forever.
|
|
572
|
+
*/
|
|
573
|
+
readonly producer: string;
|
|
574
|
+
/**
|
|
575
|
+
* Inline drawing projection/resource epoch for this paragraph.
|
|
576
|
+
*
|
|
577
|
+
* Pending→ready/refused transitions and extent/hidden changes must invalidate breaks even
|
|
578
|
+
* when paragraph text is unchanged.
|
|
579
|
+
*/
|
|
580
|
+
readonly drawingToken?: string;
|
|
581
|
+
/** Active page exclusion zones affecting this paragraph's break. */
|
|
582
|
+
readonly exclusionToken?: string;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* The cache key for one paragraph's measured break.
|
|
586
|
+
*
|
|
587
|
+
* Folds in the content, the available width, and the measurement producer. Anything that changes
|
|
588
|
+
* where lines fall must be in here, or the cache serves a break taken under different conditions.
|
|
589
|
+
*/
|
|
590
|
+
declare function paragraphLayoutKey(inputs: ParagraphKeyInputs): ParagraphLayoutKey;
|
|
591
|
+
/** How large the paragraph cache grows before least-recently-used eviction. */
|
|
592
|
+
interface ParagraphLayoutCacheOptions {
|
|
593
|
+
/**
|
|
594
|
+
* Entries retained before the least recently used are dropped.
|
|
595
|
+
*
|
|
596
|
+
* The default has to exceed a realistic document, or a full pass evicts exactly what the
|
|
597
|
+
* next one needs and the cache costs more than it saves.
|
|
598
|
+
*/
|
|
599
|
+
readonly maxEntries?: number;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* A bounded least-recently-used cache.
|
|
603
|
+
*
|
|
604
|
+
* Bounded because a long editing session touches far more paragraph states than a document
|
|
605
|
+
* contains — every keystroke mints a new key for the paragraph being typed in — and an
|
|
606
|
+
* unbounded cache would hold every intermediate state of the session.
|
|
607
|
+
*/
|
|
608
|
+
declare function createParagraphLayoutCache<T>(options?: ParagraphLayoutCacheOptions): ParagraphLayoutCache<T>;
|
|
609
|
+
|
|
610
|
+
/** Paint layer relative to body text — not the OOXML wrap element. */
|
|
611
|
+
type DrawingPaintLayer = 'behind' | 'inFront';
|
|
612
|
+
interface ExclusionZone {
|
|
613
|
+
readonly drawingNodeId: string;
|
|
614
|
+
readonly anchorParagraphId: string;
|
|
615
|
+
/** UTF-16 model offset of the anchor atom — exclusions apply at/after this point in the paragraph. */
|
|
616
|
+
readonly anchorModelStart: number;
|
|
617
|
+
readonly sourceOrder: number;
|
|
618
|
+
readonly paintLayer: DrawingPaintLayer;
|
|
619
|
+
readonly relativeHeight: number;
|
|
620
|
+
readonly allowOverlap: boolean;
|
|
621
|
+
/** Owning column index in a multi-column section — 0 for single-column and HF stories. */
|
|
622
|
+
readonly columnIndex: number;
|
|
623
|
+
/** Resolved top edge in page-content coordinates after overlap displacement. */
|
|
624
|
+
readonly y: number;
|
|
625
|
+
readonly verticalBand: LayoutBox;
|
|
626
|
+
readonly input: WrapExclusionInput;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
interface PendingLine {
|
|
630
|
+
readonly spans: StyleSpanRecord[];
|
|
631
|
+
readonly drawings: InlineDrawingRecord[];
|
|
632
|
+
readonly start: number;
|
|
633
|
+
end: number;
|
|
634
|
+
width: number;
|
|
635
|
+
height: number;
|
|
636
|
+
baseline: number;
|
|
637
|
+
/**
|
|
638
|
+
* Space ABOVE the glyph band inside {@link height}.
|
|
639
|
+
*
|
|
640
|
+
* Exact spacing can center the glyphs and move the baseline. Auto/atLeast spacing leaves
|
|
641
|
+
* this at zero and puts its extra depth below instead.
|
|
642
|
+
*/
|
|
643
|
+
leading: number;
|
|
644
|
+
/**
|
|
645
|
+
* Auto/atLeast line-spacing depth below the painted glyph band.
|
|
646
|
+
*
|
|
647
|
+
* Word lets this external depth cross the bottom text margin when the glyphs themselves
|
|
648
|
+
* still fit. Pagination therefore budgets {@link height} minus this amount at a page
|
|
649
|
+
* bottom, while paint keeps the full box and padding.
|
|
650
|
+
*/
|
|
651
|
+
trailingSpacing: number;
|
|
652
|
+
/** When true, layout must start a new page after this line is placed. */
|
|
653
|
+
pageBreakAfter?: boolean;
|
|
654
|
+
/** When true, layout must advance to the next authored section column. */
|
|
655
|
+
columnBreakAfter?: boolean;
|
|
656
|
+
/** Model ranges on this line covering deleted content; see {@link LineRecord.deletedRanges}. */
|
|
657
|
+
deletedRanges?: readonly ModelRange[];
|
|
658
|
+
/** Vertical gap inserted before this line to clear a topAndBottom exclusion band. */
|
|
659
|
+
exclusionSkipBefore?: number;
|
|
660
|
+
}
|
|
661
|
+
/** Horizontal alignment of a paragraph (`w:jc`, ECMA-376 §17.3.1.13). */
|
|
662
|
+
type Alignment = 'left' | 'center' | 'right' | 'both';
|
|
663
|
+
|
|
664
|
+
/** Soft ceiling on abstractNum / num entries read from one part. */
|
|
665
|
+
declare const MAX_NUMBERING_DEFINITIONS = 512;
|
|
666
|
+
/** Soft ceiling on override entries per `w:num`. */
|
|
667
|
+
declare const MAX_LVL_OVERRIDES = 9;
|
|
668
|
+
/** `w:suff` — what separates a list marker from the text after it. */
|
|
669
|
+
type ListSuffix = 'tab' | 'space' | 'nothing';
|
|
670
|
+
/** `w:lvlJc` — how a list marker aligns within its own indent. */
|
|
671
|
+
type ListMarkerAlign = 'left' | 'center' | 'right';
|
|
672
|
+
/**
|
|
673
|
+
* One level's indent, plus which parts the level actually AUTHORED.
|
|
674
|
+
*
|
|
675
|
+
* The provenance matters: `w:ind` cascades per-attribute, so a level that authored only `left`
|
|
676
|
+
* must not overwrite an inherited `hanging` with a synthesized zero.
|
|
677
|
+
*/
|
|
678
|
+
interface NumberingLevelIndent {
|
|
679
|
+
readonly left: number;
|
|
680
|
+
readonly right: number;
|
|
681
|
+
readonly hanging: number;
|
|
682
|
+
readonly firstLine: number;
|
|
683
|
+
/**
|
|
684
|
+
* Which of these the LEVEL actually authored.
|
|
685
|
+
*
|
|
686
|
+
* A level's `w:pPr/w:ind` sits between the paragraph style and direct formatting, so the
|
|
687
|
+
* merge has to tell "the level says left = 0" from "the level says nothing about left and
|
|
688
|
+
* the style's value stands". Absent (the default) reads as "says nothing", which is what a
|
|
689
|
+
* hand-built level in a test means.
|
|
690
|
+
*/
|
|
691
|
+
readonly stated?: {
|
|
692
|
+
readonly left: boolean;
|
|
693
|
+
readonly right: boolean;
|
|
694
|
+
/** `w:hanging`/`w:firstLine` are one mutually exclusive slot (§17.3.1.10, §17.3.1.12). */
|
|
695
|
+
readonly firstLineOffset: boolean;
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
/** One `w:lvl`: how this depth numbers, what its marker looks like, and how it indents. */
|
|
699
|
+
interface NumberingLevel {
|
|
700
|
+
readonly ilvl: number;
|
|
701
|
+
readonly start: number;
|
|
702
|
+
readonly numFmt: string;
|
|
703
|
+
readonly lvlText: string;
|
|
704
|
+
readonly lvlJc: ListMarkerAlign;
|
|
705
|
+
readonly suff: ListSuffix;
|
|
706
|
+
readonly indent: NumberingLevelIndent;
|
|
707
|
+
/**
|
|
708
|
+
* `w:lvlRestart` one-based trigger level, or `0` when the level never restarts.
|
|
709
|
+
* Omitted in XML → restart when the previous level (or any earlier level) is used.
|
|
710
|
+
*/
|
|
711
|
+
readonly lvlRestart?: number;
|
|
712
|
+
/**
|
|
713
|
+
* `w:isLgl` (§17.9.9): render EVERY level referenced by this level's `w:lvlText` in
|
|
714
|
+
* decimal, whatever number format those levels declare for themselves.
|
|
715
|
+
*/
|
|
716
|
+
readonly isLgl: boolean;
|
|
717
|
+
/** Level `w:rPr` as flat properties (for marker face / vanish). */
|
|
718
|
+
readonly runProperties: readonly OoxmlProperty[];
|
|
719
|
+
/** True when level run props request vanish — marker must not paint. */
|
|
720
|
+
readonly vanish: boolean;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* One `w:lvlOverride` on a `w:num`: a restart value, a replacement level, or both.
|
|
724
|
+
*
|
|
725
|
+
* How two lists share an abstract definition while numbering independently.
|
|
726
|
+
*/
|
|
727
|
+
interface LevelOverride {
|
|
728
|
+
readonly startOverride?: number;
|
|
729
|
+
/** Full level replacement when `w:lvl` is present under the override. */
|
|
730
|
+
readonly level?: NumberingLevel;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* One `w:abstractNum` — the shape of a list, without being a list.
|
|
734
|
+
*
|
|
735
|
+
* Never referenced by a paragraph directly. Paragraphs name a {@link NumDefinition}, which names
|
|
736
|
+
* one of these, so several lists can share a definition and still count separately.
|
|
737
|
+
*/
|
|
738
|
+
interface AbstractNumDefinition {
|
|
739
|
+
readonly abstractNumId: string;
|
|
740
|
+
readonly levels: ReadonlyMap<number, NumberingLevel>;
|
|
741
|
+
/**
|
|
742
|
+
* `w:numStyleLink` (§17.9.21): this definition carries no levels of its own — the numbering
|
|
743
|
+
* lives on the named style. Word follows the link; resolve it with
|
|
744
|
+
* {@link resolveNumberingStyleLinks} before reading levels, or every paragraph on this
|
|
745
|
+
* abstract renders with NO marker at all.
|
|
746
|
+
*/
|
|
747
|
+
readonly numStyleLink?: string;
|
|
748
|
+
/**
|
|
749
|
+
* `w:styleLink` (§17.9.23): the definition side of the same pair — this abstract IS the
|
|
750
|
+
* numbering of the named style. Kept so a link can be verified rather than assumed.
|
|
751
|
+
*/
|
|
752
|
+
readonly styleLink?: string;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* One `w:num` — the thing a paragraph's `w:numId` actually names.
|
|
756
|
+
*
|
|
757
|
+
* Points at an {@link AbstractNumDefinition} and may override any of its levels.
|
|
758
|
+
*/
|
|
759
|
+
interface NumDefinition {
|
|
760
|
+
readonly numId: string;
|
|
761
|
+
readonly abstractNumId: string;
|
|
762
|
+
readonly overrides: ReadonlyMap<number, LevelOverride>;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* The bounded projection of `numbering.xml` that list layout resolves against.
|
|
766
|
+
*
|
|
767
|
+
* Projection ONLY — never a mutation or serialization authority. Hostile values are dropped or
|
|
768
|
+
* clamped, and a missing definition resolves to "no list" rather than a guess.
|
|
769
|
+
*/
|
|
770
|
+
interface NumberingIndex {
|
|
771
|
+
readonly abstractNums: ReadonlyMap<string, AbstractNumDefinition>;
|
|
772
|
+
readonly nums: ReadonlyMap<string, NumDefinition>;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Build a numbering index from the root of a numbering part (`w:numbering`).
|
|
776
|
+
*
|
|
777
|
+
* Empty / missing roots yield an empty index. Duplicate ids keep the first definition.
|
|
778
|
+
*/
|
|
779
|
+
/**
|
|
780
|
+
* Project `numbering.xml` into the bounded index.
|
|
781
|
+
*
|
|
782
|
+
* Every ceiling here exists because the input is file-derived: definition counts, override
|
|
783
|
+
* counts, indent magnitudes and style-link hop depth are all capped, and nothing from the file
|
|
784
|
+
* becomes a loop bound or an allocation size.
|
|
785
|
+
*/
|
|
786
|
+
declare function buildNumberingIndex(root: OoxmlElement | null | undefined): NumberingIndex;
|
|
787
|
+
/** Resolve the effective level for a `numId` + `ilvl`, applying overrides. */
|
|
788
|
+
/**
|
|
789
|
+
* The effective level for one paragraph's numbering reference, after overrides and style links.
|
|
790
|
+
*
|
|
791
|
+
* Answers null for a reference the index cannot resolve — a paragraph naming a definition the
|
|
792
|
+
* file never declared is an unnumbered paragraph, not an error.
|
|
793
|
+
*/
|
|
794
|
+
declare function resolveNumberingLevel(index: NumberingIndex, numId: string, ilvl: number): {
|
|
795
|
+
readonly abstractNumId: string;
|
|
796
|
+
readonly level: NumberingLevel;
|
|
797
|
+
readonly startOverride?: number;
|
|
798
|
+
} | null;
|
|
799
|
+
/** Empty index for tests / documents without numbering. */
|
|
800
|
+
declare const EMPTY_NUMBERING_INDEX: NumberingIndex;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* A paragraph's list membership fully resolved: definition, level, marker text and geometry.
|
|
804
|
+
*
|
|
805
|
+
* `markerText` is already expanded through the counter state, so it is the string a reader sees
|
|
806
|
+
* rather than the `w:lvlText` template.
|
|
807
|
+
*/
|
|
808
|
+
interface ResolvedListItem {
|
|
809
|
+
readonly numId: string;
|
|
810
|
+
readonly ilvl: number;
|
|
811
|
+
readonly abstractNumId: string;
|
|
812
|
+
/** `w:numFmt` of the resolved level — `bullet` or a numbering format. */
|
|
813
|
+
readonly numFmt: string;
|
|
814
|
+
readonly markerText: string;
|
|
815
|
+
readonly markerAlign: ListMarkerAlign;
|
|
816
|
+
readonly suffix: ListSuffix;
|
|
817
|
+
/** Effective indent after merging level + paragraph indents, in points. */
|
|
818
|
+
readonly indent: NumberingLevelIndent;
|
|
819
|
+
readonly markerStyle: ResolvedRunStyle;
|
|
820
|
+
/** Fingerprint for layout cache keys (indent + level identity, not ordinal). */
|
|
821
|
+
readonly cacheToken: string;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Read `w:numPr` from cascaded paragraph-property nodes (last wins).
|
|
825
|
+
*
|
|
826
|
+
* Flat `OoxmlProperty[]` bags drop nested `ilvl`/`numId`, so this walks the tree nodes
|
|
827
|
+
* the same way borders and tabs do.
|
|
828
|
+
*/
|
|
829
|
+
declare function readNumPr(paragraphPropertyNodes: readonly OoxmlNode[]): {
|
|
830
|
+
numId: string;
|
|
831
|
+
ilvl: number;
|
|
832
|
+
} | null;
|
|
833
|
+
/**
|
|
834
|
+
* Resolve `w:numStyleLink` delegation using the document's styles (§17.9.21).
|
|
835
|
+
*
|
|
836
|
+
* Without a style table there is nothing to follow, so the index is returned unchanged —
|
|
837
|
+
* and so it is when nothing delegates, which keeps layout cache identity.
|
|
838
|
+
*/
|
|
839
|
+
declare function withNumberingStyleLinks(index: NumberingIndex, styleCascade: StyleCascadeTable | undefined): NumberingIndex;
|
|
840
|
+
/**
|
|
841
|
+
* The effective indent of a list paragraph: STYLE, then the numbering LEVEL, then DIRECT.
|
|
842
|
+
*
|
|
843
|
+
* Word applies a level's `w:pPr/w:ind` between the paragraph style and the paragraph's own
|
|
844
|
+
* formatting, per attribute — and the ordering matters on real documents. A converted
|
|
845
|
+
* agreement numbers its `(a)` items with a level stating `left=1512 hanging=738` under a
|
|
846
|
+
* `ListParagraph` style stating `left=775 hanging=624`, and states only `hanging="737"` on
|
|
847
|
+
* the paragraph itself. Reading the flattened cascade as "the paragraph's indent" gave the
|
|
848
|
+
* STYLE's 775 to a level that had overridden it, so every lettered sub-item hung a full
|
|
849
|
+
* indent step to the left of where Word puts it.
|
|
850
|
+
*
|
|
851
|
+
* `inherited` is the cascade WITHOUT the paragraph's own `w:pPr` (defaults, table cell style,
|
|
852
|
+
* style chain); `direct` is that `w:pPr` alone.
|
|
853
|
+
*/
|
|
854
|
+
declare function mergeListIndent(levelIndent: NumberingLevelIndent, inherited: readonly OoxmlProperty[], direct?: readonly OoxmlProperty[]): NumberingLevelIndent;
|
|
855
|
+
/**
|
|
856
|
+
* Collect paragraphs of a block list in document order, descending into tables.
|
|
857
|
+
*
|
|
858
|
+
* Caps nesting so a hostile nested-table document cannot recurse without bound.
|
|
859
|
+
*/
|
|
860
|
+
declare function walkStoryParagraphs(blocks: readonly OoxmlElement[], maxTableDepth?: number): OoxmlElement[];
|
|
861
|
+
/**
|
|
862
|
+
* Resolve every list paragraph in a story to a {@link ResolvedListItem}, keyed by node id.
|
|
863
|
+
*
|
|
864
|
+
* Non-list paragraphs are absent from the map. Hostile / missing numbering resolves inertly
|
|
865
|
+
* (paragraph omitted — laid out as ordinary text).
|
|
866
|
+
*/
|
|
867
|
+
declare function resolveStoryListItems(blocks: readonly OoxmlElement[], index: NumberingIndex, styleCascade: StyleCascadeTable | undefined, isFontAvailable?: (family: string) => boolean): ReadonlyMap<string, ResolvedListItem>;
|
|
868
|
+
/**
|
|
869
|
+
* Attach a full-story list-item map to layout options.
|
|
870
|
+
*
|
|
871
|
+
* Resolves once over `blocks` (body story including table cells) so counters continue across
|
|
872
|
+
* section boundaries. No-ops when numbering is absent.
|
|
873
|
+
*/
|
|
874
|
+
declare function withResolvedListItems<T extends {
|
|
875
|
+
readonly numberingIndex?: NumberingIndex;
|
|
876
|
+
readonly listItems?: ReadonlyMap<string, ResolvedListItem>;
|
|
877
|
+
readonly styleCascade?: StyleCascadeTable;
|
|
878
|
+
/**
|
|
879
|
+
* Host oracle for "is this font family really loaded". Supplied, a Symbol/Wingdings
|
|
880
|
+
* bullet keeps the file's own private-use codepoint so the authored typeface draws it;
|
|
881
|
+
* absent, it falls back to the Unicode equivalent rather than a tofu box.
|
|
882
|
+
*/
|
|
883
|
+
readonly isFontAvailable?: (family: string) => boolean;
|
|
884
|
+
}>(options: T, blocks: readonly OoxmlElement[]): T & {
|
|
885
|
+
readonly numberingIndex: NumberingIndex;
|
|
886
|
+
readonly listItems?: ReadonlyMap<string, ResolvedListItem>;
|
|
887
|
+
};
|
|
888
|
+
/**
|
|
889
|
+
* Horizontal marker box inside the hanging indent slot.
|
|
890
|
+
*
|
|
891
|
+
* Coordinates are relative to the same origin as paragraph content (`indent.left` is the
|
|
892
|
+
* text start). Returns null when there is nothing to paint.
|
|
893
|
+
*/
|
|
894
|
+
declare function listMarkerBox(item: ResolvedListItem, markerWidth: number, lineY: number, lineHeight: number): {
|
|
895
|
+
x: number;
|
|
896
|
+
y: number;
|
|
897
|
+
width: number;
|
|
898
|
+
height: number;
|
|
899
|
+
} | null;
|
|
900
|
+
|
|
901
|
+
/** Soft ceiling on `basedOn` chain length — enough for real templates, refuses hostile graphs. */
|
|
902
|
+
declare const MAX_STYLE_BASED_ON_DEPTH = 32;
|
|
903
|
+
/** Soft ceiling on style definitions read from one styles part. */
|
|
904
|
+
declare const MAX_STYLE_DEFINITIONS = 4096;
|
|
905
|
+
/** One `w:style` as the cascade reads it: its properties, and the style it is based on. */
|
|
906
|
+
interface StyleDefinition {
|
|
907
|
+
readonly styleId: string;
|
|
908
|
+
readonly type: string;
|
|
909
|
+
readonly basedOn: string | null;
|
|
910
|
+
readonly paragraphProperties: readonly OoxmlProperty[];
|
|
911
|
+
readonly runProperties: readonly OoxmlProperty[];
|
|
912
|
+
/** The style's `w:pPr` node, when present — needed for nested `w:pBdr`. */
|
|
913
|
+
readonly paragraphPropertiesNode: OoxmlElement | undefined;
|
|
914
|
+
/** The style's `w:tblPr`, for a `w:type="table"` style. */
|
|
915
|
+
readonly tablePropertiesNode: OoxmlElement | undefined;
|
|
916
|
+
/**
|
|
917
|
+
* `w:tblStylePr` conditional formats by `w:type` (`firstRow`, `band1Horz`, …).
|
|
918
|
+
*
|
|
919
|
+
* Word puts a table's real appearance here: `Table Grid` carries its grid in the style's
|
|
920
|
+
* `w:tblBorders`, and the banded/header looks live in these. A document states only
|
|
921
|
+
* `<w:tblStyle w:val="TableGrid"/>`.
|
|
922
|
+
*/
|
|
923
|
+
readonly conditionalTableFormats: ReadonlyMap<string, OoxmlElement>;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* The whole styles part, indexed and ready to resolve against.
|
|
927
|
+
*
|
|
928
|
+
* `cacheToken` is load-bearing: it folds into layout cache producers so breaks measured under one
|
|
929
|
+
* styles part are never reused under another.
|
|
930
|
+
*/
|
|
931
|
+
interface StyleCascadeTable {
|
|
932
|
+
/**
|
|
933
|
+
* Bounded fingerprint folded into layout cache producers so a different styles part cannot
|
|
934
|
+
* reuse breaks measured under another cascade. Computed once per table (FNV-1a hex).
|
|
935
|
+
*/
|
|
936
|
+
readonly cacheToken: string;
|
|
937
|
+
readonly docDefaultsRun: readonly OoxmlProperty[];
|
|
938
|
+
readonly docDefaultsParagraph: readonly OoxmlProperty[];
|
|
939
|
+
readonly docDefaultsParagraphNode: OoxmlElement | undefined;
|
|
940
|
+
/** `w:style[@w:default='1'][@w:type='paragraph']` — last wins among defaults of that type. */
|
|
941
|
+
readonly defaultParagraphStyleId: string | null;
|
|
942
|
+
/** `w:style[@w:default='1'][@w:type='character']` — last wins among defaults of that type. */
|
|
943
|
+
readonly defaultCharacterStyleId: string | null;
|
|
944
|
+
/**
|
|
945
|
+
* The theme part's Latin typefaces, for `w:rFonts` theme references.
|
|
946
|
+
*
|
|
947
|
+
* Lives on the cascade because it is document-level style material with the same
|
|
948
|
+
* lifetime, and because every site that resolves run properties already holds this table.
|
|
949
|
+
*/
|
|
950
|
+
readonly themeFonts: ThemeFonts;
|
|
951
|
+
readonly styles: ReadonlyMap<string, StyleDefinition>;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* A paragraph's properties after the cascade, plus the same list WITHOUT its own `w:pPr`.
|
|
955
|
+
*
|
|
956
|
+
* Both, because a writer needs to know what a paragraph INHERITS to decide whether setting a
|
|
957
|
+
* value is a change or a no-op — and writing back an inherited value freezes it into the
|
|
958
|
+
* paragraph as though the author had chosen it.
|
|
959
|
+
*/
|
|
960
|
+
interface CascadedParagraphFormatting {
|
|
961
|
+
/** Flat paragraph properties in cascade order (defaults → bases → style → direct). */
|
|
962
|
+
readonly paragraphProperties: readonly OoxmlProperty[];
|
|
963
|
+
/**
|
|
964
|
+
* The same list WITHOUT the paragraph's own `w:pPr` — everything it inherits.
|
|
965
|
+
*
|
|
966
|
+
* Numbering needs the two tiers apart: a level's `w:pPr/w:ind` outranks the style's and is
|
|
967
|
+
* outranked by the paragraph's own, and a flattened list cannot say which is which.
|
|
968
|
+
*/
|
|
969
|
+
readonly inheritedParagraphProperties: readonly OoxmlProperty[];
|
|
970
|
+
/** Matching `w:pPr` nodes for nested border resolution. */
|
|
971
|
+
readonly paragraphPropertyNodes: readonly OoxmlNode[];
|
|
972
|
+
/** Inherited run properties for every run in the paragraph (before direct run `rPr`). */
|
|
973
|
+
readonly runProperties: readonly OoxmlProperty[];
|
|
974
|
+
/**
|
|
975
|
+
* The style this paragraph resolved to, or null when it names none and there is no
|
|
976
|
+
* document default. `w:contextualSpacing` compares neighbours by exactly this.
|
|
977
|
+
*/
|
|
978
|
+
readonly styleId: string | null;
|
|
979
|
+
}
|
|
980
|
+
/** Accepted style ids only — over-long, control-bearing, or dangerous keys are dropped. */
|
|
981
|
+
declare function isValidStyleId(raw: string | undefined): raw is string;
|
|
982
|
+
/**
|
|
983
|
+
* What a table style contributes to the paragraphs of ONE cell: the style's whole-table
|
|
984
|
+
* `w:pPr`/`w:rPr` followed by every `w:tblStylePr` the cell is under (17.7.6.6), weakest
|
|
985
|
+
* first in the caller's condition order (banding, column, row, corner).
|
|
986
|
+
*
|
|
987
|
+
* This is how Word makes a header row bold and centred while the document states nothing
|
|
988
|
+
* but `<w:tblStyle w:val="…"/>` on the table and plain runs in the cells.
|
|
989
|
+
*/
|
|
990
|
+
interface TableCellStyleFormatting {
|
|
991
|
+
readonly paragraphProperties: readonly OoxmlProperty[];
|
|
992
|
+
/** Matching `w:pPr` nodes, for nested `w:pBdr` / `w:tabs` resolution. */
|
|
993
|
+
readonly paragraphPropertyNodes: readonly OoxmlElement[];
|
|
994
|
+
/** Inherited run properties for every run in the cell, before the paragraph style. */
|
|
995
|
+
readonly runProperties: readonly OoxmlProperty[];
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Build a cascade table from a styles part root.
|
|
999
|
+
*
|
|
1000
|
+
* Only direct `w:style` children of the root participate (bounded count). Duplicate
|
|
1001
|
+
* `styleId` values keep the last definition, matching Word's reader for this fixture class.
|
|
1002
|
+
* Default paragraph/character style ids track `w:default="1"` with the same last-wins rule.
|
|
1003
|
+
*/
|
|
1004
|
+
declare function buildStyleCascadeTable(stylesRoot: OoxmlElement | null, themeFonts?: ThemeFonts): StyleCascadeTable;
|
|
1005
|
+
/**
|
|
1006
|
+
* Cascade paragraph + inherited run properties for one paragraph's direct `w:pPr`.
|
|
1007
|
+
*
|
|
1008
|
+
* Order: `docDefaults` → table style → `basedOn` ancestors → paragraph style → direct
|
|
1009
|
+
* formatting, which is the style hierarchy of 17.7.2: a table style sits above the document
|
|
1010
|
+
* defaults and below the paragraph style a cell paragraph names for itself.
|
|
1011
|
+
* When `w:pStyle` is absent, the document's default paragraph style (`w:default="1"`) is used.
|
|
1012
|
+
* Direct formatting is last so it overrides inherited values inside the existing resolvers.
|
|
1013
|
+
*/
|
|
1014
|
+
declare function cascadeParagraphFormatting(table: StyleCascadeTable, directPPr: OoxmlNode | undefined, tableCellStyle?: TableCellStyleFormatting): CascadedParagraphFormatting;
|
|
1015
|
+
/**
|
|
1016
|
+
* Bottom border after cascade: a later `w:pBdr` replaces an earlier one; absence inherits.
|
|
1017
|
+
* `nil`/`none` clear the edge via `paragraphBorders`.
|
|
1018
|
+
*/
|
|
1019
|
+
declare function cascadedBottomBorder(paragraphPropertyNodes: readonly OoxmlNode[]): ParagraphBorderEdge | undefined;
|
|
1020
|
+
/**
|
|
1021
|
+
* Merge inherited paragraph-style run props with a run's direct `rPr` (direct last).
|
|
1022
|
+
*
|
|
1023
|
+
* When a cascade table is supplied, also resolves `w:rStyle` character styles (basedOn chain,
|
|
1024
|
+
* cycle/depth capped). Runs without an explicit `rStyle` pick up the default character style
|
|
1025
|
+
* (`w:default="1"`). Precedence: inherited → character style chain → direct formatting.
|
|
1026
|
+
*/
|
|
1027
|
+
declare function cascadeRunProperties(inheritedRunProperties: readonly OoxmlProperty[], directRunProperties: readonly OoxmlProperty[], table?: StyleCascadeTable): readonly OoxmlProperty[];
|
|
1028
|
+
/** Everything the line breaker needs about one paragraph, already cascaded and converted. */
|
|
1029
|
+
interface ParagraphLayoutInputs {
|
|
1030
|
+
readonly props: OoxmlProperty[];
|
|
1031
|
+
readonly indent: {
|
|
1032
|
+
left: number;
|
|
1033
|
+
right: number;
|
|
1034
|
+
hanging: number;
|
|
1035
|
+
firstLine: number;
|
|
1036
|
+
};
|
|
1037
|
+
readonly available: number;
|
|
1038
|
+
readonly alignment: Alignment;
|
|
1039
|
+
readonly spacing: ParagraphSpacing;
|
|
1040
|
+
/** Resolved `w:line` / `w:lineRule`; single spacing where the cascade says nothing. */
|
|
1041
|
+
readonly lineSpacing: ParagraphLineSpacing;
|
|
1042
|
+
/** `w:contextualSpacing`: drop before/after between same-style neighbours. */
|
|
1043
|
+
readonly contextualSpacing: boolean;
|
|
1044
|
+
/** Resolved paragraph style id, for the `w:contextualSpacing` neighbour comparison. */
|
|
1045
|
+
readonly styleId: string | null;
|
|
1046
|
+
readonly bottomBorder: ParagraphBorderEdge | undefined;
|
|
1047
|
+
/**
|
|
1048
|
+
* Every `CT_PBdr` edge after cascade, not just the bottom one.
|
|
1049
|
+
*
|
|
1050
|
+
* `bottomBorder` stays alongside it because the fragment signature and the table flow
|
|
1051
|
+
* read that field by name; this is the whole box, so a cell paragraph gets the same
|
|
1052
|
+
* frame a body paragraph does.
|
|
1053
|
+
*/
|
|
1054
|
+
readonly borders: ParagraphBorders;
|
|
1055
|
+
/** Validated 6-hex paragraph shading fill from cascaded `w:pPr/w:shd`, absent for none. */
|
|
1056
|
+
readonly shading: string | undefined;
|
|
1057
|
+
readonly inheritedRunProperties: readonly OoxmlProperty[];
|
|
1058
|
+
/** Cascaded custom tab stops + default interval for paragraph-flow breaking. */
|
|
1059
|
+
readonly tabStops: ResolvedTabStops;
|
|
1060
|
+
/**
|
|
1061
|
+
* Fingerprint folded into the paragraph layout cache key — nested `w:tabs` are absent
|
|
1062
|
+
* from flat property bags, so style-inherited stops must be named explicitly.
|
|
1063
|
+
*/
|
|
1064
|
+
readonly tabStopsCacheToken: string;
|
|
1065
|
+
/** Resolved list marker inputs when the paragraph participates in numbering. */
|
|
1066
|
+
readonly listItem?: ResolvedListItem;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Resolve every paragraph input semantic layout / table cells share: cascaded props when a
|
|
1070
|
+
* style table is present, otherwise direct formatting only.
|
|
1071
|
+
*
|
|
1072
|
+
* When `listItem` is provided, its merged level indent becomes the paragraph indent (list
|
|
1073
|
+
* hanging / left from `numbering.xml`), which is what Word uses for fixture list paragraphs
|
|
1074
|
+
* that author no direct `w:ind`.
|
|
1075
|
+
*
|
|
1076
|
+
* `tableCellStyle` carries what the enclosing table's style says about this cell's
|
|
1077
|
+
* paragraphs; body paragraphs pass nothing.
|
|
1078
|
+
*/
|
|
1079
|
+
declare function resolveParagraphLayoutInputs(paragraph: OoxmlElement, contentWidth: number, styleCascade: StyleCascadeTable | undefined, listItem?: ResolvedListItem, tableCellStyle?: TableCellStyleFormatting): ParagraphLayoutInputs;
|
|
1080
|
+
|
|
1081
|
+
/** Hard ceiling on notes laid out in one pass (fail closed beyond). */
|
|
1082
|
+
declare const MAX_NOTES_LAID_OUT = 10000;
|
|
1083
|
+
/** Hard ceiling on fragments emitted for one note (split / continuation). */
|
|
1084
|
+
declare const MAX_NOTE_FRAGMENTS = 512;
|
|
1085
|
+
/**
|
|
1086
|
+
* Why note layout stopped short and fell back.
|
|
1087
|
+
*
|
|
1088
|
+
* Every one is a BOUND rather than a bug: note counts, fragment counts and heights all come from
|
|
1089
|
+
* a file, and a document can ask for more note area than a page has. Falling back with a reason
|
|
1090
|
+
* keeps the document open instead of failing to lay out.
|
|
1091
|
+
*/
|
|
1092
|
+
type NoteLayoutFallbackReason = 'note-count-limit' | 'note-fragment-limit' | 'note-reflow-exhausted' | 'note-height-cap'
|
|
1093
|
+
/** Authored separator/continuationSeparator taller than the content column. */
|
|
1094
|
+
| 'note-separator-height-cap' | 'missing-note-body' | 'dangling-note-reference';
|
|
1095
|
+
/**
|
|
1096
|
+
* One note's body laid out as its own story, in story-relative coordinates.
|
|
1097
|
+
*
|
|
1098
|
+
* Relative rather than page-absolute because a note moves between pages during pagination — the
|
|
1099
|
+
* page it lands on is decided after its content is measured.
|
|
1100
|
+
*/
|
|
1101
|
+
interface NoteStoryLayout {
|
|
1102
|
+
readonly noteKind: NoteKind;
|
|
1103
|
+
readonly noteId: number;
|
|
1104
|
+
/** `footnote:N` / `endnote:N` — matches EditorScope note id encoding. */
|
|
1105
|
+
readonly scopeId: string;
|
|
1106
|
+
readonly noteType: ReturnType<typeof noteTypeOf>;
|
|
1107
|
+
/** Story-relative fragments; origin at the story box's top-left. */
|
|
1108
|
+
readonly fragments: readonly BlockFragmentRecord[];
|
|
1109
|
+
/** Height the blocks flow to (points). */
|
|
1110
|
+
readonly flowHeight: number;
|
|
1111
|
+
/** True when layout hit a named bound and returned a truncated / empty story. */
|
|
1112
|
+
readonly fallbackReason?: NoteLayoutFallbackReason;
|
|
1113
|
+
}
|
|
1114
|
+
/** Paint style for Word-default / marker-only separator rules (not CSS inventing content). */
|
|
1115
|
+
type NoteSeparatorRuleStyle = 'single' | 'double';
|
|
1116
|
+
/**
|
|
1117
|
+
* The rule between body text and the note area.
|
|
1118
|
+
*
|
|
1119
|
+
* Synthesized when the document declares none, because Word draws one regardless — a document
|
|
1120
|
+
* without an authored separator still shows the line a reader expects.
|
|
1121
|
+
*/
|
|
1122
|
+
interface NoteSeparatorLayout {
|
|
1123
|
+
readonly kind: 'separator' | 'continuationSeparator';
|
|
1124
|
+
readonly fragments: readonly BlockFragmentRecord[];
|
|
1125
|
+
readonly flowHeight: number;
|
|
1126
|
+
/** True when the engine synthesized a default rule (document had none). */
|
|
1127
|
+
readonly synthetic: boolean;
|
|
1128
|
+
/**
|
|
1129
|
+
* Layout-owned rule when the separator is marker-only (`w:separator` /
|
|
1130
|
+
* `w:continuationSeparator`) or fully synthetic. Absent when an authored separator
|
|
1131
|
+
* story has real paragraph/run/border content that paint should render as fragments.
|
|
1132
|
+
*/
|
|
1133
|
+
readonly ruleStyle?: NoteSeparatorRuleStyle;
|
|
1134
|
+
/** Set when an oversize authored separator was replaced with a synthetic rule. */
|
|
1135
|
+
readonly fallbackReason?: NoteLayoutFallbackReason;
|
|
1136
|
+
}
|
|
1137
|
+
interface LayoutNoteStoryOptions {
|
|
1138
|
+
readonly measurer: TextMeasurer;
|
|
1139
|
+
readonly producer: string;
|
|
1140
|
+
readonly cache?: ParagraphLayoutCache<readonly PendingLine[]>;
|
|
1141
|
+
readonly styleCascade?: StyleCascadeTable;
|
|
1142
|
+
readonly defaultTabStopPt?: number;
|
|
1143
|
+
/** Derived display marks for noteRef projection inside the note body. */
|
|
1144
|
+
readonly noteMarks?: NoteMarkContext;
|
|
1145
|
+
/**
|
|
1146
|
+
* Cap on flow height for a single note story (points). Hostile notes must not allocate
|
|
1147
|
+
* unbounded page fragments; overflow is split by the pagination layer, not here.
|
|
1148
|
+
*/
|
|
1149
|
+
readonly maxFlowHeightPt?: number;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Stable line-id namespace for one note. Body line counters compare these as opaque strings
|
|
1153
|
+
* and must not collide with `line-N` / `hf-…` ids.
|
|
1154
|
+
*/
|
|
1155
|
+
declare function noteLineIdPrefix(noteKind: NoteKind, noteId: number): string;
|
|
1156
|
+
/** Collect normal (body) notes from a notes part, bounded. */
|
|
1157
|
+
declare function normalNotesOf(part: OoxmlPart | null | undefined): readonly OoxmlElement[];
|
|
1158
|
+
/** Find separator / continuationSeparator note body in a notes part. */
|
|
1159
|
+
declare function findSeparatorNote(part: OoxmlPart | null | undefined, kind: 'separator' | 'continuationSeparator'): OoxmlElement | undefined;
|
|
1160
|
+
/**
|
|
1161
|
+
* Lay one note node out at `contentWidth`.
|
|
1162
|
+
*
|
|
1163
|
+
* Does not paginate. Callers that need splits ask for fragments and cut at paragraph/line
|
|
1164
|
+
* boundaries in the note-pagination layer.
|
|
1165
|
+
*/
|
|
1166
|
+
declare function layoutNoteStory(note: OoxmlNode, contentWidth: number, options: LayoutNoteStoryOptions): NoteStoryLayout | null;
|
|
1167
|
+
/** Layout a note by id from a notes part; null when missing. */
|
|
1168
|
+
declare function layoutNoteById(part: OoxmlPart | null | undefined, noteId: number, contentWidth: number, options: LayoutNoteStoryOptions): NoteStoryLayout | null;
|
|
1169
|
+
/**
|
|
1170
|
+
* Word-default paint style for a separator marker.
|
|
1171
|
+
*
|
|
1172
|
+
* Footnote and endnote separators both use a short single rule. A full-width double
|
|
1173
|
+
* border on a body heading (e.g. the comprehensive fixture’s end banner) is ordinary
|
|
1174
|
+
* paragraph `w:pBdr` ownership and must not transfer onto the note separator record.
|
|
1175
|
+
* Authored separator stories with real paragraph/run/border content bypass this via
|
|
1176
|
+
* fragment paint.
|
|
1177
|
+
*/
|
|
1178
|
+
declare function defaultNoteSeparatorRuleStyle(_noteKind: NoteKind, _kind: 'separator' | 'continuationSeparator'): NoteSeparatorRuleStyle;
|
|
1179
|
+
/**
|
|
1180
|
+
* True when a separator note contains only OOXML separator markers (and empty noteRef
|
|
1181
|
+
* atoms Word often authors beside them) — no measurable text or paragraph borders.
|
|
1182
|
+
*/
|
|
1183
|
+
declare function isMarkerOnlySeparatorNote(note: OoxmlNode): boolean;
|
|
1184
|
+
/**
|
|
1185
|
+
* Layout the document's separator note, or synthesize a short horizontal rule.
|
|
1186
|
+
*
|
|
1187
|
+
* Marker-only / missing separators emit no paragraph fragments — paint draws the rule
|
|
1188
|
+
* from {@link NoteSeparatorLayout.ruleStyle} + box geometry. Authored separators with
|
|
1189
|
+
* real paragraph/run/border content keep their fragment story (including `w:pBdr`).
|
|
1190
|
+
*
|
|
1191
|
+
* When `maxFlowHeightPt` is set and an authored separator exceeds it, the engine fails
|
|
1192
|
+
* closed to a short synthetic rule ({@link note-separator-height-cap}) so note pagination
|
|
1193
|
+
* cannot burn the overflow budget on zero-progress separator-only pages.
|
|
1194
|
+
*/
|
|
1195
|
+
declare function layoutNoteSeparator(part: OoxmlPart | null | undefined, kind: 'separator' | 'continuationSeparator', contentWidth: number, options: LayoutNoteStoryOptions, noteKind: NoteKind, maxFlowHeightPt?: number): NoteSeparatorLayout;
|
|
1196
|
+
/** Default rule geometry for a synthetic / marker-only separator, story-relative. */
|
|
1197
|
+
declare function syntheticSeparatorBox(contentWidth: number, flowHeight: number): LayoutBox;
|
|
1198
|
+
/** Absolute separator box: short rule for marker/synthetic, full width for authored stories. */
|
|
1199
|
+
declare function noteSeparatorAreaBox(separator: NoteSeparatorLayout, contentX: number, contentWidth: number, areaTop: number): LayoutBox;
|
|
1200
|
+
|
|
1201
|
+
/** Bound on reflow attempts per document layout pass. */
|
|
1202
|
+
declare const MAX_NOTE_REFLOW_ATTEMPTS = 8;
|
|
1203
|
+
/** Cap on empty pages created solely to drain footnote/endnote overflow. */
|
|
1204
|
+
declare const MAX_NOTE_OVERFLOW_PAGES = 256;
|
|
1205
|
+
/**
|
|
1206
|
+
* Cap on synthetic eachPage mark candidates measured per section (plus actual marks).
|
|
1207
|
+
*
|
|
1208
|
+
* eachPage sequences restart every page, so a page almost never carries more than a
|
|
1209
|
+
* handful of auto-numbered notes. Measuring `numStart .. numStart + N - 1` covers
|
|
1210
|
+
* single→double digit decimal growth and typical roman width peaks (e.g. `viii` vs `ix`)
|
|
1211
|
+
* without scanning hostile `numStart` ranges unboundedly. Derived marks already assigned
|
|
1212
|
+
* for the pass are always included in addition to this window.
|
|
1213
|
+
*/
|
|
1214
|
+
declare const MAX_EACH_PAGE_MARK_CANDIDATES = 12;
|
|
1215
|
+
/**
|
|
1216
|
+
* Why note PAGINATION fell back, widening {@link NoteLayoutFallbackReason} with the reasons that
|
|
1217
|
+
* only arise while distributing notes across pages.
|
|
1218
|
+
*/
|
|
1219
|
+
type NotePaginationFallbackReason = NoteLayoutFallbackReason | 'note-reflow-exhausted' | 'note-area-fragment-limit' | 'note-overflow-page-limit'
|
|
1220
|
+
/**
|
|
1221
|
+
* Overflow/drain iteration placed zero note stories while carry/pending remained —
|
|
1222
|
+
* abort rather than minting blank separator-only sheets up to the page budget.
|
|
1223
|
+
*/
|
|
1224
|
+
| 'note-overflow-stalled'
|
|
1225
|
+
/** A single note line exceeds the full content column; content is not placed overflowing. */
|
|
1226
|
+
| 'note-line-exceeds-page';
|
|
1227
|
+
/**
|
|
1228
|
+
* Everything note pagination needs: the note parts, and the per-section properties governing
|
|
1229
|
+
* them.
|
|
1230
|
+
*
|
|
1231
|
+
* Per-SECTION because numbering, restart rules and placement are all section properties — one
|
|
1232
|
+
* document can restart footnote numbering at every section and end notes at the document end.
|
|
1233
|
+
*/
|
|
1234
|
+
interface NotesLayoutInput {
|
|
1235
|
+
readonly footnotesPart: OoxmlPart | null;
|
|
1236
|
+
readonly endnotesPart: OoxmlPart | null;
|
|
1237
|
+
/** Per-section resolved footnote properties (index-aligned with document sections). */
|
|
1238
|
+
readonly footnotePropsBySection: readonly ResolvedFootnoteProperties[];
|
|
1239
|
+
/** Per-section resolved endnote properties. */
|
|
1240
|
+
readonly endnotePropsBySection: readonly ResolvedEndnoteProperties[];
|
|
1241
|
+
/** Document-level defaults (section 0 fallback). */
|
|
1242
|
+
readonly documentFootnoteProps: ResolvedFootnoteProperties;
|
|
1243
|
+
readonly documentEndnoteProps: ResolvedEndnoteProperties;
|
|
1244
|
+
readonly measurer: TextMeasurer;
|
|
1245
|
+
readonly producer: string;
|
|
1246
|
+
readonly cache?: ParagraphLayoutCache<readonly PendingLine[]>;
|
|
1247
|
+
readonly styleCascade?: StyleCascadeTable;
|
|
1248
|
+
readonly defaultTabStopPt?: number;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* The layout with notes attached, plus any fallbacks taken and the mark context used.
|
|
1252
|
+
*
|
|
1253
|
+
* The marks come back because they feed the body's incremental cache tokens: a note number that
|
|
1254
|
+
* changed must invalidate the paragraph that references it.
|
|
1255
|
+
*/
|
|
1256
|
+
interface NotesAttachResult {
|
|
1257
|
+
readonly layout: SemanticLayout;
|
|
1258
|
+
readonly fallbackReasons: readonly NotePaginationFallbackReason[];
|
|
1259
|
+
/** Mark context used for the final body projection (for incremental cache tokens). */
|
|
1260
|
+
readonly noteMarks: NoteMarkContext;
|
|
1261
|
+
}
|
|
1262
|
+
interface PageRefHit {
|
|
1263
|
+
readonly noteKind: NoteKind;
|
|
1264
|
+
readonly noteId: number;
|
|
1265
|
+
readonly paragraphId: string;
|
|
1266
|
+
/** Canonical UTF-16 atom offset within the paragraph. */
|
|
1267
|
+
readonly atomOffset: number;
|
|
1268
|
+
readonly customMarkFollows: boolean;
|
|
1269
|
+
readonly sectionIndex: number;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* Whether a paragraph fragment owns a note atom at `atomOffset`.
|
|
1274
|
+
*
|
|
1275
|
+
* Fragment ranges are half-open for content ownership: `[start, end)`. The shared
|
|
1276
|
+
* boundary offset belongs to the later fragment (downstream affinity), matching line
|
|
1277
|
+
* splits where `fragmentStart = previous.range.end`.
|
|
1278
|
+
*/
|
|
1279
|
+
declare function fragmentOwnsAtomOffset(fragment: ParagraphFragmentRecord, atomOffset: number): boolean;
|
|
1280
|
+
/** Paragraph-id → refs index for linear {@link filterRefsOnPage} over a layout pass. */
|
|
1281
|
+
type PageRefIndex = ReadonlyMap<string, readonly PageRefHit[]>;
|
|
1282
|
+
/** Build a reusable paragraph-id index (document order preserved per paragraph). */
|
|
1283
|
+
declare function buildPageRefIndex(allRefs: readonly PageRefHit[]): PageRefIndex;
|
|
1284
|
+
/**
|
|
1285
|
+
* Collect note references that appear in laid-out body fragments on a page.
|
|
1286
|
+
* Matches {@link ParagraphFragmentRecord.range} ownership (half-open + boundary affinity).
|
|
1287
|
+
*
|
|
1288
|
+
* Pass {@link buildPageRefIndex} result as `refIndex` for O(fragments + matching refs)
|
|
1289
|
+
* instead of scanning every document ref against every page fragment.
|
|
1290
|
+
*/
|
|
1291
|
+
declare function filterRefsOnPage(page: PageRecord, allRefs: readonly PageRefHit[], refIndex?: PageRefIndex): readonly PageRefHit[];
|
|
1292
|
+
/**
|
|
1293
|
+
* Compute per-page bottom reserves (points) needed for footnotes given a provisional layout.
|
|
1294
|
+
* Used by the bounded reflow loop before final attach.
|
|
1295
|
+
*
|
|
1296
|
+
* Height is measured against a column-derived note budget (not leftover body slack). Measuring
|
|
1297
|
+
* from slack makes `stable` true on the first pass and never shrinks the body — references and
|
|
1298
|
+
* notes then compete for the same band. Oversized notes still split/continue within the budget;
|
|
1299
|
+
* {@link MIN_FOOTNOTE_BODY_BAND_PT} keeps a body band so reflow cannot chase blank sheets.
|
|
1300
|
+
*/
|
|
1301
|
+
declare function computeFootnoteReserves(layout: SemanticLayout, allRefs: readonly PageRefHit[], input: NotesLayoutInput, noteMarks: NoteMarkContext): {
|
|
1302
|
+
readonly reserves: ReadonlyMap<number, number>;
|
|
1303
|
+
readonly stable: boolean;
|
|
1304
|
+
readonly reasons: readonly NotePaginationFallbackReason[];
|
|
1305
|
+
};
|
|
1306
|
+
/**
|
|
1307
|
+
* Attach footnote/endnote areas onto a body layout. Does not re-paginate — callers that
|
|
1308
|
+
* need reservation must re-run body layout with {@link pageBottomReserves} first.
|
|
1309
|
+
*/
|
|
1310
|
+
declare function attachNotesToLayout(layout: SemanticLayout, allRefs: readonly PageRefHit[], input: NotesLayoutInput, options?: {
|
|
1311
|
+
readonly fallbackReasons?: readonly NotePaginationFallbackReason[];
|
|
1312
|
+
readonly paragraphSectionIndex?: ReadonlyMap<string, number>;
|
|
1313
|
+
}): NotesAttachResult;
|
|
1314
|
+
/**
|
|
1315
|
+
* Build a continuous (pre-page) mark context for the first body layout pass.
|
|
1316
|
+
* eachPage reserves digit width; {@link reprojectBodyNoteMarks} publishes final marks
|
|
1317
|
+
* onto body citations after page assignment in {@link attachNotesToLayout}.
|
|
1318
|
+
*/
|
|
1319
|
+
declare function provisionalNoteMarks(refs: readonly PageRefHit[], input: NotesLayoutInput): NoteMarkContext;
|
|
1320
|
+
|
|
1321
|
+
/** Page geometry for header/footer anchored frame resolution (story-relative layout space). */
|
|
1322
|
+
interface HeaderFooterPageContext {
|
|
1323
|
+
readonly pageNumber: number;
|
|
1324
|
+
readonly pageWidth: number;
|
|
1325
|
+
readonly pageHeight: number;
|
|
1326
|
+
readonly marginLeft: number;
|
|
1327
|
+
readonly marginRight: number;
|
|
1328
|
+
readonly marginTop: number;
|
|
1329
|
+
readonly marginBottom: number;
|
|
1330
|
+
}
|
|
1331
|
+
interface HeaderFooterStoryLayout {
|
|
1332
|
+
readonly partName: string;
|
|
1333
|
+
/** Main-document relationship id when the furniture source knows it. */
|
|
1334
|
+
readonly rId?: string;
|
|
1335
|
+
/**
|
|
1336
|
+
* Bounded identity of the story's canonical OOXML content.
|
|
1337
|
+
*
|
|
1338
|
+
* Furniture cache keys must not rely on {@link flowHeight} alone: equal-height A→B edits
|
|
1339
|
+
* would otherwise reuse stale page furniture. Derived as a 16-hex FNV-1a over the part's
|
|
1340
|
+
* canonical fingerprint — never DOM identity or unbounded raw serialization in the key.
|
|
1341
|
+
* PAGE/NUMPAGES/SECTIONPAGES projection shares this key; page context is cached separately.
|
|
1342
|
+
*/
|
|
1343
|
+
readonly contentKey: string;
|
|
1344
|
+
/** Story-relative fragments; origin at the story box's top-left. */
|
|
1345
|
+
readonly fragments: readonly BlockFragmentRecord[];
|
|
1346
|
+
/** The height the blocks actually flow to — what sizes the box on every page. */
|
|
1347
|
+
readonly flowHeight: number;
|
|
1348
|
+
/**
|
|
1349
|
+
* Allowlisted complex PAGE / NUMPAGES / SECTIONPAGES presence detected for this story.
|
|
1350
|
+
*
|
|
1351
|
+
* Callers use this to skip attaching a page-field projector when the baseline is enough.
|
|
1352
|
+
*/
|
|
1353
|
+
readonly pageFieldNeeds: StoryPageFieldNeeds;
|
|
1354
|
+
/** Anchored drawings owned by this story, in story-relative coordinates. */
|
|
1355
|
+
readonly anchoredDrawings?: readonly AnchoredDrawingRecord[];
|
|
1356
|
+
/**
|
|
1357
|
+
* Re-layout this story under a page-field context.
|
|
1358
|
+
*
|
|
1359
|
+
* Field-free stories return `this`. Count-only stories cache by the counts they read.
|
|
1360
|
+
* PAGE stories cache by the distinct evaluated values (including format) with a bounded LRU.
|
|
1361
|
+
*/
|
|
1362
|
+
readonly withPageContext: (ctx: FieldPageContext) => HeaderFooterStoryLayout;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Lay one header/footer part out at `contentWidth`.
|
|
1366
|
+
*
|
|
1367
|
+
* Line ids are namespaced by part so the body's `line-N` counter — which incremental
|
|
1368
|
+
* convergence compares — never moves because a header changed.
|
|
1369
|
+
*
|
|
1370
|
+
* When `pageContext` is set, allowlisted PAGE/NUMPAGES/SECTIONPAGES instructions project
|
|
1371
|
+
* live values; otherwise those fields contribute only cached result text (often empty).
|
|
1372
|
+
* Field-free stories ignore `pageContext` and share one baseline layout.
|
|
1373
|
+
*
|
|
1374
|
+
* `defaultTabStopPt` is the document's `w:settings/w:defaultTabStop` (ECMA-376 §17.15.1.25)
|
|
1375
|
+
* in points; absent keeps the 0.5" schema default. Furniture tabs on the SAME grid as the
|
|
1376
|
+
* body — a page-number tab in a metric-locale footer belongs on the document's interval, not
|
|
1377
|
+
* on a constant. It sits at the tail because the parameters ahead of it are already
|
|
1378
|
+
* positional; new callers should keep passing `undefined` for what they do not set.
|
|
1379
|
+
*/
|
|
1380
|
+
declare function layoutHeaderFooterStory(part: OoxmlPart, contentWidth: number, measurer: TextMeasurer, producer: string, cache?: ParagraphLayoutCache<readonly PendingLine[]>, styleCascade?: StyleCascadeTable, pageContext?: FieldPageContext, maxPageContextEntries?: number, defaultTabStopPt?: number, displayMode?: RevisionDisplayMode, inlineDrawingLayout?: InlineDrawingLayoutContext, drawingTokenForParagraph?: (paragraph: OoxmlNode) => string, drawingLayoutToken?: string, hfPageContext?: HeaderFooterPageContext): HeaderFooterStoryLayout;
|
|
1381
|
+
|
|
1382
|
+
/** The flow state as it stood immediately before one block was placed. */
|
|
1383
|
+
interface FlowCheckpoint {
|
|
1384
|
+
/** Completed pages at this point. The prefix of the previous layout that still stands. */
|
|
1385
|
+
readonly pageCount: number;
|
|
1386
|
+
/** Fragments already on the page being built. */
|
|
1387
|
+
readonly pageFragments: readonly BlockFragmentRecord[];
|
|
1388
|
+
/** Anchored drawings already collected for the open page. */
|
|
1389
|
+
readonly pendingAnchoredDrawings: readonly AnchoredDrawingRecord[];
|
|
1390
|
+
readonly cursorY: number;
|
|
1391
|
+
readonly lineCounter: number;
|
|
1392
|
+
/** Trailing paragraph spacing participating in adjacent-spacing collapse. */
|
|
1393
|
+
readonly previousSpaceAfter: number;
|
|
1394
|
+
/** Active column index when the section uses multiple columns. */
|
|
1395
|
+
readonly flowColumnIndex: number;
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* What the last layout pass actually did — the observable evidence that incremental layout is
|
|
1399
|
+
* working.
|
|
1400
|
+
*
|
|
1401
|
+
* `reusedPages` and `fullPasses` are the ones that matter: a typing keystroke that rebuilds every
|
|
1402
|
+
* page produces identical output and unusable performance, so the tests assert on these rather
|
|
1403
|
+
* than on the rendered result.
|
|
1404
|
+
*/
|
|
1405
|
+
interface LayoutSessionStats {
|
|
1406
|
+
/** Paragraphs placed by the last pass, against the number in the document. */
|
|
1407
|
+
readonly placed: number;
|
|
1408
|
+
readonly total: number;
|
|
1409
|
+
/** Pages carried over from the previous layout without being rebuilt. */
|
|
1410
|
+
readonly reusedPages: number;
|
|
1411
|
+
/** Passes that could not resume and laid the document out from the top. */
|
|
1412
|
+
readonly fullPasses: number;
|
|
1413
|
+
}
|
|
1414
|
+
/** One section's place on the previous document sheet stack. */
|
|
1415
|
+
interface SectionStackSpan {
|
|
1416
|
+
readonly startIndex: number;
|
|
1417
|
+
readonly pageCount: number;
|
|
1418
|
+
readonly sheetY: number;
|
|
1419
|
+
/** Remapped pages (projectors intact) from the last pass for this section. */
|
|
1420
|
+
readonly remappedPages: readonly PageRecord[];
|
|
1421
|
+
}
|
|
1422
|
+
/** Orchestrator state for multi-section incremental layout. */
|
|
1423
|
+
interface MultiSectionLayoutState {
|
|
1424
|
+
structureKey: string;
|
|
1425
|
+
sections: LayoutSession[];
|
|
1426
|
+
spans: SectionStackSpan[];
|
|
1427
|
+
previousRemapped: readonly PageRecord[];
|
|
1428
|
+
previousFinalized: SemanticLayout | null;
|
|
1429
|
+
previousPageCount: number;
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Carried-over state that makes layout incremental.
|
|
1433
|
+
*
|
|
1434
|
+
* A caller creates one and hands the SAME object back each pass. It holds the previous pages, the
|
|
1435
|
+
* per-block cache keys and the flow checkpoints a pass resumes from, so an edit low in a document
|
|
1436
|
+
* re-lays only what follows it. A no-change pass returns the previous pages by identity.
|
|
1437
|
+
*/
|
|
1438
|
+
interface LayoutSession {
|
|
1439
|
+
/** @internal Mutable across passes; a caller only creates one and passes it back. */
|
|
1440
|
+
previous: SemanticLayout | null;
|
|
1441
|
+
checkpoints: FlowCheckpoint[];
|
|
1442
|
+
keys: string[];
|
|
1443
|
+
/** Geometry and producer of the previous pass; a change to either forces a full pass. */
|
|
1444
|
+
context: string;
|
|
1445
|
+
/**
|
|
1446
|
+
* Line counter after the last block of the previous pass.
|
|
1447
|
+
*
|
|
1448
|
+
* Multi-section orchestration threads a global line counter across sections; early-exit
|
|
1449
|
+
* paths (unchanged / converged) must report this rather than the resume cursor.
|
|
1450
|
+
*/
|
|
1451
|
+
endLineCounter: number;
|
|
1452
|
+
/**
|
|
1453
|
+
* Flow state after the last block of the previous pass, for a section that CONTINUES
|
|
1454
|
+
* onto this one's last sheet (`w:type="continuous"`).
|
|
1455
|
+
*
|
|
1456
|
+
* `endCursorY` is the used height of that sheet's content column; `endSpaceAfter` is the
|
|
1457
|
+
* trailing paragraph spacing still eligible for adjacent-spacing collapse. Reported by
|
|
1458
|
+
* the early-exit paths (unchanged / converged) for the same reason as
|
|
1459
|
+
* {@link endLineCounter}: the resume cursor is not the end of the flow.
|
|
1460
|
+
*/
|
|
1461
|
+
endCursorY: number;
|
|
1462
|
+
endSpaceAfter: number;
|
|
1463
|
+
/** Whether the last page of that pass was still open (no trailing page break). */
|
|
1464
|
+
endsOpenPage: boolean;
|
|
1465
|
+
stats: LayoutSessionStats;
|
|
1466
|
+
/**
|
|
1467
|
+
* Column-height limit chosen by the last balanced multi-column pass, or null when the
|
|
1468
|
+
* last pass did not balance.
|
|
1469
|
+
*
|
|
1470
|
+
* Lets the next pass try the remembered limit FIRST: an unchanged balanced section
|
|
1471
|
+
* early-exits on that single attempt instead of re-running the natural pass and the
|
|
1472
|
+
* whole balance search every time.
|
|
1473
|
+
*/
|
|
1474
|
+
balanceLimit: number | null;
|
|
1475
|
+
/** Present when the last pass was multi-section; child sessions live here. */
|
|
1476
|
+
multi: MultiSectionLayoutState | null;
|
|
1477
|
+
/**
|
|
1478
|
+
* Page-bottom footnote reserves from the last published notes layout.
|
|
1479
|
+
*
|
|
1480
|
+
* Seeded into the next {@link layoutSemanticDocumentWithNotes} call so the first body
|
|
1481
|
+
* pass already matches the reserved context key — without this, every notes document
|
|
1482
|
+
* starts empty, mismatches, and throws away the session on reflow.
|
|
1483
|
+
*/
|
|
1484
|
+
notePageBottomReserves: ReadonlyMap<number, number> | null;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* A layout session, retained across revisions by the caller.
|
|
1488
|
+
*/
|
|
1489
|
+
declare function createLayoutSession(): LayoutSession;
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* A deterministic measurer for tests and headless use.
|
|
1493
|
+
*
|
|
1494
|
+
* Monospace by construction: every character is the same width and every line the same
|
|
1495
|
+
* height, scaled by `w:sz` when present. Real shaping is the HarfBuzz path; this exists so
|
|
1496
|
+
* layout behaviour can be asserted without a font stack deciding the answer.
|
|
1497
|
+
*/
|
|
1498
|
+
declare function createFixedMeasurer(charWidth?: number, lineHeight?: number): TextMeasurer;
|
|
1499
|
+
|
|
1500
|
+
/** Which header/footer variant a page shows (ECMA-376 §17.10.5). */
|
|
1501
|
+
type HeaderFooterVariantName = 'default' | 'first' | 'even';
|
|
1502
|
+
/**
|
|
1503
|
+
* Pre-laid page furniture, supplied by the host (phase 2).
|
|
1504
|
+
*
|
|
1505
|
+
* Baseline stories are laid out once per variant (`layoutHeaderFooterStory`) for furniture
|
|
1506
|
+
* height. Stories that actually contain allowlisted PAGE/NUMPAGES fields attach a projector
|
|
1507
|
+
* so document-level finalize can re-layout under the known page count; field-free furniture
|
|
1508
|
+
* reuses the baseline on every sheet.
|
|
1509
|
+
*/
|
|
1510
|
+
interface PageFurniture {
|
|
1511
|
+
readonly titlePage: boolean;
|
|
1512
|
+
readonly evenAndOddHeaders: boolean;
|
|
1513
|
+
readonly headers: ReadonlyMap<HeaderFooterVariantName, HeaderFooterStoryLayout>;
|
|
1514
|
+
readonly footers: ReadonlyMap<HeaderFooterVariantName, HeaderFooterStoryLayout>;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Everything a layout pass needs beyond the document itself.
|
|
1518
|
+
*
|
|
1519
|
+
* `measurer` is the only required field — layout is DOM-free and measures through whatever is
|
|
1520
|
+
* injected here, which is what lets the same code paginate on a server and in a browser.
|
|
1521
|
+
*/
|
|
1522
|
+
interface SemanticLayoutOptions {
|
|
1523
|
+
readonly geometry?: PageGeometry;
|
|
1524
|
+
readonly measurer: TextMeasurer;
|
|
1525
|
+
/**
|
|
1526
|
+
* Reuse of measured-and-broken paragraphs across revisions (task 9.2).
|
|
1527
|
+
*
|
|
1528
|
+
* Only the BREAK is cached. Placement — y, fragments, page cuts — is always redone, so
|
|
1529
|
+
* an edit high in the document still repaginates everything below it while paragraphs
|
|
1530
|
+
* nobody touched are never measured again.
|
|
1531
|
+
*/
|
|
1532
|
+
readonly cache?: ParagraphLayoutCache<readonly PendingLine[]>;
|
|
1533
|
+
/**
|
|
1534
|
+
* Who produced the measurements, folded into every cache key.
|
|
1535
|
+
*
|
|
1536
|
+
* A font arriving after first paint changes every advance in the document while no
|
|
1537
|
+
* content changes; without this the cache would serve the pre-font layout forever.
|
|
1538
|
+
*/
|
|
1539
|
+
readonly producer?: string;
|
|
1540
|
+
/**
|
|
1541
|
+
* Incremental placement across revisions (task 9.3).
|
|
1542
|
+
*
|
|
1543
|
+
* Holds the previous complete layout and a flow checkpoint per paragraph, so a pass can
|
|
1544
|
+
* resume just before the first affected paragraph instead of re-placing the document from
|
|
1545
|
+
* the top, and can stop early when the flow reconverges with the previous run.
|
|
1546
|
+
*
|
|
1547
|
+
* Multi-section documents keep per-section child sessions on {@link LayoutSession.multi}.
|
|
1548
|
+
*/
|
|
1549
|
+
readonly session?: LayoutSession;
|
|
1550
|
+
/** Header/footer stories to attach per page; absent means no furniture. */
|
|
1551
|
+
readonly furniture?: PageFurniture;
|
|
1552
|
+
/**
|
|
1553
|
+
* Which tracked revisions this pass resolves away (ECMA-376 §17.13).
|
|
1554
|
+
*
|
|
1555
|
+
* `all-markup` (the default) lays out both halves of every change. `proposed` lays out what
|
|
1556
|
+
* the document becomes if every change is accepted; `original` what it was before any of
|
|
1557
|
+
* them. Both are LAYOUT INPUTS: neither applies a `TreeDocOp` nor publishes a `ModelChange`,
|
|
1558
|
+
* so a user who switches to the proposed result, saves, and sends the file has not silently
|
|
1559
|
+
* accepted every proposal in it.
|
|
1560
|
+
*/
|
|
1561
|
+
readonly displayMode?: RevisionDisplayMode;
|
|
1562
|
+
/**
|
|
1563
|
+
* Per-section furniture, index-aligned with `enumerateDocumentSections`.
|
|
1564
|
+
*
|
|
1565
|
+
* When present, multi-section layout attaches each section's own headers/footers (after
|
|
1566
|
+
* OOXML inheritance). `furniture` remains the single-section / last-section fallback.
|
|
1567
|
+
*/
|
|
1568
|
+
readonly sectionFurniture?: readonly (PageFurniture | undefined)[];
|
|
1569
|
+
/** Authored column count/gap for anchored `relativeFrom="column"` frame resolution. */
|
|
1570
|
+
readonly sectionColumns?: SectionColumns;
|
|
1571
|
+
/**
|
|
1572
|
+
* Styles-part cascade table (docDefaults + `w:style` last-wins). Absent keeps direct
|
|
1573
|
+
* formatting only — the pre-cascade behaviour, used by unit tests that never open a
|
|
1574
|
+
* package.
|
|
1575
|
+
*/
|
|
1576
|
+
readonly styleCascade?: StyleCascadeTable;
|
|
1577
|
+
/**
|
|
1578
|
+
* Projection of `/word/numbering.xml`. Absent keeps pre-list behaviour (no markers /
|
|
1579
|
+
* level indents). The index is immutable for a session; list counter state is derived
|
|
1580
|
+
* per layout pass from document order.
|
|
1581
|
+
*/
|
|
1582
|
+
readonly numberingIndex?: NumberingIndex;
|
|
1583
|
+
/**
|
|
1584
|
+
* Optional precomputed list items for the body story. When absent and
|
|
1585
|
+
* {@link numberingIndex} is set, layout walks the full body (including table cells)
|
|
1586
|
+
* once so counters continue across section boundaries and table document order.
|
|
1587
|
+
*/
|
|
1588
|
+
readonly listItems?: ReadonlyMap<string, ResolvedListItem>;
|
|
1589
|
+
/**
|
|
1590
|
+
* `w:settings/w:defaultTabStop` in points (ECMA-376 §17.15.1.25); absent keeps the 0.5"
|
|
1591
|
+
* schema default.
|
|
1592
|
+
*
|
|
1593
|
+
* It arrives as an option because the paragraph cascade cannot see `settings.xml`. A
|
|
1594
|
+
* metric-locale template declares 1134 twips (2cm) and every default-interval tab in the
|
|
1595
|
+
* document belongs on that grid. Constant for a session — the settings part is immutable
|
|
1596
|
+
* here — which is why the prepared-block memo does not key on it.
|
|
1597
|
+
*/
|
|
1598
|
+
readonly defaultTabStopPt?: number;
|
|
1599
|
+
/**
|
|
1600
|
+
* Turns a typed `w:hyperlink` into the SANITIZED record its spans carry.
|
|
1601
|
+
*
|
|
1602
|
+
* An option because resolving `r:id` needs the package's relationships, which layout — a
|
|
1603
|
+
* per-part walk — cannot see. Absent means link runs still measure, break and paint;
|
|
1604
|
+
* they simply carry no link, so nothing is clickable and no text is lost. That is the
|
|
1605
|
+
* degradation a headless test or a furniture-only pass gets, and it is the safe one.
|
|
1606
|
+
*/
|
|
1607
|
+
readonly projectLink?: HyperlinkProjector;
|
|
1608
|
+
/**
|
|
1609
|
+
* Footnote/endnote layout input. When present, body layout projects note marks and a
|
|
1610
|
+
* post-pass attaches note areas (with bounded reflow for pageBottom reservation).
|
|
1611
|
+
*/
|
|
1612
|
+
readonly notes?: NotesLayoutInput;
|
|
1613
|
+
/**
|
|
1614
|
+
* Per-page bottom reserves (points) subtracted from content height before line placement.
|
|
1615
|
+
* Produced by the note reflow loop; absent means full content column.
|
|
1616
|
+
*/
|
|
1617
|
+
readonly pageBottomReserves?: ReadonlyMap<number, number>;
|
|
1618
|
+
/** Derived note marks for body/note projection (provisional or final). */
|
|
1619
|
+
readonly noteMarks?: NoteMarkContext;
|
|
1620
|
+
/** Inline drawing projection for typed `w:drawing` / `wp:inline` nodes. */
|
|
1621
|
+
readonly inlineDrawingLayout?: InlineDrawingLayoutContext;
|
|
1622
|
+
/** Per-paragraph drawing projection/resource token for break cache keys. */
|
|
1623
|
+
readonly drawingTokenForParagraph?: (paragraph: OoxmlNode) => string;
|
|
1624
|
+
/** @deprecated Prefer {@link drawingTokenForParagraph}. */
|
|
1625
|
+
readonly drawingLayoutToken?: string;
|
|
1626
|
+
/** Internal: reflow pass index while wrap exclusions converge. */
|
|
1627
|
+
readonly drawingExclusionPass?: number;
|
|
1628
|
+
/** Internal: converged exclusion zones — skips the reflow loop when set with zones. */
|
|
1629
|
+
readonly drawingExclusionConverged?: boolean;
|
|
1630
|
+
/** Internal: exclusion zones from the prior reflow pass, keyed by page index. */
|
|
1631
|
+
readonly drawingExclusionZonesByPage?: ReadonlyMap<number, readonly ExclusionZone[]>;
|
|
1632
|
+
/** Canonical drawing traversal order within the owner story part. */
|
|
1633
|
+
readonly drawingSourceOrder?: ReadonlyMap<string, number>;
|
|
1634
|
+
/**
|
|
1635
|
+
* Cross-paragraph TOC field begin/end paragraph ids. Empty chrome on these ids suppresses
|
|
1636
|
+
* the caret placeholder line in layout while the tree nodes stay intact for refresh/save.
|
|
1637
|
+
*/
|
|
1638
|
+
readonly tocFieldChromeParagraphIds?: ReadonlySet<string>;
|
|
1639
|
+
/**
|
|
1640
|
+
* Begin-paragraph ids of empty TOCs. These keep one layout line so paint can host an
|
|
1641
|
+
* identifiable empty-TOC furniture placeholder (overrides chrome suppression).
|
|
1642
|
+
*/
|
|
1643
|
+
readonly emptyTocPlaceholderParagraphIds?: ReadonlySet<string>;
|
|
1644
|
+
/**
|
|
1645
|
+
* Empty result-paragraph ids inside empty TOCs. Suppressed like field chrome so blank
|
|
1646
|
+
* cached rows do not stack under the empty placeholder.
|
|
1647
|
+
*/
|
|
1648
|
+
readonly emptyTocSuppressedResultParagraphIds?: ReadonlySet<string>;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Lay one story part out into pages.
|
|
1652
|
+
*
|
|
1653
|
+
* The engine's layout entry point. Walks body, header, footer and note roots, flattens block
|
|
1654
|
+
* SDTs, paginates tables with header-row repeats and vertical merges, and resolves every
|
|
1655
|
+
* paragraph through the style cascade.
|
|
1656
|
+
*
|
|
1657
|
+
* Incremental when given a {@link LayoutSession}: per-block cache keys plus flow checkpoints mean
|
|
1658
|
+
* a pass that changes nothing returns the previous pages by identity.
|
|
1659
|
+
*/
|
|
1660
|
+
declare function layoutSemanticDocument(part: OoxmlPart, revision: number, options: SemanticLayoutOptions): SemanticLayout;
|
|
1661
|
+
|
|
1662
|
+
/** Soft ceiling on an expanded marker string (codepoints). */
|
|
1663
|
+
declare const MAX_MARKER_TEXT_LENGTH = 64;
|
|
1664
|
+
/** Soft ceiling on authored `w:lvlText` before expansion. */
|
|
1665
|
+
declare const MAX_LVL_TEXT_LENGTH = 64;
|
|
1666
|
+
/** Clamp a list counter into a safe non-negative integer Word can format. */
|
|
1667
|
+
declare function clampListValue(value: number): number;
|
|
1668
|
+
/** `decimal` (§17.18.59). Clamped, because the counter derives from file-declared restarts. */
|
|
1669
|
+
declare function formatDecimal(value: number): string;
|
|
1670
|
+
/** `decimalZero` (§17.18.59): single digits zero-padded to two. */
|
|
1671
|
+
declare function formatDecimalZero(value: number): string;
|
|
1672
|
+
/**
|
|
1673
|
+
* `upperRoman` (§17.18.59). Saturates at 3999, the largest value classical Roman numerals
|
|
1674
|
+
* express — beyond it there is nothing correct to emit, so it stops rather than inventing
|
|
1675
|
+
* notation.
|
|
1676
|
+
*/
|
|
1677
|
+
declare function formatUpperRoman(value: number): string;
|
|
1678
|
+
/** `lowerRoman` (§17.18.59). */
|
|
1679
|
+
declare function formatLowerRoman(value: number): string;
|
|
1680
|
+
/**
|
|
1681
|
+
* Excel-style letter sequence: 1→A … 26→Z, 27→AA.
|
|
1682
|
+
* Caps length so a hostile counter cannot grow without bound.
|
|
1683
|
+
*/
|
|
1684
|
+
declare function formatUpperLetter(value: number): string;
|
|
1685
|
+
/** `lowerLetter` (§17.18.59): the {@link formatUpperLetter} sequence, lower-cased. */
|
|
1686
|
+
declare function formatLowerLetter(value: number): string;
|
|
1687
|
+
/**
|
|
1688
|
+
* Format one counter for a `w:numFmt` value (ST_NumberFormat, §17.18.59).
|
|
1689
|
+
*
|
|
1690
|
+
* `none` prints NOTHING — it is the format Word uses for a level that contributes only
|
|
1691
|
+
* literal text, and formatting it as decimal invents a number the document never had.
|
|
1692
|
+
* `bullet` is not formatted here — callers use the literal `lvlText`.
|
|
1693
|
+
*
|
|
1694
|
+
* The remaining enumerants (`japaneseCounting`, `hebrew1`, `thaiNumbers`, `ganada`, …) are
|
|
1695
|
+
* per-script numeral sequences we do not carry glyph tables for. They fall back to decimal
|
|
1696
|
+
* deliberately: the ORDINAL is still the authored one, only the script differs, which reads
|
|
1697
|
+
* as a number in the wrong alphabet rather than as a missing or wrong marker.
|
|
1698
|
+
*/
|
|
1699
|
+
declare function formatNumFmt(numFmt: string, value: number): string;
|
|
1700
|
+
/**
|
|
1701
|
+
* Expand `w:lvlText` placeholders `%1`…`%9` using per-level counters and formats.
|
|
1702
|
+
*
|
|
1703
|
+
* `formats[i]` / `counters[i]` correspond to ilvl `i`. Missing slots use decimal / 1.
|
|
1704
|
+
* Literal percent signs that are not `%1`…`%9` are kept. Output is hard-capped.
|
|
1705
|
+
*/
|
|
1706
|
+
declare function expandLvlText(lvlText: string, counters: readonly number[], formats: readonly string[]): string;
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* The result of counting ONE list paragraph: the level that applied, the counter vector after it,
|
|
1710
|
+
* and the marker text a reader sees.
|
|
1711
|
+
*
|
|
1712
|
+
* Counters are a vector across all nine levels, not a single number, because a deeper level
|
|
1713
|
+
* restarting resets the ones below it while leaving those above intact.
|
|
1714
|
+
*/
|
|
1715
|
+
interface ListCounterAdvance {
|
|
1716
|
+
/** Effective abstract numbering template for this num instance. */
|
|
1717
|
+
readonly abstractNumId: string;
|
|
1718
|
+
readonly numId: string;
|
|
1719
|
+
readonly ilvl: number;
|
|
1720
|
+
readonly level: NumberingLevel;
|
|
1721
|
+
/** Counter vector after this item was counted (indices 0..8). */
|
|
1722
|
+
readonly counters: readonly number[];
|
|
1723
|
+
/** Expanded marker text (empty when vanished / empty lvlText). */
|
|
1724
|
+
readonly markerText: string;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* The running counters for one layout pass over one story.
|
|
1728
|
+
*
|
|
1729
|
+
* Stateful and order-dependent by nature: a list number is a function of every numbered paragraph
|
|
1730
|
+
* before it, which is why markers are computed during layout and cannot be read off a paragraph
|
|
1731
|
+
* in isolation.
|
|
1732
|
+
*/
|
|
1733
|
+
interface ListCounterState {
|
|
1734
|
+
/**
|
|
1735
|
+
* Advance counters for one list paragraph.
|
|
1736
|
+
*
|
|
1737
|
+
* Returns null when the numbering definition cannot be resolved — callers treat the
|
|
1738
|
+
* paragraph as non-list (inert fallback).
|
|
1739
|
+
*/
|
|
1740
|
+
advance(numId: string, ilvl: number): ListCounterAdvance | null;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Create a fresh counter bag for one story (body, or one header/footer part).
|
|
1744
|
+
*/
|
|
1745
|
+
declare function createListCounterState(index: NumberingIndex): ListCounterState;
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* The stack used when a run names no font (or names one the sink refuses).
|
|
1749
|
+
*
|
|
1750
|
+
* Paint only sets `font-family` when `w:rFonts` supplies a validated name, so an unstyled
|
|
1751
|
+
* run inherits the surrounding face. Measuring one stack and painting another drifts every
|
|
1752
|
+
* advance; this is the Word-like Latin fallback the canvas path measures against.
|
|
1753
|
+
*/
|
|
1754
|
+
declare const DEFAULT_CANVAS_FONT_STACK = "Calibri, Carlito, Helvetica, Arial, sans-serif";
|
|
1755
|
+
/**
|
|
1756
|
+
* The canvas text-metrics surface the editor injects.
|
|
1757
|
+
*
|
|
1758
|
+
* Structural subset of `CanvasRenderingContext2D` — declared here so the layout lane stays
|
|
1759
|
+
* off the DOM lib. Hosts pass a real 2d context; tests pass a controllable mock.
|
|
1760
|
+
*/
|
|
1761
|
+
interface CanvasTextMetrics {
|
|
1762
|
+
readonly width: number;
|
|
1763
|
+
readonly fontBoundingBoxAscent?: number;
|
|
1764
|
+
readonly fontBoundingBoxDescent?: number;
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* The slice of a 2D canvas context measurement needs.
|
|
1768
|
+
*
|
|
1769
|
+
* A structural subset rather than `CanvasRenderingContext2D`, so layout stays DOM-free and a test
|
|
1770
|
+
* can supply a deterministic stub.
|
|
1771
|
+
*/
|
|
1772
|
+
interface CanvasTextContext {
|
|
1773
|
+
font: string;
|
|
1774
|
+
measureText(text: string): CanvasTextMetrics;
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* How the canvas measurer resolves fonts, scales, and bounds its caches. Every field optional.
|
|
1778
|
+
*
|
|
1779
|
+
* Layout never creates a canvas itself: without a `context` this measurer does not exist and the
|
|
1780
|
+
* surface falls back to fixed metrics.
|
|
1781
|
+
*/
|
|
1782
|
+
interface CanvasMeasurerOptions {
|
|
1783
|
+
/**
|
|
1784
|
+
* Layout units to CSS pixels — the same value the painter uses.
|
|
1785
|
+
*
|
|
1786
|
+
* Measuring at the layout size and multiplying afterwards is not the same as measuring at
|
|
1787
|
+
* the painted size: font metrics are hinted per pixel size. Everything here measures at
|
|
1788
|
+
* the painted size and converts back.
|
|
1789
|
+
*/
|
|
1790
|
+
readonly scale?: number;
|
|
1791
|
+
readonly fallbackFamily?: string;
|
|
1792
|
+
/**
|
|
1793
|
+
* The engine-minted family a document-embedded face was registered under, if any.
|
|
1794
|
+
*
|
|
1795
|
+
* Paint prefers this alias over the declared family, so measurement has to as well: a
|
|
1796
|
+
* document whose face is embedded rather than installed would otherwise be measured
|
|
1797
|
+
* against the fallback and painted with the embedded glyphs, and every advance, wrap
|
|
1798
|
+
* point and page break would be taken from a font the reader never sees.
|
|
1799
|
+
*/
|
|
1800
|
+
readonly fontAlias?: (family: string) => string | undefined;
|
|
1801
|
+
/**
|
|
1802
|
+
* Injected 2d text context from the editor/browser seam.
|
|
1803
|
+
*
|
|
1804
|
+
* `undefined` or `null` makes {@link tryCreateCanvasMeasurer} return null so the surface
|
|
1805
|
+
* falls back to the fixed measurer. Layout never creates a canvas element itself.
|
|
1806
|
+
*/
|
|
1807
|
+
readonly context?: CanvasTextContext | null;
|
|
1808
|
+
/**
|
|
1809
|
+
* Unique `(font, text)` width entries retained before LRU eviction.
|
|
1810
|
+
*
|
|
1811
|
+
* Long editing sessions measure many transient paragraph states; bounding this cache keeps
|
|
1812
|
+
* memory predictable without changing layout output.
|
|
1813
|
+
*/
|
|
1814
|
+
readonly maxWidthEntries?: number;
|
|
1815
|
+
/** Distinct font-shorthand line metrics retained before LRU eviction. */
|
|
1816
|
+
readonly maxMetricsEntries?: number;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* The measurer a surface ended up with, plus the identity its cache keys must include.
|
|
1820
|
+
*
|
|
1821
|
+
* The `producer` string is load-bearing: the same canvas measuring against document-embedded
|
|
1822
|
+
* faces produces DIFFERENT advances, so the two must not share a cache key space or a document
|
|
1823
|
+
* would keep its pre-font pagination after the font arrived.
|
|
1824
|
+
*/
|
|
1825
|
+
interface ResolvedSurfaceMeasurer {
|
|
1826
|
+
readonly measurer: TextMeasurer;
|
|
1827
|
+
/**
|
|
1828
|
+
* Cache-invalidation identity when the caller did not supply `producer`.
|
|
1829
|
+
*
|
|
1830
|
+
* `canvas-measurer+embedded` is the canvas measurer resolving document-embedded faces:
|
|
1831
|
+
* the same canvas, different advances, so it must not share a cache key space.
|
|
1832
|
+
*/
|
|
1833
|
+
readonly producer: 'canvas-measurer' | 'canvas-measurer+embedded' | 'fixed-measurer';
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Whether an injected canvas text context is usable for measurement.
|
|
1837
|
+
*
|
|
1838
|
+
* Availability is decided by the editor seam (which alone may create a canvas). Layout
|
|
1839
|
+
* never probes `document` — a missing context is simply "unavailable".
|
|
1840
|
+
*/
|
|
1841
|
+
declare function isCanvasMeasurementAvailable(context?: CanvasTextContext | null | undefined): boolean;
|
|
1842
|
+
/**
|
|
1843
|
+
* Build a canvas-backed measurer, or `null` when no 2d context was injected.
|
|
1844
|
+
*
|
|
1845
|
+
* Prefer {@link resolveDefaultSurfaceMeasurer} at the editor surface: that keeps the
|
|
1846
|
+
* fixed fallback for SSR/tests in one place.
|
|
1847
|
+
*/
|
|
1848
|
+
declare function tryCreateCanvasMeasurer(options?: CanvasMeasurerOptions): TextMeasurer | null;
|
|
1849
|
+
/**
|
|
1850
|
+
* The surface's default measurer: canvas when a 2d context was injected, otherwise fixed.
|
|
1851
|
+
*
|
|
1852
|
+
* Host-supplied and shaping measurers override this entirely — call only when the options
|
|
1853
|
+
* did not already name one. The editor seam is responsible for creating the canvas context.
|
|
1854
|
+
*/
|
|
1855
|
+
declare function resolveDefaultSurfaceMeasurer(scale?: number, options?: CanvasMeasurerOptions): ResolvedSurfaceMeasurer;
|
|
1856
|
+
|
|
1857
|
+
/**
|
|
1858
|
+
* The story's blocks — paragraphs and tables — in document order, flattening through
|
|
1859
|
+
* block-level content-control wrappers under the shared nesting budget.
|
|
1860
|
+
*/
|
|
1861
|
+
declare function storyBlocks(part: OoxmlPart, displayMode?: RevisionDisplayMode): OoxmlElement[];
|
|
1862
|
+
/**
|
|
1863
|
+
* Blocks of one typed footnote/endnote node — a separate semantic story root.
|
|
1864
|
+
*
|
|
1865
|
+
* The footnotes/endnotes part root is never a story; each note is laid out independently
|
|
1866
|
+
* so line ids and incremental convergence stay namespaced by note identity.
|
|
1867
|
+
*/
|
|
1868
|
+
declare function noteStoryBlocks(note: OoxmlNode, displayMode?: RevisionDisplayMode): OoxmlElement[];
|
|
1869
|
+
|
|
1870
|
+
/** Paragraph ids for TOC field begin/end chrome that must not reserve vertical flow when empty. */
|
|
1871
|
+
declare function tocFieldChromeParagraphIds(part: OoxmlPart): ReadonlySet<string>;
|
|
1872
|
+
/**
|
|
1873
|
+
* Begin-paragraph ids of TOCs that have no visible cached result rows.
|
|
1874
|
+
*
|
|
1875
|
+
* Layout keeps a single caret-height line on these ids so paint can host an identifiable
|
|
1876
|
+
* empty-TOC furniture placeholder; ordinary field chrome on the same ids stays suppressed.
|
|
1877
|
+
*/
|
|
1878
|
+
declare function emptyTocPlaceholderParagraphIds(part: OoxmlPart): ReadonlySet<string>;
|
|
1879
|
+
/**
|
|
1880
|
+
* Empty result-paragraph ids inside an empty TOC.
|
|
1881
|
+
*
|
|
1882
|
+
* Suppressed like field chrome so blank cached rows do not stack under the empty placeholder.
|
|
1883
|
+
*/
|
|
1884
|
+
declare function emptyTocSuppressedResultParagraphIds(part: OoxmlPart): ReadonlySet<string>;
|
|
1885
|
+
|
|
1886
|
+
/** One page-local rectangle covering the part of a control that sits on that page. */
|
|
1887
|
+
interface ContentControlFragmentRecord extends LayoutBox {
|
|
1888
|
+
readonly pageIndex: number;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Everything a consumer needs to point at one control without walking the tree.
|
|
1892
|
+
*
|
|
1893
|
+
* `controlId` is the canonical NODE id — the identity `w:id` cannot provide, because the file
|
|
1894
|
+
* may omit it and may repeat it. `id` is the file's own `w:id` where it wrote one, carried as
|
|
1895
|
+
* metadata for a caller that has to speak to something outside this engine.
|
|
1896
|
+
*/
|
|
1897
|
+
interface ContentControlBoundaryRecord {
|
|
1898
|
+
readonly controlId: string;
|
|
1899
|
+
readonly type: ContentControlKind;
|
|
1900
|
+
readonly level: ContentControlLevel;
|
|
1901
|
+
/** 0 for a top-level control; the nesting depth otherwise. */
|
|
1902
|
+
readonly depth: number;
|
|
1903
|
+
readonly lock: ContentControlLock;
|
|
1904
|
+
/** The lock in force here, including every lock an enclosing control imposes. */
|
|
1905
|
+
readonly effectiveLock: ContentControlLock;
|
|
1906
|
+
readonly showingPlaceholder: boolean;
|
|
1907
|
+
readonly temporary: boolean;
|
|
1908
|
+
readonly tag?: string;
|
|
1909
|
+
readonly alias?: string;
|
|
1910
|
+
readonly id?: number;
|
|
1911
|
+
/** Preserved `w:dataBinding` metadata. Present means value writes are refused as bound. */
|
|
1912
|
+
readonly dataBinding?: ContentControlDataBinding;
|
|
1913
|
+
/** Paragraphs the control's content holds, in reading order. */
|
|
1914
|
+
readonly paragraphIds: readonly string[];
|
|
1915
|
+
/** One rectangle per page the content reaches, in the page's own coordinate space. */
|
|
1916
|
+
readonly fragments: readonly ContentControlFragmentRecord[];
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Derive one boundary record per control the part declares, in document order.
|
|
1920
|
+
*
|
|
1921
|
+
* A control whose content the layout never placed — one inside a story this layout is not of,
|
|
1922
|
+
* or one holding nothing — answers no fragments rather than a zero-sized box at the origin: a
|
|
1923
|
+
* rectangle nothing painted is a rectangle a hit test would match.
|
|
1924
|
+
*/
|
|
1925
|
+
declare function contentControlBoundaries(part: OoxmlPart, layout: SemanticLayout): readonly ContentControlBoundaryRecord[];
|
|
1926
|
+
|
|
1927
|
+
/** The visible band of the document, in layout units. */
|
|
1928
|
+
interface ViewportWindow {
|
|
1929
|
+
/** Distance from the top of the document to the top of the visible area, in layout units. */
|
|
1930
|
+
readonly top: number;
|
|
1931
|
+
readonly height: number;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Which pages to build in detail.
|
|
1935
|
+
*
|
|
1936
|
+
* A page left out keeps its size and position but no content, so the document's height and page
|
|
1937
|
+
* count are unchanged and scrolling to it reveals it rather than reflowing everything below.
|
|
1938
|
+
*/
|
|
1939
|
+
interface MaterializationInput {
|
|
1940
|
+
readonly layout: SemanticLayout;
|
|
1941
|
+
/** Omitted means "no viewport": everything is materialized, which is the honest default. */
|
|
1942
|
+
readonly viewport?: ViewportWindow;
|
|
1943
|
+
/** Extra pages kept ready either side of the visible band. */
|
|
1944
|
+
readonly overscanPages?: number;
|
|
1945
|
+
/** Pages that must be built wherever they are — caret, selection, a search hit. */
|
|
1946
|
+
readonly pinnedPages?: Iterable<number>;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* The page indices to build in detail.
|
|
1950
|
+
*
|
|
1951
|
+
* Returns indices rather than records so a caller can compare cheaply against what it
|
|
1952
|
+
* already has mounted, and so the decision can be made without touching the layout.
|
|
1953
|
+
*/
|
|
1954
|
+
declare function pagesToMaterialize(input: MaterializationInput): Set<number>;
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* What a batch of commits can affect.
|
|
1958
|
+
*
|
|
1959
|
+
* `paragraphIds` is the set the commits touched directly. What that set MEANS depends on
|
|
1960
|
+
* `impact`: for a local impact it bounds the work, for a structural one it only says where
|
|
1961
|
+
* the reflow starts.
|
|
1962
|
+
*/
|
|
1963
|
+
interface LayoutScope {
|
|
1964
|
+
readonly impact: ImpactClass;
|
|
1965
|
+
/** Node ids touched directly by the coalesced commits. */
|
|
1966
|
+
readonly paragraphIds: ReadonlySet<string>;
|
|
1967
|
+
/** Ids the commits created — no previous layout exists for these. */
|
|
1968
|
+
readonly created: ReadonlySet<string>;
|
|
1969
|
+
/** Ids the commits removed — any retained layout for these must be released. */
|
|
1970
|
+
readonly deleted: ReadonlySet<string>;
|
|
1971
|
+
/** Cache keys the commits invalidated (styles, numbering, fonts). */
|
|
1972
|
+
readonly dependencyKeys: ReadonlySet<string>;
|
|
1973
|
+
/** True when the block SEQUENCE changed, so ids alone cannot bound the reflow. */
|
|
1974
|
+
readonly structural: boolean;
|
|
1975
|
+
/** The revision this scope describes — the layout result must carry the same one. */
|
|
1976
|
+
readonly revision: number;
|
|
1977
|
+
}
|
|
1978
|
+
/** How the scheduler produces layouts and when it publishes them. */
|
|
1979
|
+
interface LayoutSchedulerOptions {
|
|
1980
|
+
/**
|
|
1981
|
+
* Produce a complete layout for the CURRENT model state.
|
|
1982
|
+
*
|
|
1983
|
+
* Given the accumulated scope so an incremental implementation can use it. It must tag
|
|
1984
|
+
* the result with the revision it actually read, which is what makes staleness detectable
|
|
1985
|
+
* rather than assumed.
|
|
1986
|
+
*/
|
|
1987
|
+
readonly run: (scope: LayoutScope) => SemanticLayout;
|
|
1988
|
+
/** The model's revision right now. Read at publish time, never cached. */
|
|
1989
|
+
readonly currentRevision: () => number;
|
|
1990
|
+
/** Called with a layout that is known current. Never called with a stale one. */
|
|
1991
|
+
readonly publish: (layout: SemanticLayout, scope: LayoutScope) => void;
|
|
1992
|
+
/**
|
|
1993
|
+
* Defer work to a later turn, returning a canceller.
|
|
1994
|
+
*
|
|
1995
|
+
* Injected rather than assumed: the browser wants an animation frame, tests want to step
|
|
1996
|
+
* the queue by hand, and a server has neither.
|
|
1997
|
+
*/
|
|
1998
|
+
readonly schedule?: (run: () => void) => () => void;
|
|
1999
|
+
/**
|
|
2000
|
+
* Run a layout in slices instead of in one call (task 9.5).
|
|
2001
|
+
*
|
|
2002
|
+
* Optional: without it `run` is used and completes in one turn, which is right for a
|
|
2003
|
+
* document small enough that slicing costs more than it saves.
|
|
2004
|
+
*/
|
|
2005
|
+
readonly runCooperatively?: (scope: LayoutScope) => CooperativeRun;
|
|
2006
|
+
}
|
|
2007
|
+
interface CooperativeRun {
|
|
2008
|
+
/**
|
|
2009
|
+
* Advance the work. Returns the finished layout, or null to be called again.
|
|
2010
|
+
*
|
|
2011
|
+
* Split so a global relayout does not hold the main thread for the whole document: the
|
|
2012
|
+
* scheduler yields between slices and a newer revision can cancel the run mid-flight
|
|
2013
|
+
* rather than the user waiting for work whose result is already stale.
|
|
2014
|
+
*/
|
|
2015
|
+
step(): SemanticLayout | null;
|
|
2016
|
+
/** Abandon the run. Nothing partial is ever published. */
|
|
2017
|
+
cancel(): void;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Coalesces commits into layout passes.
|
|
2021
|
+
*
|
|
2022
|
+
* A keystroke is one commit but must not be one full layout pass, so changes accumulate into a
|
|
2023
|
+
* scope and are laid out together. Every published layout is tagged with the revision it actually
|
|
2024
|
+
* read, which is what makes a stale result detectable rather than merely late.
|
|
2025
|
+
*/
|
|
2026
|
+
interface LayoutScheduler {
|
|
2027
|
+
/** Record a commit. Coalesces with anything already pending. */
|
|
2028
|
+
notify(change: TreeModelChange): void;
|
|
2029
|
+
/** Request a relayout for a reason the store did not report (page size, zoom, fonts). */
|
|
2030
|
+
invalidateAll(revision: number, reason?: string): void;
|
|
2031
|
+
/** Run any pending work now, synchronously. Returns whether a layout was published. */
|
|
2032
|
+
flush(): boolean;
|
|
2033
|
+
/** The scope that would be used if `flush` ran now, or null when nothing is pending. */
|
|
2034
|
+
pending(): LayoutScope | null;
|
|
2035
|
+
/** Drop pending work without publishing — for teardown. */
|
|
2036
|
+
cancel(): void;
|
|
2037
|
+
/** How many layouts were discarded for being stale. Diagnostics, and a test hook. */
|
|
2038
|
+
readonly staleDiscards: number;
|
|
2039
|
+
/** How many cooperative runs were abandoned because a newer revision arrived. */
|
|
2040
|
+
readonly cancelledRuns: number;
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Build the scheduler that turns a stream of commits into coalesced layout passes.
|
|
2044
|
+
*
|
|
2045
|
+
* Keystrokes arrive faster than a document can be laid out, so changes accumulate into one scope
|
|
2046
|
+
* and are laid out together rather than once per commit.
|
|
2047
|
+
*/
|
|
2048
|
+
declare function createLayoutScheduler(options: LayoutSchedulerOptions): LayoutScheduler;
|
|
2049
|
+
|
|
2050
|
+
/** Far above anything Word authors (its UI caps at 63) while keeping allocation bounded. */
|
|
2051
|
+
declare const MAX_TABLE_COLUMNS = 1024;
|
|
2052
|
+
|
|
2053
|
+
/**
|
|
2054
|
+
* `w:tblW` / `w:tcW` / `w:wBefore` (CT_TblWidth, 17.4.63 / 17.4.71 / 17.4.86): a PREFERRED
|
|
2055
|
+
* width plus the unit it is stated in. Preferred is the operative word — it is what the
|
|
2056
|
+
* producer asked for, not what the table resolved to.
|
|
2057
|
+
*
|
|
2058
|
+
* `pct` is stated in fiftieths of a percent (5000 = 100%) by Word, and in the `"50%"`
|
|
2059
|
+
* string form of `ST_Percentage` by others; both are read. `auto` and `nil` carry no width.
|
|
2060
|
+
*/
|
|
2061
|
+
type PreferredWidthType = 'dxa' | 'pct' | 'auto' | 'nil';
|
|
2062
|
+
/**
|
|
2063
|
+
* `w:tblW` / `w:tcW` — a requested width, whose UNIT depends on its type.
|
|
2064
|
+
*
|
|
2065
|
+
* Points for `dxa`, percent for `pct`, and zero for `auto`/`nil`. Reading `value` without `type`
|
|
2066
|
+
* is always wrong.
|
|
2067
|
+
*/
|
|
2068
|
+
interface PreferredWidth {
|
|
2069
|
+
readonly type: PreferredWidthType;
|
|
2070
|
+
/** POINTS for `dxa`, PERCENT (0–100) for `pct`, 0 for `auto`/`nil`. */
|
|
2071
|
+
readonly value: number;
|
|
2072
|
+
}
|
|
2073
|
+
/** The frozen "no preferred width" value — what a table or cell that declares none resolves to. */
|
|
2074
|
+
declare const AUTO_PREFERRED_WIDTH: PreferredWidth;
|
|
2075
|
+
|
|
2076
|
+
/**
|
|
2077
|
+
* Layout-time nesting ceiling. Parse-time depth (MAX_DEPTH = 256 XML levels) alone still
|
|
2078
|
+
* admits ~80 levels of `w:tbl` recursion into the layout walk; deeper tables render as an
|
|
2079
|
+
* empty cell box rather than recursing.
|
|
2080
|
+
*/
|
|
2081
|
+
declare const MAX_TABLE_NESTING = 16;
|
|
2082
|
+
/**
|
|
2083
|
+
* Fallback cell padding in points (60 twips) when neither `tblCellMar` nor `tcMar` authors
|
|
2084
|
+
* a side. Matches the historical uniform `CELL_PAD` inset.
|
|
2085
|
+
*/
|
|
2086
|
+
declare const CELL_PAD = 3;
|
|
2087
|
+
/**
|
|
2088
|
+
* Soft ceiling on an authored `w:trHeight` (~22"). Hostile `w:val` otherwise becomes a
|
|
2089
|
+
* multi-page row that every pagination preflight and cell box inherits.
|
|
2090
|
+
*/
|
|
2091
|
+
declare const MAX_TABLE_ROW_HEIGHT_PT: number;
|
|
2092
|
+
/**
|
|
2093
|
+
* `w:trPr/w:trHeight` (17.4.81) resolved for layout. Points leave the reader already —
|
|
2094
|
+
* twips convert once here, matching every other table geometry boundary.
|
|
2095
|
+
*
|
|
2096
|
+
* Word quirk (matches Form025U and Word's UI export): a present `@w:val` with an omitted
|
|
2097
|
+
* `@w:hRule` is treated as `atLeast`, not ECMA's `auto`. Explicit `auto` still ignores val.
|
|
2098
|
+
*/
|
|
2099
|
+
type TableRowHeightRule = 'auto' | 'atLeast' | 'exact';
|
|
2100
|
+
/**
|
|
2101
|
+
* `w:trHeight` — a row's height rule and its value.
|
|
2102
|
+
*
|
|
2103
|
+
* `auto` carries no value at all, which is why this is a union rather than a rule plus an
|
|
2104
|
+
* optional number.
|
|
2105
|
+
*/
|
|
2106
|
+
type TableRowHeight = {
|
|
2107
|
+
readonly rule: 'auto';
|
|
2108
|
+
} | {
|
|
2109
|
+
readonly rule: 'atLeast' | 'exact';
|
|
2110
|
+
readonly valuePt: number;
|
|
2111
|
+
};
|
|
2112
|
+
/** `w:vAlign` — where a cell's content sits when the row is taller than the content. */
|
|
2113
|
+
type CellVerticalAlign = 'top' | 'center' | 'bottom';
|
|
2114
|
+
/** `w:tblPr/w:jc` (17.4.29, ST_JcTable): where the table sits within the text column. */
|
|
2115
|
+
type TableAlignment = 'left' | 'center' | 'right';
|
|
2116
|
+
/**
|
|
2117
|
+
* `w:tblpPr/@w:horzAnchor` (17.4.58) and `@w:vertAnchor` (17.4.66): the box a floated
|
|
2118
|
+
* table's offsets are measured from. Absent means `text` for both.
|
|
2119
|
+
*/
|
|
2120
|
+
type TableFloatAnchor = 'text' | 'margin' | 'page';
|
|
2121
|
+
/** `w:tblpPr/@w:tblpXSpec` (17.4.63, ST_XAlign). */
|
|
2122
|
+
type TableFloatXSpec = 'left' | 'center' | 'right' | 'inside' | 'outside';
|
|
2123
|
+
/** `w:tblpPr/@w:tblpYSpec` (17.4.65, ST_YAlign). */
|
|
2124
|
+
type TableFloatYSpec = 'inline' | 'top' | 'center' | 'bottom' | 'inside' | 'outside';
|
|
2125
|
+
/**
|
|
2126
|
+
* `w:tblPr/w:tblpPr` (17.4.57) — a table positioned against an anchor box rather than at
|
|
2127
|
+
* the point in the text where it was authored.
|
|
2128
|
+
*
|
|
2129
|
+
* A spec (`tblpXSpec`/`tblpYSpec`) supersedes the matching offset when both are present:
|
|
2130
|
+
* 17.4.57 states the alignment outright, and the offset only answers "how far from the
|
|
2131
|
+
* anchor" for the case where no alignment was stated.
|
|
2132
|
+
*/
|
|
2133
|
+
interface TableFloatPosition {
|
|
2134
|
+
readonly horzAnchor: TableFloatAnchor;
|
|
2135
|
+
readonly vertAnchor: TableFloatAnchor;
|
|
2136
|
+
readonly xSpec?: TableFloatXSpec;
|
|
2137
|
+
/** `w:tblpX` in points; signed, so a table can be pulled into the margin. */
|
|
2138
|
+
readonly xPt: number;
|
|
2139
|
+
readonly ySpec?: TableFloatYSpec;
|
|
2140
|
+
/** `w:tblpY` in points; signed. */
|
|
2141
|
+
readonly yPt: number;
|
|
2142
|
+
}
|
|
2143
|
+
/** Resolved cell padding in points, after the table default and any per-cell override. */
|
|
2144
|
+
interface CellMarginsPt {
|
|
2145
|
+
readonly top: number;
|
|
2146
|
+
readonly right: number;
|
|
2147
|
+
readonly bottom: number;
|
|
2148
|
+
readonly left: number;
|
|
2149
|
+
}
|
|
2150
|
+
/** Word's own default cell padding, applied where a table declares no `w:tblCellMar`. */
|
|
2151
|
+
declare const DEFAULT_CELL_MARGINS: CellMarginsPt;
|
|
2152
|
+
/**
|
|
2153
|
+
* One cell in the resolved table structure.
|
|
2154
|
+
*
|
|
2155
|
+
* `gridSpan` is clamped at READ time and layout never re-derives it — the value comes from a file
|
|
2156
|
+
* and would otherwise be a loop bound an attacker controls.
|
|
2157
|
+
*/
|
|
2158
|
+
interface SemanticTableCell {
|
|
2159
|
+
readonly id: string;
|
|
2160
|
+
/** Clamped to [1, MAX_TABLE_COLUMNS] at read time; layout never re-derives it. */
|
|
2161
|
+
readonly gridSpan: number;
|
|
2162
|
+
/**
|
|
2163
|
+
* Absolute grid column this cell starts on, after `w:gridBefore` and every preceding
|
|
2164
|
+
* span. Structural conditional formats and cell geometry both key on this, never on the
|
|
2165
|
+
* cell's position in the row: one `gridSpan` cell otherwise shifts firstCol/lastCol and
|
|
2166
|
+
* the vertical bands for every cell after it.
|
|
2167
|
+
*/
|
|
2168
|
+
readonly gridColumn: number;
|
|
2169
|
+
/** Canonical `w:gridCol` node id for this cell's start column, when the grid is authored. */
|
|
2170
|
+
readonly gridColumnId?: string;
|
|
2171
|
+
/** A vMerge cell that is not the restart continues the cell above: box, no content. */
|
|
2172
|
+
readonly vMergeContinue: boolean;
|
|
2173
|
+
/** `w:vAlign` — defaults to top when omitted/unrecognised. */
|
|
2174
|
+
readonly vAlign: CellVerticalAlign;
|
|
2175
|
+
/** Resolved per-side margins (tcMar over tblCellMar over CELL_PAD). */
|
|
2176
|
+
readonly margins: CellMarginsPt;
|
|
2177
|
+
/** Three-state authored `tcBorders` (omitted / none / edge). */
|
|
2178
|
+
readonly borders: CellBorderBox;
|
|
2179
|
+
/** Validated 6-hex shading fill, absent for none/auto. */
|
|
2180
|
+
readonly shading?: string;
|
|
2181
|
+
/**
|
|
2182
|
+
* `w:tcW` — the width this cell asked for, as authored.
|
|
2183
|
+
*
|
|
2184
|
+
* Published for consumers that need the cell's own statement (a column-resize handle has
|
|
2185
|
+
* to write back to it). Column geometry is NOT derived from this field: the resolver works
|
|
2186
|
+
* from a flat claim list built in the same pass, because resolving a column means looking
|
|
2187
|
+
* at every cell that covers it across every row, not at one cell at a time. Read
|
|
2188
|
+
* `columnWidthsPt` for what the table actually laid out.
|
|
2189
|
+
*/
|
|
2190
|
+
readonly preferredWidth: PreferredWidth;
|
|
2191
|
+
/**
|
|
2192
|
+
* What the table style says about this cell's paragraphs and runs (17.7.6.6) — a header
|
|
2193
|
+
* row's bold and centring live here, not in the cell's own properties.
|
|
2194
|
+
*/
|
|
2195
|
+
readonly styleFormatting: TableCellStyleFormatting;
|
|
2196
|
+
/** Block children in reading order, with content-control wrappers flattened. */
|
|
2197
|
+
readonly blocks: readonly OoxmlElement[];
|
|
2198
|
+
}
|
|
2199
|
+
/** One row in the resolved structure: its cells, its height rule, and any row-level revision. */
|
|
2200
|
+
interface SemanticTableRow {
|
|
2201
|
+
readonly id: string;
|
|
2202
|
+
/** Pending Word row insertion/deletion authored in `w:trPr`. */
|
|
2203
|
+
readonly revisionKind?: 'insert' | 'delete';
|
|
2204
|
+
/**
|
|
2205
|
+
* The `w:trPr/w:ins|w:del` attribution, carried with the kind so a painted row can say
|
|
2206
|
+
* WHOSE pending decision it is — the review model addresses the decision by exactly this
|
|
2207
|
+
* `(id, author, date)` triple, and a surface with only the kind could highlight the row
|
|
2208
|
+
* but never open its card.
|
|
2209
|
+
*/
|
|
2210
|
+
readonly revisionId?: string;
|
|
2211
|
+
readonly revisionAuthor?: string;
|
|
2212
|
+
readonly revisionDate?: string;
|
|
2213
|
+
/** `w:trPr/w:tblHeader` — the row repeats atop each page the table continues onto. */
|
|
2214
|
+
readonly isHeader: boolean;
|
|
2215
|
+
/**
|
|
2216
|
+
* `w:trPr/w:cantSplit` — the row must stay on one page. When it cannot fit a fresh page,
|
|
2217
|
+
* layout fails closed rather than fragmenting or overflowing the content box.
|
|
2218
|
+
*/
|
|
2219
|
+
readonly cantSplit: boolean;
|
|
2220
|
+
/** `w:trPr/w:trHeight` — auto / atLeast floor / exact (clipped) row height. */
|
|
2221
|
+
readonly height: TableRowHeight;
|
|
2222
|
+
readonly cells: readonly SemanticTableCell[];
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* A table resolved into a rectangular grid: column widths, rows, and the widths it asked for.
|
|
2226
|
+
*
|
|
2227
|
+
* The grid is normalized here so layout never has to reconcile `w:gridCol` against actual cell
|
|
2228
|
+
* spans — vertical merges and column spans are already accounted for.
|
|
2229
|
+
*/
|
|
2230
|
+
interface SemanticTableStructure {
|
|
2231
|
+
readonly columnWidthsPt: readonly number[];
|
|
2232
|
+
readonly rows: readonly SemanticTableRow[];
|
|
2233
|
+
/** `w:tblPr/w:tblW` — the width the table asked for. */
|
|
2234
|
+
readonly tableWidth: PreferredWidth;
|
|
2235
|
+
/**
|
|
2236
|
+
* `w:tblInd` (17.4.50) in points — "this indentation should shift the table into the text
|
|
2237
|
+
* margin by the specified amount". Applies to a left-aligned table; `w:jc` decides the
|
|
2238
|
+
* placement outright for the other two.
|
|
2239
|
+
*/
|
|
2240
|
+
readonly indentPt: number;
|
|
2241
|
+
/** `w:tblPr/w:jc` (17.4.29) — where the table sits in the text column. */
|
|
2242
|
+
readonly alignment: TableAlignment;
|
|
2243
|
+
/**
|
|
2244
|
+
* `w:tblPr/w:tblpPr` (17.4.57) — present when the table is positioned against an anchor
|
|
2245
|
+
* box. Placement then comes from {@link tableFloatOriginX} rather than `w:jc`/`w:tblInd`.
|
|
2246
|
+
*/
|
|
2247
|
+
readonly float?: TableFloatPosition;
|
|
2248
|
+
/**
|
|
2249
|
+
* `w:tblCellSpacing` (17.4.45) in points: the gap between adjacent cell edges. Applied as
|
|
2250
|
+
* a half-gap inset on each side of every cell, so cells separate visually without the grid
|
|
2251
|
+
* itself moving. Word ALSO grows the table's overall width by the spacing it adds around
|
|
2252
|
+
* the outside; that part is not modelled, so a spaced table is laid out on the same grid
|
|
2253
|
+
* its file states rather than a wider one.
|
|
2254
|
+
*/
|
|
2255
|
+
readonly cellSpacingPt: number;
|
|
2256
|
+
/**
|
|
2257
|
+
* `w:tblPr/w:tblLayout/@w:type="fixed"` (17.4.52 — 17.4.53 is the `w:tblPrEx` exception
|
|
2258
|
+
* variant, not this element). Fixed layout takes the grid as final;
|
|
2259
|
+
* anything else is autofit, which in Word never renders wider than the text column.
|
|
2260
|
+
*/
|
|
2261
|
+
readonly layoutFixed: boolean;
|
|
2262
|
+
/** Table-level `tblBorders` (three-state, including insideH/insideV). */
|
|
2263
|
+
readonly tableBorders: TableBorderBox;
|
|
2264
|
+
/** Table-level `tblCellMar` defaults (per-side, CELL_PAD when a side is omitted). */
|
|
2265
|
+
readonly defaultMargins: CellMarginsPt;
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Where a table's left edge sits inside the box that contains it.
|
|
2269
|
+
*
|
|
2270
|
+
* 17.4.50 puts a left-aligned table at `w:tblInd` from the leading margin. 17.4.29's other
|
|
2271
|
+
* two placements are stated relative to the containing box instead, so the indent does not
|
|
2272
|
+
* also apply to them — Word centres a centred table in the text column whatever indent the
|
|
2273
|
+
* file carries. A table wider than its container starts flush so its leading edge stays on
|
|
2274
|
+
* the page rather than being centred off it.
|
|
2275
|
+
*/
|
|
2276
|
+
declare function tableOriginX(structure: SemanticTableStructure, containerWidthPt: number): number;
|
|
2277
|
+
/**
|
|
2278
|
+
* Read one typed table node into a bounded structure, or null when the node is not a
|
|
2279
|
+
* typed table or sits beyond the nesting ceiling.
|
|
2280
|
+
*/
|
|
2281
|
+
declare function readTableStructure(table: OoxmlNode, contentWidthPt: number, depth: number, styleCascade?: StyleCascadeTable,
|
|
2282
|
+
/** Which revisions the view resolves away; only the proposed result performs the join. */
|
|
2283
|
+
displayMode?: RevisionDisplayMode): SemanticTableStructure | null;
|
|
2284
|
+
|
|
2285
|
+
/**
|
|
2286
|
+
* `w:pPr/w:rPr/w:del` — the paragraph mark was deleted by a tracked revision.
|
|
2287
|
+
*
|
|
2288
|
+
* Read from the paragraph-mark run properties only. A `w:del` anywhere else in the paragraph
|
|
2289
|
+
* deletes run content, which is a different statement entirely.
|
|
2290
|
+
*/
|
|
2291
|
+
declare function paragraphMarkDeleted(paragraph: OoxmlNode): boolean;
|
|
2292
|
+
/**
|
|
2293
|
+
* True when a tracked revision has removed this paragraph from the rendered document, so
|
|
2294
|
+
* layout should emit no box for it at all.
|
|
2295
|
+
*/
|
|
2296
|
+
declare function revisionRemovesParagraph(paragraph: OoxmlNode, displayMode?: 'all-markup' | 'proposed' | 'original'): boolean;
|
|
2297
|
+
|
|
2298
|
+
/** Soft ceiling on fragments emitted for one authored row (hostile / runaway splits). */
|
|
2299
|
+
declare const MAX_TABLE_ROW_FRAGMENTS = 4096;
|
|
2300
|
+
/**
|
|
2301
|
+
* Why a table could not be paginated as authored.
|
|
2302
|
+
*
|
|
2303
|
+
* Each is a bound: a row taller than a page, a row that cannot be split, or a row producing more
|
|
2304
|
+
* fragments than the limit allows.
|
|
2305
|
+
*/
|
|
2306
|
+
type TablePaginationErrorCode = 'table-row-overheight' | 'table-row-split-unsupported' | 'table-row-fragment-limit';
|
|
2307
|
+
/**
|
|
2308
|
+
* Bounded table pagination failure. Prefer this over emitting a fragment that overflows
|
|
2309
|
+
* the page content box.
|
|
2310
|
+
*/
|
|
2311
|
+
declare class TablePaginationError extends Error {
|
|
2312
|
+
readonly code: TablePaginationErrorCode;
|
|
2313
|
+
constructor(code: TablePaginationErrorCode, message: string);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
export { AUTO_PREFERRED_WIDTH, type AbstractNumDefinition, type BidiEmbeddingLevels, BlockFragmentRecord, CELL_PAD, COMPOUND_BORDER_MIN_GAP_PT, COMPOUND_BORDER_MIN_STROKE_PT, type CanvasMeasurerOptions, type CanvasTextContext, type CanvasTextMetrics, type CascadedParagraphFormatting, CellBorderBox, type CellMarginsPt, type CellVerticalAlign, type CompoundBorderMetrics, type ContentControlFragmentRecord, DEFAULT_CANVAS_FONT_STACK, DEFAULT_CELL_MARGINS, EMPTY_NUMBERING_INDEX, FixedPoint, FixedPointRoundingMode, FontByteValidator, type FontSlot, GRAPHEME_SEGMENTER_LOCALE, type GraphemeBoundary, type GraphemeSegment, type GraphemeWordSegmentRecord, HARFBUZZ_SHAPING_LIBRARY, type HarfBuzzFaceCacheEvent, type HarfBuzzOutlineCacheEvent, type HarfBuzzShapeCacheEvent, HarfBuzzShapingError, type HarfBuzzShapingErrorCode, type HarfBuzzTextShaper, type HarfBuzzTextShaperInstrumentation, type HarfBuzzTextShaperOptions, type HeaderFooterVariantName, HyperlinkProjector, LayoutBox, type LayoutCacheStats, type LayoutScheduler, type LayoutSchedulerOptions, type LayoutScope, type LayoutSession, type LayoutSessionStats, type LevelOverride, type ListCounterAdvance, type ListCounterState, type ListMarkerAlign, type ListSuffix, MAX_EACH_PAGE_MARK_CANDIDATES, MAX_LVL_OVERRIDES, MAX_LVL_TEXT_LENGTH, MAX_MARKER_TEXT_LENGTH, MAX_NOTES_LAID_OUT, MAX_NOTE_FRAGMENTS, MAX_NOTE_OVERFLOW_PAGES, MAX_NOTE_REFLOW_ATTEMPTS, MAX_NUMBERING_DEFINITIONS, MAX_STYLE_BASED_ON_DEPTH, MAX_STYLE_DEFINITIONS, MAX_TABLE_COLUMNS, MAX_TABLE_NESTING, MAX_TABLE_ROW_FRAGMENTS, MAX_TABLE_ROW_HEIGHT_PT, type MaterializationInput, type NoteDisplayMark, type NoteLayoutFallbackReason, type NoteMarkContext, type NotePaginationFallbackReason, type NoteReferenceSite, type NoteSeparatorLayout, type NoteSeparatorRuleStyle, type NoteStoryLayout, type NotesAttachResult, type NotesLayoutInput, type NumDefinition, type NumberingIndex, type NumberingLevel, type NumberingLevelIndent, type PageFurniture, PageGeometry, PageRecord, type PageRefIndex, ParagraphBorderEdge, ParagraphBorders, ParagraphFragmentRecord, type ParagraphKeyInputs, type ParagraphLayoutCache, type ParagraphLayoutCacheOptions, type ParagraphLayoutInputs, ParagraphLineSpacing, ParagraphSpacing, type PreferredWidth, type PreferredWidthType, type ResolvedListItem, ResolvedRunStyle, type ResolvedSurfaceMeasurer, ResolvedTabStops, RevisionDisplayMode, type ScriptItem, SectionColumns, SemanticLayout, type SemanticLayoutOptions, type SemanticTableCell, type SemanticTableRow, type SemanticTableStructure, ShapedRun, type StyleCascadeTable, type StyleDefinition, StyleSpanRecord, type TableAlignment, TableBorderBox, TableBorderSide, type TableCellStyleFormatting, TablePaginationError, type TablePaginationErrorCode, type TableRowHeight, type TableRowHeightRule, TextDirection, TextMeasurer, TextShaper, UnsupportedScriptError, VersionedShapingLibrary, type ViewportWindow, WORD_SEGMENTER_LOCALE, type WordBoundary, type WordBoundaryResolverDeps, type WordSegment, attachNotesToLayout, boundedFallbackWordSegments, buildNumberingIndex, buildPageRefIndex, buildStyleCascadeTable, cascadeParagraphFormatting, cascadeRunProperties, cascadedBottomBorder, clampListValue, computeDoubleBorderMetricsPt, computeFootnoteReserves, contentControlBoundaries, createBoundedFallbackWordBoundary, createDefaultWordBoundary, createFixedMeasurer, createHarfBuzzTextShaper, createIntlWordBoundary, createLayoutScheduler, createLayoutSession, createListCounterState, createParagraphLayoutCache, defaultNoteSeparatorRuleStyle, deriveNoteDisplayMarks, deriveNoteDisplayMarksResolved, effectiveBorderSide, emptyTocPlaceholderParagraphIds, emptyTocSuppressedResultParagraphIds, expandLvlText, filterRefsOnPage, findSeparatorNote, formatDecimal, formatDecimalZero, formatLowerLetter, formatLowerRoman, formatNumFmt, formatUpperLetter, formatUpperRoman, fragmentOwnsAtomOffset, graphemeBoundaryEpoch, graphemeCount, graphemeOffsetToUtf16, harfBuzzFontValidator, initializeHarfBuzz, intlGraphemeBoundary, isCanvasMeasurementAvailable, isCumulativeGeometryTrustedFromLineOrigin, isGeometryTrustedCaretOffset, isHarfBuzzInitialized, isIntlSegmenterAvailable, isIntlWordSegmenterAvailable, isMarkerOnlySeparatorNote, isValidStyleId, isWholeGraphemeHorizontalBoundary, itemizeScriptFontSlots, layoutHeaderFooterStory, layoutNoteById, layoutNoteSeparator, layoutNoteStory, layoutSemanticDocument, listMarkerBox, mergeListIndent, normalNotesOf, noteDisplayMarkMap, noteLineIdPrefix, noteMarkKey, noteSeparatorAreaBox, noteStoryBlocks, pagesToMaterialize, paragraphLayoutKey, paragraphMarkDeleted, paragraphShading, paragraphShadingBox, projectedNoteMarkText, provisionalNoteMarks, readNumPr, readTableStructure, resetGraphemeBoundary, resolveDefaultSurfaceMeasurer, resolveDefaultWordBoundary, resolveNumberingLevel, resolveOoxmlShadingFill, resolveParagraphLayoutInputs, resolveStoryListItems, resolveStrictHexFill, revisionRemovesParagraph, roundFontUnitToFixedPoint, segmentGraphemes, segmentWords, semanticHorizontalBoundaries, setGraphemeBoundary, shadingFillFromElement, shapedHorizontalBoundaries, storyBlocks, syntheticSeparatorBox, tableOriginX, tocFieldChromeParagraphIds, tryCreateCanvasMeasurer, utf16OffsetToGrapheme, walkStoryParagraphs, withNumberingStyleLinks, withResolvedListItems, wordSegmentsToGraphemeRecords };
|