@eigenpal/docx-editor-core/layout-bridge
Layout Bridge — measure, hit-test, and map between PM positions and pixels.
Internal layer between the layout engine and rendering. The named exports below are the public contract for adapter authors, but the API is still evolving and may change in minor releases until a third-party adapter validates it.
Functions(90)
applyFootnotePresentation
Footnote-specific block normalization. Mirrors the spirit of `normalizeHeaderFooterMeasureBlocks`: post-process the body-pipeline output for a single footnote so it carries the correct visual prefix (its display number, rendered as a superscript) and a default 8pt font for any run that didn't specify a size.
The displayNumber is prepended onto the FIRST paragraph as a fresh superscript text run — visually matches Word's footnote numbering without disturbing the authored runs.
Exported for callers that want to compose their own conversion pipeline; `convertFootnoteToContent` calls it as part of its flow.
declare function applyFootnotePresentation(blocks: FlowBlock[], displayNumber: number): FlowBlock[];buildFontString
Build a CSS font string from styling properties
Font sizes are in points and need to be converted to pixels for canvas. 1pt = 96/72 px ≈ 1.333px at standard web DPI.
Uses the font resolver to get category-appropriate fallback stacks (serif fonts get serif fallbacks, sans-serif get sans-serif, etc.) matching the same stacks used in rendering for consistent measurements.
declare function buildFontString(style: FontStyle): string;buildFontString( fontFamily: "Arial", fontSize: 12, bold: true ) // Returns: "bold 16px Arial, Arimo, Helvetica, sans-serif" (12pt = 16px)buildFootnoteContentMap
Build footnote content for all footnotes referenced in the document. Display numbers are assigned by first-appearance order (the same way Word renders them).
declare function buildFootnoteContentMap(footnotes: Footnote[], footnoteRefs: Array<{
footnoteId: number;
}>, contentWidth: number, options: ConvertFootnoteOptions): Map<number, FootnoteContent>;buildFootnoteRenderItems
Turn the page→footnote-id map into the per-page render payload that `renderPages` consumes via `footnotesByPage`. Skips non-`normal` notes (separators, continuation notices), reads the display number out of the content map, and pulls plain text via `getFootnoteText`.
Lives in core (not in either adapter) so React + Vue both call the same helper — same rule as the rest of this module.
declare function buildFootnoteRenderItems(pageFootnoteMap: Map<number, number[]>, footnoteContentMap: Map<number, FootnoteContent>, doc: Document | null): Map<number, FootnoteRenderItem[]>;calculateFootnoteReservedHeights
Calculate per-page footnote reserved heights. Returns MappageNumber, reservedHeight.
declare function calculateFootnoteReservedHeights(pageFootnoteMap: Map<number, number[]>, footnoteContentMap: Map<number, {
height: number;
}>): Map<number, number>;clampFloatingWrapMargins
When a float's wrap margins consume the entire content width (or more), there is no horizontal strip beside it for body text. Word renders the following lines at full content width instead of squeezing them into a 1-pixel column. Unchecked margins from near-full-width tables/images can exceed contentWidth and collapse every line to ~1 glyph (the "single character per line after a wide floating table" bug).
Returned margins are zeroed when: - either side alone is = contentWidth (no strip on that side at all), or - their sum is = contentWidth (no strip exists between the two sides).
declare function clampFloatingWrapMargins(leftMargin: number, rightMargin: number, contentWidth: number): {
leftMargin: number;
rightMargin: number;
};clearAllCaches
Clear all measurement caches Call when fonts change, page width changes, or for testing
declare function clearAllCaches(): void;clearFontMetricsCache
Clear the font metrics cache
declare function clearFontMetricsCache(): void;clearParagraphMeasureCache
Clear the paragraph measure cache
declare function clearParagraphMeasureCache(): void;clearTextWidthCache
Clear the text width cache
declare function clearTextWidthCache(): void;clickToPosition
Main entry point: Map a click to a PM position.
This function takes the result of hit testing and returns the PM position.
declare function clickToPosition(fragmentHit: FragmentHit | null, tableCellHit?: TableCellHit | null): number | null;clickToPositionDom
Find ProseMirror position from a click using DOM-based detection.
declare function clickToPositionDom(container: HTMLElement, clientX: number, clientY: number, zoom?: number): number | null;clickToPositionInParagraph
Map a click within a paragraph fragment to a PM position.
declare function clickToPositionInParagraph(fragmentHit: FragmentHit): PositionResult | null;clickToPositionInTableCell
Map a click within a table cell to a PM position.
declare function clickToPositionInTableCell(tableCellHit: TableCellHit): number | null;collectFootnoteRefs
Scan FlowBlocks for runs with footnoteRefId set. Returns a list of footnoteId, pmPos in document order.
declare function collectFootnoteRefs(blocks: FlowBlock[]): Array<{
footnoteId: number;
pmPos: number;
}>;convertBorderSpecToLayout
Convert an OOXML BorderSpec to a layout-engine BorderStyle. Shared by paragraph borders, cell borders, and header/footer borders.
declare function convertBorderSpecToLayout(border: {
style?: string;
size?: number;
space?: number;
color?: {
rgb?: string;
themeColor?: string;
themeTint?: string;
themeShade?: string;
};
}, theme?: Theme | null): BorderStyle | undefined;convertFootnoteToContent
Convert a Footnote to renderable FootnoteContent via the body pipeline: `footnoteToProseDoc → toFlowBlocks → applyFootnotePresentation → measureBlocks`. Pre-PR (#378) this lived in a hand-rolled shadow stack that silently dropped non-paragraph content; routing through the body pipeline gives footnotes full block-kind support — paragraph + table + image + textBox + fields.
declare function convertFootnoteToContent(footnote: Footnote, displayNumber: number, contentWidth: number, options: ConvertFootnoteOptions): FootnoteContent;countTableColumns
Total grid columns, derived from the widest row's accumulated colSpans.
declare function countTableColumns(tableBlock: TableBlock): number;detectTableInsertHover
Detect whether a mousemove should surface a row/column insert "+" button.
Returns the button anchor + target cell PM position, or `null` if the mouse isn't near a row's left edge or a column's top edge — or if the relevant table belongs to an inactive HF/body region.
declare function detectTableInsertHover(input: TableInsertHoverInput): TableInsertHoverHit | null;findBodyEmptyRuns
All body-tree empty-paragraph runs. Used as a caret fallback when a paragraph has no painted text spans.
declare function findBodyEmptyRuns(container: ParentNode): HTMLElement[];findBodyPmAnchor
First body-tree element whose `data-pm-start` exactly matches `pmStart`.
Used for scroll anchors and image `NodeSelection` resolution where the caller already knows the exact PM position it wants to find. Returns `null` for non-finite inputs so callers don't need to guard.
declare function findBodyPmAnchor(container: ParentNode, pmStart: number): HTMLElement | null;findBodyPmAnchors
All body-tree elements carrying a `data-pm-start` attribute.
Includes paragraph elements as well as run spans, which is what scroll- anchor recovery needs. Distinct from [findBodyPmSpans](findBodyPmSpans), which filters down to run spans only.
declare function findBodyPmAnchors(container: ParentNode): HTMLElement[];findBodyPmSpans
All body-tree run spans carrying both `data-pm-start` and `data-pm-end`.
This is the workhorse for caret resolution and selection-rect painting.
declare function findBodyPmSpans(container: ParentNode): HTMLElement[];findCharacterAtX
Find the character offset at a given X position within a text run
declare function findCharacterAtX(x: number, charWidths: number[]): number;footnoteReservedHeightsEqual
Compare two per-page footnote reservation maps. Used by the React + Vue adapters to detect when the multi-pass loop has converged.
declare function footnoteReservedHeightsEqual(a: Map<number, number>, b: Map<number, number>): boolean;getCachedFontMetrics
Get cached font metrics or return undefined
declare function getCachedFontMetrics(fontFamily: string, fontSize: number, bold?: boolean, italic?: boolean): FontMetricsEntry | undefined;getCachedParagraphMeasure
Get cached paragraph measurement or return undefined
declare function getCachedParagraphMeasure(block: ParagraphBlock, maxWidth: number): ParagraphMeasure | undefined;getCachedTextWidth
Get cached text width or return undefined
declare function getCachedTextWidth(text: string, font: string, letterSpacing?: number): number | undefined;getCanvasContext
Get or create a canvas 2D context for text measurement
declare function getCanvasContext(): CanvasRenderingContext2D;getCaretPosition
Get caret position for a collapsed selection.
declare function getCaretPosition(layout: Layout, blocks: FlowBlock[], measures: Measure[], pmPosition: number): CaretPosition | null;getCaretPositionFromDom
declare function getCaretPositionFromDom(container: HTMLElement, pmPos: number, overlayRect: DOMRect): DomCaretPosition | null;getFloatingMargins
declare function getFloatingMargins(lineY: number, lineHeight: number, zones: FloatingImageZone[] | undefined, paragraphYOffset: number): FloatingLineMargins;getFontCacheSize
Get current font metrics cache size
declare function getFontCacheSize(): number;Get typography metrics for a given font size and family
Uses Canvas TextMetrics API when available for precise metrics, falls back to ratio-based approximations.
declare function getFontMetrics(style: FontStyle): FontMetrics;Convert SectionProperties margins (twips) → pixel `PageMargins`.
`header` / `footer` default to 48px (Word's 0.5-inch default) so the HF margin-extension math doesn't have to special-case undefined.
declare function getMargins(sp: SectionProperties | null | undefined): PageMargins;getPageBounds
Get page bounds (top and bottom Y coordinates).
declare function getPageBounds(layout: Layout, pageIndex: number): {
top: number;
bottom: number;
} | null;getPageIndexAtY
Get the page index at a specific Y coordinate. Returns null if the coordinate is outside all pages.
declare function getPageIndexAtY(layout: Layout, y: number): number | null;Convert SectionProperties page size (twips) → pixel `{ w, h }`.
declare function getPageSize(sp: SectionProperties | null | undefined): {
w: number;
h: number;
};getPageTop
Calculate the Y offset of a page from the top of the document.
declare function getPageTop(layout: Layout, pageIndex: number): number;getParagraphCacheSize
Get current paragraph measure cache size
declare function getParagraphCacheSize(): number;getPositionRect
Get the bounding rect for a PM position (for caret rendering).
declare function getPositionRect(block: ParagraphBlock, measure: ParagraphMeasure, pmPosition: number, fragmentX: number, fragmentY: number, fragmentWidth: number, fromLine: number): {
x: number;
y: number;
height: number;
} | null;getRunCharWidths
Get per-character widths for a text run (for click positioning)
declare function getRunCharWidths(run: TextRun): number[];getScrollYForPage
Get Y coordinate to scroll to for a specific page.
declare function getScrollYForPage(layout: Layout, pageIndex: number): number;getSelectionRectsFromDom
declare function getSelectionRectsFromDom(container: HTMLElement, from: number, to: number, overlayRect: DOMRect): DomSelectionRect[];getTextCacheSize
Get current text width cache size
declare function getTextCacheSize(): number;getTotalCacheSize
Get total size of all caches
declare function getTotalCacheSize(): number;getTotalDocumentHeight
Get the total document height (all pages + gaps).
declare function getTotalDocumentHeight(layout: Layout): number;getXForCharacter
Get the X position of a character offset within a text run
declare function getXForCharacter(offset: number, charWidths: number[]): number;groupRectsByPage
Get selection rectangles grouped by page.
declare function groupRectsByPage(rects: SelectionRect[]): Map<number, SelectionRect[]>;Convert OOXML half-points to pixels OOXML font sizes are in half-points (24 = 12pt)
declare function halfPtToPx(halfPt: number): number;hashParagraphBlock
Generate a simple hash for a paragraph block Used as cache key to identify identical content
declare function hashParagraphBlock(block: ParagraphBlock): string;Perform complete hit testing from document coordinates to the most specific element.
declare function hitTest(layout: Layout, blocks: FlowBlock[], measures: Measure[], point: Point): HitTestResult;hitTestFragment
Hit-test fragments on a page to find which fragment contains a point.
Fragments are checked in visual order (sorted by Y then X). Only considers paragraph and table fragments (not images for now).
declare function hitTestFragment(pageHit: PageHit, blocks: FlowBlock[], measures: Measure[], pagePoint: Point): FragmentHit | null;hitTestImageFragment
Hit-test to find image fragments (they may overlap other content).
declare function hitTestImageFragment(pageHit: PageHit, blocks: FlowBlock[], measures: Measure[], pagePoint: Point): FragmentHit | null;hitTestPage
Hit-test pages to find which page contains a given Y coordinate.
Pages are stacked vertically with optional gaps between them. If the point is in a gap, returns the nearest page.
declare function hitTestPage(layout: Layout, point: Point): PageHit | null;hitTestTableCell
Hit-test within a table fragment to find the specific cell.
declare function hitTestTableCell(pageHit: PageHit, blocks: FlowBlock[], measures: Measure[], pagePoint: Point): TableCellHit | null;isMultiPageSelection
Check if a selection spans multiple pages.
declare function isMultiPageSelection(rects: SelectionRect[]): boolean;mapFootnotesToPages
After layout, determine which footnotes appear on which pages. Checks each page's fragments to see if any footnoteRef PM positions fall within.
Returns MappageNumber, footnoteId[] in document order.
declare function mapFootnotesToPages(pages: Page[], footnoteRefs: Array<{
footnoteId: number;
pmPos: number;
}>): Map<number, number[]>;measureParagraph
Measure a paragraph block and compute line breaks
declare function measureParagraph(block: ParagraphBlock, maxWidth: number, options?: MeasureParagraphOptions): ParagraphMeasure;measureParagraphs
Measure multiple paragraph blocks
declare function measureParagraphs(blocks: ParagraphBlock[], maxWidth: number): ParagraphMeasure[];Measure a run of text and return per-character widths for click positioning
declare function measureRun(text: string, style: FontStyle): RunMeasurement;measureTableBlock
Measure a `TableBlock` against a content-width budget.
`measureBlock` is the per-cell-content measurement callback the adapter uses for everything inside a cell. The adapter passes its own `measureBlock` so block coverage stays per-renderer.
declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, measureBlock: (block: FlowBlock, contentWidth: number) => Measure): TableMeasure;measureTableCellBlockVisualHeight
Visual height of a single block inside a table cell.
A one-line paragraph that contains only image runs is laid out at the image's intrinsic height (plus the paragraph's explicit spacing), not a full text line — matching Word's per-cell layout. Everything else uses the measured `totalHeight` / `height`.
declare function measureTableCellBlockVisualHeight(block: FlowBlock, blockMeasure: Measure): number;Measure text and return full metrics
declare function measureText(text: string, style: FontStyle): TextMeasurement;measureTextWidth
Measure the width of a text string with specific styling
declare function measureTextWidth(text: string, style: FontStyle): number;normalizeTableColumnWidths
Make `columnWidths` exactly `colCount` long with every entry positive. Missing trailing columns inherit the average of existing positives; zero or negative entries split the leftover `targetWidth` evenly. Callers scale down totals that exceed the target — this helper only fills gaps.
declare function normalizeTableColumnWidths(columnWidths: number[], colCount: number, targetWidth: number): number[];Map a PM position to X coordinates within a line (for caret positioning).
declare function positionToX(block: ParagraphBlock, measure: ParagraphMeasure, pmPosition: number, _fragmentWidth: number): {
x: number;
lineIndex: number;
} | null;Convert points to pixels
declare function ptToPx(pt: number): number;Convert pixels to OOXML half-points
declare function pxToHalfPt(px: number): number;Convert pixels to points
declare function pxToPt(px: number): number;Convert pixels to twips
declare function pxToTwips(px: number): number;rectsToFloatingZones
declare function rectsToFloatingZones(rects: FloatingExclusionRect[], contentWidth: number): FloatingImageZone[];resetBlockIdCounter
Reset the block ID counter (useful for testing).
declare function resetBlockIdCounter(): void;resetCanvasContext
Reset the canvas context (useful for testing)
declare function resetCanvasContext(): void;resolveListTemplate
Resolve an OOXML lvlText template like "%1.%2." against the counter stack and per-level numFmt list (ECMA-376 §17.9.11).
When a referenced counter has no value yet (e.g. "%2" referenced from a level-0 paragraph), the placeholder AND the punctuation immediately following it are dropped — matches Word's behavior so "%1.%2." renders "1." rather than "1..".
Exported for unit testing.
declare function resolveListTemplate(template: string, counters: number[], levelNumFmts: NumberFormat[] | undefined): string;selectionToRects
Convert a ProseMirror selection range to screen rectangles.
declare function selectionToRects(layout: Layout, blocks: FlowBlock[], measures: Measure[], from: number, to: number): SelectionRect[];setCachedFontMetrics
Store font metrics in cache
declare function setCachedFontMetrics(fontFamily: string, fontSize: number, bold: boolean, italic: boolean, metrics: FontMetricsEntry): void;setCachedParagraphMeasure
Store paragraph measurement in cache
declare function setCachedParagraphMeasure(block: ParagraphBlock, maxWidth: number, measure: ParagraphMeasure): void;setCachedTextWidth
Store text width in cache
declare function setCachedTextWidth(text: string, font: string, letterSpacing: number, width: number): void;setFontCacheSize
Set the maximum size of the font metrics cache
declare function setFontCacheSize(size: number): void;setParagraphCacheSize
Set the maximum size of the paragraph measure cache
declare function setParagraphCacheSize(size: number): void;setTextCacheSize
Set the maximum size of the text width cache
declare function setTextCacheSize(size: number): void;stabilizeFootnoteLayout
Run the multi-pass footnote layout loop. Reserving footnote space on a page can move a reference to another page, which changes the reservation, which can move references again. Iterate until the page→height contract is the same one used by the latest layout, or `MAX_FOOTNOTE_LAYOUT_PASSES` passes have run.
Lives in core so the React + Vue adapters call the same loop and stay in lockstep on convergence behaviour. Writes `page.footnoteIds` onto each page in the returned layout so renderers can paint footnote areas.
declare function stabilizeFootnoteLayout(args: StabilizeFootnoteLayoutArgs): StabilizeFootnoteLayoutResult;toFlowBlocks
Convert a ProseMirror document to FlowBlock array.
Walks the document tree, converting each node to the appropriate block type. Tracks pmStart/pmEnd positions for each block for click-to-position mapping.
declare function toFlowBlocks(doc: Node, options?: ToFlowBlocksOptions): FlowBlock[];Convert twips to pixels
declare function twipsToPx(twips: number): number;Interfaces(12)
DomCaretPosition
Get caret position from DOM for a PM position.
interface DomCaretPosition| Member | Type | Summary |
|---|---|---|
| height | number | |
| pageIndex | number | |
| x | number | |
| y | number |
DomSelectionRect
Get selection rectangles for a PM range using DOM-based detection.
interface DomSelectionRect| Member | Type | Summary |
|---|---|---|
| height | number | |
| pageIndex | number | |
| width | number | |
| x | number | |
| y | number |
FloatingExclusionRect
interface FloatingExclusionRect| Member | Type | Summary |
|---|---|---|
| distBottom | number | |
| distLeft | number | |
| distRight | number | |
| distTop | number | |
| height | number | |
| side | 'left' | 'right' | Which side the object is on for simple one-sided wrapping. |
| width | number | |
| wrapText? | WrapTextDirection | |
| wrapType? | string | |
| x | number | X position relative to the content area. |
| y | number | Y position relative to the content area. |
FloatingImageZone
interface FloatingImageZone| Member | Type | Summary |
|---|---|---|
| bottomY | number | |
| leftMargin | number | |
| rightMargin | number | |
| segments? | FloatingLineSegmentZone[] | |
| topY | number |
FloatingLineSegmentZone
interface FloatingLineSegmentZone| Member | Type | Summary |
|---|---|---|
| availableWidth | number | |
| leftOffset | number |
FontMetrics
Typography metrics for a font
interface FontMetrics| Member | Type | Summary |
|---|---|---|
| ascent | number | |
| descent | number | |
| fontFamily | string | |
| fontSize | number | |
| lineHeight | number | |
| singleLineRatio | number | OS/2 single-line ratio for OOXML line spacing calculation |
FontStyle
Font styling properties for measurement
interface FontStyle| Member | Type | Summary |
|---|---|---|
| bold? | boolean | |
| fontFamily? | string | |
| fontSize? | number | |
| italic? | boolean | |
| letterSpacing? | number |
MeasureParagraphOptions
Options for paragraph measurement
interface MeasureParagraphOptions| Member | Type | Summary |
|---|---|---|
| floatingZones? | FloatingImageZone[] | Floating image exclusion zones that affect line widths |
| paragraphYOffset? | number | Y offset of this paragraph relative to the exclusion zones (default: 0) |
RunMeasurement
Result of measuring a run of text
interface RunMeasurement| Member | Type | Summary |
|---|---|---|
| charWidths | number[] | |
| metrics | FontMetrics | |
| width | number |
StabilizeFootnoteLayoutArgs
interface StabilizeFootnoteLayoutArgs| Member | Type | Summary |
|---|---|---|
| blocks | FlowBlock[] | |
| footnoteContentMap | Map<number, FootnoteContent> | |
| footnoteRefs | Array<{
footnoteId: number;
pmPos: number;
}> | |
| initialLayout | Layout | First-pass layout already computed by the caller without reserved heights. |
| layoutOpts | LayoutOptions | |
| measures | Measure[] |
StabilizeFootnoteLayoutResult
interface StabilizeFootnoteLayoutResult| Member | Type | Summary |
|---|---|---|
| converged | boolean | True if the loop converged before hitting MAX_FOOTNOTE_LAYOUT_PASSES. |
| layout | Layout | |
| pageFootnoteMap | Map<number, number[]> |
TextMeasurement
Result of measuring a text string
interface TextMeasurement| Member | Type | Summary |
|---|---|---|
| ascent | number | |
| descent | number | |
| height | number | |
| width | number |
Type aliases(15)
CaretPosition_2
Caret position for collapsed selection.
type CaretPosition = {
x: number;
y: number;
height: number;
pageIndex: number;
};ConvertFootnoteOptions
Options for [convertFootnoteToContent](convertFootnoteToContent).
type ConvertFootnoteOptions = {
styles?: StyleDefinitions | null;
theme?: Theme | null;
measureBlocks: MeasureBlocksFn;
};FragmentHit
Result of hit-testing to find which fragment contains a point.
type FragmentHit = {
fragment: Fragment;
block: FlowBlock;
measure: Measure;
pageIndex: number;
localY: number;
localX: number;
};HitTestResult
Combined result of all hit testing.
type HitTestResult = {
pageHit: PageHit | null;
fragmentHit: FragmentHit | null;
tableCellHit: TableCellHit | null;
};MeasureBlocksFn
Adapter-supplied block measurement function. The caller (React / Vue / etc.) supplies its platform's measure routine — at minimum paragraph + table + image + textBox — so this core helper stays Canvas-free.
type MeasureBlocksFn = (blocks: FlowBlock[], contentWidth: number) => Measure[];Result of hit-testing to find which page contains a point.
type PageHit = {
pageIndex: number;
page: Page;
pageY: number;
};A 2D point in page coordinate space.
type Point = {
x: number;
y: number;
};PositionResult
Result of click-to-position mapping.
type PositionResult = {
pmPosition: number;
charOffset: number;
lineIndex: number;
};SelectionRect
A rectangle representing part of a selection.
type SelectionRect = {
x: number;
y: number;
width: number;
height: number;
pageIndex: number;
};TableCellHit
Result of hit-testing a table to find the cell.
type TableCellHit = {
fragment: TableFragment;
block: TableBlock;
measure: TableMeasure;
pageIndex: number;
rowIndex: number;
colIndex: number;
cellBlock?: ParagraphBlock;
cellMeasure?: ParagraphMeasure;
cellLocalX: number;
cellLocalY: number;
};TableInsertHoverHit
type TableInsertHoverHit = {
type: 'row' | 'column';
clientX: number;
clientY: number;
cellPmPos: number;
};TableInsertHoverInput
type TableInsertHoverInput = {
mouseX: number;
mouseY: number;
pagesContainer: HTMLElement;
target: HTMLElement;
hfEditMode: 'header' | 'footer' | null;
edgeProximity?: number;
};ToFlowBlocksOptions
Options for the conversion.
type ToFlowBlocksOptions = {
defaultFont?: string;
defaultSize?: number;
theme?: Theme | null;
pageContentHeight?: number;
listCounters?: Map<number, number[]>;
listSeenNumIds?: Set<string>;
};Variables(8)
DEFAULT_BODY_MARGIN_PX
1 inch at 96 DPI — Word's default body margins.
DEFAULT_BODY_MARGIN_PX = 96DEFAULT_HF_DISTANCE_PX
Word's default `headerDistance` / `footerDistance` (0.5in = 48px).
DEFAULT_HF_DISTANCE_PX = 48DEFAULT_PAGE_HEIGHT_PX
US Letter at 96 DPI.
DEFAULT_PAGE_HEIGHT_PX = 1056DEFAULT_PAGE_WIDTH_PX
US Letter at 96 DPI — Word's default page size.
DEFAULT_PAGE_WIDTH_PX = 816FOOTNOTE_SEPARATOR_HEIGHT
Separator line height + vertical padding in pixels.
FOOTNOTE_SEPARATOR_HEIGHT = 12MAX_FOOTNOTE_LAYOUT_PASSES
Hard cap on the multi-pass footnote layout loop. Reserving footnote space can move a reference to another page, so adapters keep remapping until the page→height contract is stable. Dense layouts converge in 2–3 passes in practice; 6 is a safe ceiling.
MAX_FOOTNOTE_LAYOUT_PASSES = 6TABLE_INSERT_EDGE_PROXIMITY
Table-insert "+" hover hit-test.
Pure DOM logic for the floating row/column insert button that shows when the mouse is near the left or top edge of a layout table. Lives in core so React + Vue + any future adapter can share the hit-test and just wire up their own UI rendering of the button.
The function is gated by `hfEditMode`: tables in inactive HF/body regions don't surface the affordance. A header table only matches when the user is editing the header; a body table only matches when not in any HF edit mode.
TABLE_INSERT_EDGE_PROXIMITY = 30TABLE_INSERT_HIDE_DELAY_MS
TABLE_INSERT_HIDE_DELAY_MS = 200