New

docx-editor 1.x has shipped. Vue support, i18n, agents. Read the migration guide →

API Referencev1.0.2

@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)

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[];

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)

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>;

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[]>;

Calculate per-page footnote reserved heights. Returns MappageNumber, reservedHeight.

declare function calculateFootnoteReservedHeights(pageFootnoteMap: Map<number, number[]>, footnoteContentMap: Map<number, {
    height: number;
}>): Map<number, number>;
declare function calculateHeaderFooterVisualBounds(blocks: FlowBlock[], measures: Measure[], flowHeight: number, metrics: HeaderFooterMetrics): {
    visualTop: number;
    visualBottom: number;
};

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;
};

Clear all measurement caches Call when fonts change, page width changes, or for testing

declare function clearAllCaches(): void;

Clear the font metrics cache

declare function clearFontMetricsCache(): void;

Clear the paragraph measure cache

declare function clearParagraphMeasureCache(): void;

Clear the text width cache

declare function clearTextWidthCache(): void;

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;

Find ProseMirror position from a click using DOM-based detection.

declare function clickToPositionDom(container: HTMLElement, clientX: number, clientY: number, zoom?: number): number | null;

Map a click within a paragraph fragment to a PM position.

declare function clickToPositionInParagraph(fragmentHit: FragmentHit): PositionResult | null;

Map a click within a table cell to a PM position.

declare function clickToPositionInTableCell(tableCellHit: TableCellHit): number | null;

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;
}>;

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;

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;

Convert HeaderFooter (document type) to HeaderFooterContent (render type).

Routes through the same pipeline as the body: HF.content → headerFooterToProseDoc → toFlowBlocks → measureBlocks. The inline editor uses the same conversion chain, so block support (paragraph, table, image, textBox, fields) and the inline editor's content stay in lockstep.

declare function convertHeaderFooterToContent(headerFooter: HeaderFooter | null | undefined, contentWidth: number, metrics: HeaderFooterMetrics, options: ConvertHeaderFooterOptions): HeaderFooterContent | undefined;

Total grid columns, derived from the widest row's accumulated colSpans.

declare function countTableColumns(tableBlock: TableBlock): number;

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;

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[];

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;

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[];

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[];

Find the character offset at a given X position within a text run

declare function findCharacterAtX(x: number, charWidths: number[]): number;

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;

Get cached font metrics or return undefined

declare function getCachedFontMetrics(fontFamily: string, fontSize: number, bold?: boolean, italic?: boolean): FontMetricsEntry | undefined;

Get cached paragraph measurement or return undefined

declare function getCachedParagraphMeasure(block: ParagraphBlock, maxWidth: number): ParagraphMeasure | undefined;

Get cached text width or return undefined

declare function getCachedTextWidth(text: string, font: string, letterSpacing?: number): number | undefined;

Get or create a canvas 2D context for text measurement

declare function getCanvasContext(): CanvasRenderingContext2D;

Get caret position for a collapsed selection.

declare function getCaretPosition(layout: Layout, blocks: FlowBlock[], measures: Measure[], pmPosition: number): CaretPosition | null;
declare function getCaretPositionFromDom(container: HTMLElement, pmPos: number, overlayRect: DOMRect): DomCaretPosition | null;
declare function getFloatingMargins(lineY: number, lineHeight: number, zones: FloatingImageZone[] | undefined, paragraphYOffset: number): FloatingLineMargins;

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;

Get page bounds (top and bottom Y coordinates).

declare function getPageBounds(layout: Layout, pageIndex: number): {
    top: number;
    bottom: number;
} | null;

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;
};

Calculate the Y offset of a page from the top of the document.

declare function getPageTop(layout: Layout, pageIndex: number): number;

Get current paragraph measure cache size

declare function getParagraphCacheSize(): number;

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;

Get per-character widths for a text run (for click positioning)

declare function getRunCharWidths(run: TextRun): number[];

Get Y coordinate to scroll to for a specific page.

declare function getScrollYForPage(layout: Layout, pageIndex: number): number;
declare function getSelectionRectsFromDom(container: HTMLElement, from: number, to: number, overlayRect: DOMRect): DomSelectionRect[];

Get current text width cache size

declare function getTextCacheSize(): number;

Get total size of all caches

declare function getTotalCacheSize(): number;

Get the total document height (all pages + gaps).

declare function getTotalDocumentHeight(layout: Layout): number;

Get the X position of a character offset within a text run

declare function getXForCharacter(offset: number, charWidths: number[]): number;

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;

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;

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;

Hit-test to find image fragments (they may overlap other content).

declare function hitTestImageFragment(pageHit: PageHit, blocks: FlowBlock[], measures: Measure[], pagePoint: Point): FragmentHit | null;

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;

Hit-test within a table fragment to find the specific cell.

declare function hitTestTableCell(pageHit: PageHit, blocks: FlowBlock[], measures: Measure[], pagePoint: Point): TableCellHit | null;

Check if a selection spans multiple pages.

declare function isMultiPageSelection(rects: SelectionRect[]): boolean;

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[]>;

Measure a paragraph block and compute line breaks

declare function measureParagraph(block: ParagraphBlock, maxWidth: number, options?: MeasureParagraphOptions): ParagraphMeasure;

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;

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;
fn

measureTableCellBlockVisualHeight

packages/core/src/layout-bridge/index.ts:130

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;

Measure the width of a text string with specific styling

declare function measureTextWidth(text: string, style: FontStyle): number;
declare function normalizeHeaderFooterMeasureBlocks(blocks: FlowBlock[]): FlowBlock[];

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;
declare function rectsToFloatingZones(rects: FloatingExclusionRect[], contentWidth: number): FloatingImageZone[];

Reset the block ID counter (useful for testing).

declare function resetBlockIdCounter(): void;

Reset the canvas context (useful for testing)

declare function resetCanvasContext(): void;

Resolve the HeaderFooter pair (default + first-page) for a section.

Mirrors React's lookup in DocxEditor.tsx: read `pkg.headers`/`footers` (Maps keyed by rId), resolve through `sp.headerReferences` / `footerReferences`. When `titlePg` is unset and only `first` HFs exist, they serve as the default — same Word fallback both adapters have shipped.

declare function resolveHeaderFooter(doc: Document | null, sp: SectionProperties | null | undefined): {
    header: HeaderFooter | null;
    footer: HeaderFooter | null;
    firstHeader: HeaderFooter | null;
    firstFooter: HeaderFooter | null;
};
declare function resolveHeaderFooterVisualTop(run: ImageRun, paragraphY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;

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;

Convert a ProseMirror selection range to screen rectangles.

declare function selectionToRects(layout: Layout, blocks: FlowBlock[], measures: Measure[], from: number, to: number): SelectionRect[];

Store font metrics in cache

declare function setCachedFontMetrics(fontFamily: string, fontSize: number, bold: boolean, italic: boolean, metrics: FontMetricsEntry): void;

Store paragraph measurement in cache

declare function setCachedParagraphMeasure(block: ParagraphBlock, maxWidth: number, measure: ParagraphMeasure): void;

Store text width in cache

declare function setCachedTextWidth(text: string, font: string, letterSpacing: number, width: number): void;

Set the maximum size of the font metrics cache

declare function setFontCacheSize(size: number): void;

Set the maximum size of the paragraph measure cache

declare function setParagraphCacheSize(size: number): void;

Set the maximum size of the text width cache

declare function setTextCacheSize(size: number): void;

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;

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)

Get caret position from DOM for a PM position.

interface DomCaretPosition
MemberTypeSummary
heightnumber
pageIndexnumber
xnumber
ynumber

Get selection rectangles for a PM range using DOM-based detection.

interface DomSelectionRect
MemberTypeSummary
heightnumber
pageIndexnumber
widthnumber
xnumber
ynumber
interface FloatingExclusionRect
MemberTypeSummary
distBottomnumber
distLeftnumber
distRightnumber
distTopnumber
heightnumber
side'left' | 'right'Which side the object is on for simple one-sided wrapping.
widthnumber
wrapText?WrapTextDirection
wrapType?string
xnumberX position relative to the content area.
ynumberY position relative to the content area.
interface FloatingImageZone
MemberTypeSummary
bottomYnumber
leftMarginnumber
rightMarginnumber
segments?FloatingLineSegmentZone[]
topYnumber
interface FloatingLineSegmentZone
MemberTypeSummary
availableWidthnumber
leftOffsetnumber

Typography metrics for a font

interface FontMetrics
MemberTypeSummary
ascentnumber
descentnumber
fontFamilystring
fontSizenumber
lineHeightnumber
singleLineRationumberOS/2 single-line ratio for OOXML line spacing calculation

Font styling properties for measurement

interface FontStyle
MemberTypeSummary
bold?boolean
fontFamily?string
fontSize?number
italic?boolean
letterSpacing?number

Options for paragraph measurement

interface MeasureParagraphOptions
MemberTypeSummary
floatingZones?FloatingImageZone[]Floating image exclusion zones that affect line widths
paragraphYOffset?numberY offset of this paragraph relative to the exclusion zones (default: 0)

Result of measuring a run of text

interface RunMeasurement
MemberTypeSummary
charWidthsnumber[]
metricsFontMetrics
widthnumber
interface StabilizeFootnoteLayoutArgs
MemberTypeSummary
blocksFlowBlock[]
footnoteContentMapMap<number, FootnoteContent>
footnoteRefsArray<{ footnoteId: number; pmPos: number; }>
initialLayoutLayoutFirst-pass layout already computed by the caller without reserved heights.
layoutOptsLayoutOptions
measuresMeasure[]
interface

StabilizeFootnoteLayoutResult

packages/core/src/layout-bridge/footnoteLayout.ts:360
interface StabilizeFootnoteLayoutResult
MemberTypeSummary
convergedbooleanTrue if the loop converged before hitting MAX_FOOTNOTE_LAYOUT_PASSES.
layoutLayout
pageFootnoteMapMap<number, number[]>

Result of measuring a text string

interface TextMeasurement
MemberTypeSummary
ascentnumber
descentnumber
heightnumber
widthnumber

Type aliases(15)

type

CaretPosition_2

Caret position for collapsed selection.

type CaretPosition = {
    x: number;
    y: number;
    height: number;
    pageIndex: number;
};

Options for [convertFootnoteToContent](convertFootnoteToContent).

type ConvertFootnoteOptions = {
    styles?: StyleDefinitions | null;
    theme?: Theme | null;
    measureBlocks: MeasureBlocksFn;
};
type ConvertHeaderFooterOptions = {
    styles?: StyleDefinitions | null;
    theme?: Theme | null;
    measureBlocks: MeasureBlocksFn;
};

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;
};

Header / Footer Layout Utilities

The header/footer rendering pipeline lives here so any rendering adapter (React, Vue, etc.) can share the conversion logic and just supply its platform-specific [MeasureBlocksFn](MeasureBlocksFn). Mirrors the footnote pipeline in `footnoteLayout.ts`.

Pipeline: HF.content → headerFooterToProseDoc → toFlowBlocks → measureBlocks (caller-supplied, Canvas-aware) → HeaderFooterContent (blocks, measures, height, visualTop/Bottom)

The render side uses the normalized block list so paint and measurement stay in lockstep. Visual-bounds calculation still inspects the original block list because floating images can paint above/below the nominal flow box even when they do not contribute to flow height.

type HeaderFooterMetrics = {
    section: 'header' | 'footer';
    pageSize: {
        w: number;
        h: number;
    };
    margins: PageMargins;
};

Combined result of all hit testing.

type HitTestResult = {
    pageHit: PageHit | null;
    fragmentHit: FragmentHit | null;
    tableCellHit: TableCellHit | null;
};

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;
};

Result of click-to-position mapping.

type PositionResult = {
    pmPosition: number;
    charOffset: number;
    lineIndex: number;
};

A rectangle representing part of a selection.

type SelectionRect = {
    x: number;
    y: number;
    width: number;
    height: number;
    pageIndex: number;
};

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;
};
type TableInsertHoverHit = {
    type: 'row' | 'column';
    clientX: number;
    clientY: number;
    cellPmPos: number;
};
type TableInsertHoverInput = {
    mouseX: number;
    mouseY: number;
    pagesContainer: HTMLElement;
    target: HTMLElement;
    hfEditMode: 'header' | 'footer' | null;
    edgeProximity?: number;
};

Options for the conversion.

type ToFlowBlocksOptions = {
    defaultFont?: string;
    defaultSize?: number;
    theme?: Theme | null;
    pageContentHeight?: number;
    listCounters?: Map<number, number[]>;
    listSeenNumIds?: Set<string>;
};

Variables(8)

1 inch at 96 DPI — Word's default body margins.

DEFAULT_BODY_MARGIN_PX = 96

Word's default `headerDistance` / `footerDistance` (0.5in = 48px).

DEFAULT_HF_DISTANCE_PX = 48

US Letter at 96 DPI.

DEFAULT_PAGE_HEIGHT_PX = 1056

US Letter at 96 DPI — Word's default page size.

DEFAULT_PAGE_WIDTH_PX = 816

Separator line height + vertical padding in pixels.

FOOTNOTE_SEPARATOR_HEIGHT = 12

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 = 6

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 = 30
TABLE_INSERT_HIDE_DELAY_MS = 200