@eigenpal/docx-editor-core/layout-painter
Layout Painter
Main entry point for rendering Layout data to DOM. Provides reconciliation for efficient incremental updates.
Stable enough for the first-party React adapter, but the API may change in minor releases until a third-party adapter validates it. Pin a version range if you depend on this directly.
Functions(18)
captureInlinePositionEmu
Capture the rendered position of an inline image as EMUs, normalised to unzoomed coordinates. Returns horizontal offset relative to the page content area (column origin) and vertical offset relative to the containing paragraph — matches the OOXML attrs the resolver writes (`relativeFrom: 'column'` / `relativeFrom: 'paragraph'`).
`zoom` defaults to 1; pass the editor's current zoom factor when the pages container has a CSS scale applied so `getBoundingClientRect` deltas are converted back to authored-pixel space before going to EMU.
Returns undefined for non-inline images or detached DOM.
declare function captureInlinePositionEmu(imageEl: HTMLElement, zoom?: number): {
horizontalEmu: number;
verticalEmu: number;
} | undefined;createPainter
Create a new LayoutPainter instance
declare function createPainter(options?: PainterOptions): LayoutPainter;deriveLayoutChoice
Map an image's current OOXML attrs onto the menu's choice vocabulary so the menu can highlight the active option. Returns null for `topAndBottom` and any unknown wrap type — those don't have a directional menu entry.
`cssFloat` accepts both `null` and `undefined` so framework adapters can use either as their "unset" sentinel without an extra normalisation step.
declare function deriveLayoutChoice(wrapType: WrapType, cssFloat?: ImageAttrs['cssFloat'] | null): ImageLayoutTarget | null;findImageElement
Walk up from a click target to the nearest rendered image element, returning just the element (no PM position parsing). Used by the left-click selection path in both adapters — `data-pm-start` is read separately by callers that need the position. Returns the element when the click was directly on an inline `<img.layout-run-image>` OR inside one of the container classes registered in `LAYOUT_IMAGE_CLASSES`.
declare function findImageElement(target: EventTarget | null): HTMLElement | null;hitTestImage
Walk up from an event target looking for any rendered image element. Returns the PM position embedded in `data-pm-start`, or null if the target isn't on an image.
declare function hitTestImage(target: EventTarget | null): ImageHitTestResult | null;isFloatingImageRun
Check if an image run is a floating image positioned at page/cell level.
declare function isFloatingImageRun(run: ImageRun): boolean;isImageLayoutOptionEnabled
Whether a given option is enabled for an image with the given current wrap type. Every option stays clickable — picking the option that matches the current state is a no-op (the PM command early-returns), which matches Word's behavior. We don't grey out the current option, so the menu reads consistently regardless of whether the image is inline or anchored.
The flag is kept around for forward-compatibility (e.g. future read-only mode), but currently always returns true.
declare function isImageLayoutOptionEnabled(_option: ImageLayoutOptionDef, _currentWrapType: WrapType): boolean;isTextWrappingFloatingImageRun
Check if a floating image should create text wrapping exclusion zones. wrapNone images (`behind` / `inFront`) are positioned floats but do not shrink line widths; text paints over or under them.
declare function isTextWrappingFloatingImageRun(run: ImageRun): boolean;renderFragment
Render a fragment to DOM
declare function renderFragment(fragment: Fragment, context: RenderContext, options?: RenderFragmentOptions): HTMLElement;renderImageFragment
Render an image fragment to DOM
declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;Render a single line
declare function renderLine(block: ParagraphBlock, line: MeasuredLine, alignment: 'left' | 'center' | 'right' | 'justify' | undefined, doc: Document, options?: RenderLineOptions): HTMLElement;Render a single page to DOM
declare function renderPage(page: Page, context: RenderContext, options?: RenderPageOptions): HTMLElement;Render multiple pages to a container with virtualization for large documents.
For documents with fewer than VIRTUALIZATION_THRESHOLD pages, all pages are rendered eagerly. For larger documents, only pages near the visible viewport are fully rendered — off-screen pages are lightweight shells with correct dimensions to preserve scroll position.
An IntersectionObserver watches page elements and populates/clears content as pages scroll into and out of view.
declare function renderPages(pages: Page[], container: HTMLElement, options?: RenderPageOptions & {
pageGap?: number;
footnotesByPage?: Map<number, FootnoteRenderItem[]>;
}): RenderPagesUpdateKind;renderParagraphFragment
Render a paragraph fragment
declare function renderParagraphFragment(fragment: ParagraphFragment, block: ParagraphBlock, measure: ParagraphMeasure, context: RenderContext, options?: RenderParagraphOptions): HTMLElement;renderTableFragment
Render a table fragment to DOM
declare function renderTableFragment(fragment: TableFragment, block: TableBlock, measure: TableMeasure, context: RenderContext, options?: RenderTableFragmentOptions): HTMLElement;renderTextBoxFragment
Render a text box fragment to DOM
declare function renderTextBoxFragment(fragment: TextBoxFragment, block: TextBoxBlock, measure: TextBoxMeasure, context: RenderContext, options?: RenderTextBoxFragmentOptions): HTMLElement;sliceRunsForLine
Slice runs for a specific line
declare function sliceRunsForLine(block: ParagraphBlock, line: MeasuredLine): Run[];toolbarValueToLayoutTarget
Translate the legacy toolbar wrap-type vocabulary (`wrapLeft` / `wrapRight` / `square` / `tight` / `through` / `topAndBottom` / `behind` / `inFront` / `inline`) into a `ImageLayoutTarget` so toolbar dispatch shares the same PM command path as the right-click menu.
Returns `undefined` for unknown values; callers should treat that as "no-op".
declare function toolbarValueToLayoutTarget(value: string): ImageLayoutTarget | undefined;Classes(1)
LayoutPainter
Layout Painter class
Renders Layout data to DOM with efficient reconciliation. Only updates changed pages and fragments for better performance.
declare class LayoutPainter| Member | Type | Summary |
|---|---|---|
| (constructor) | — | Constructs a new instance of the `LayoutPainter` class |
| getPageCount | — | Get the current page count |
| getPageElement | — | Get a page element by index |
| mount | — | Mount the painter to a container element |
| paint | — | Paint a layout to the container |
| resolvedCommentIds | Set<number> | |
| scrollToPage | — | Scroll to a specific page |
| setBlockLookup | — | Set the block lookup map for rendering fragments |
| unmount | — | Unmount the painter |
Interfaces(8)
BlockLookupEntry
Block lookup entry for painter
interface BlockLookupEntry| Member | Type | Summary |
|---|---|---|
| block | FlowBlock | |
| measure | Measure | |
| version? | string |
FootnoteRenderItem
A single footnote item ready for rendering at page bottom.
interface FootnoteRenderItem| Member | Type | Summary |
|---|---|---|
| content? | FootnoteContent | Measured body-pipeline content used for WYSIWYG painting. |
| displayNumber | string | Display number (e.g. "1", "2") |
| text | string | Plain text content |
ImageHitTestResult
interface ImageHitTestResult| Member | Type | Summary |
|---|---|---|
| imageEl | HTMLElement | The matched element — pass to `captureInlinePositionEmu` if it's inline. |
| pos | number | PM doc position of the image node, read from `data-pm-start`. |
ImageLayoutOptionDef
interface ImageLayoutOptionDef| Member | Type | Summary |
|---|---|---|
| choice | ImageLayoutTarget | Choice value — what gets dispatched on click. |
| i18nDescKey | string | i18n key under `imageWrap.menuDesc.*`. |
| i18nLabelKey | string | i18n key under `imageWrap.menu.*`. |
| iconHint | ImageLayoutIconHint | Hint for the framework's icon registry. |
PainterOptions
Painter options
interface PainterOptions| Member | Type | Summary |
|---|---|---|
| containerBackground? | string | Container background color |
| document? | Document | Document to create elements in |
| pageBackground? | string | Background color for pages |
| pageGap? | number | Gap between pages in pixels |
| showShadow? | boolean | Show page shadows |
RenderContext
Context passed to fragment renderers
interface RenderContext| Member | Type | Summary |
|---|---|---|
| contentWidth? | number | Content width in pixels (page width minus margins) - used for justify |
| insideTableCell? | boolean | When true, floating images render in-flow instead of being skipped (for table cells) |
| pageNumber | number | Current page number (1-indexed) |
| positioning? | 'absolute' | 'flow' | How the renderer should position its outer element. The body lays fragments at absolute (x, y) on the page (`'absolute'`, the default), while headers/footers and text boxes flow blocks vertically and let normal document flow handle placement (`'flow'`). The caller passes 'flow' instead of overwriting the renderer's inline styles after the fact (#379). |
| resolvedCommentIds? | Set<number> | Comment IDs that are resolved — skip highlight for these |
| section | 'body' | 'header' | 'footer' | Which section is being rendered |
| totalPages | number | Total number of pages |
RenderPageOptions
Options for rendering a page
interface RenderPageOptions| Member | Type | Summary |
|---|---|---|
| backgroundColor? | string | Background color for pages |
| blockLookup? | BlockLookup | Block lookup for rendering actual content. |
| document? | Document | Document to create elements in (default: window.document) |
| firstPageFooterContent? | HeaderFooterContent | Footer content for the first page only (when titlePg is set). |
| firstPageHeaderContent? | HeaderFooterContent | Header content for the first page only (when titlePg is set). |
| footerContent? | HeaderFooterContent | Footer content to render (used for all pages, or pages 2+ when titlePg is set). |
| footerDistance? | number | Distance from page bottom to footer content. |
| footnoteArea? | FootnoteRenderItem[] | Footnotes to render at the bottom of this page. |
| headerContent? | HeaderFooterContent | Header content to render (used for all pages, or pages 2+ when titlePg is set). |
| headerDistance? | number | Distance from page top to header content. |
| pageBorders? | {
top?: BorderSpec;
bottom?: BorderSpec;
left?: BorderSpec;
right?: BorderSpec;
display?: 'allPages' | 'firstPage' | 'notFirstPage';
offsetFrom?: 'page' | 'text';
zOrder?: 'front' | 'back';
} | OOXML page borders from section properties. |
| pageClassName? | string | Custom page class name |
| resolvedCommentIds? | Set<number> | Comment IDs that are resolved — skip highlight for these |
| showBorders? | boolean | Show page borders (for debugging) |
| showShadow? | boolean | Drop shadow on pages |
| theme? | Theme | null | Theme for resolving border colors. |
| titlePg? | boolean | Whether different first page headers/footers are enabled (w:titlePg). |
Type aliases(3)
BlockLookup
Block lookup map type
type BlockLookup = Map<string, BlockLookupEntry>;ImageLayoutIconHint
Hint to the framework's icon registry for which Material Symbol — or equivalent — to render alongside each option. Bindings own the icon component itself.
type ImageLayoutIconHint = 'inline' | 'squareLeft' | 'squareRight' | 'behind' | 'inFront';RenderPagesUpdateKind
Multi-page rendering with virtualization.
For documents under VIRTUALIZATION_THRESHOLD pages, all pages render eagerly. Larger documents render only pages near the viewport — off-screen pages are lightweight shells (correct dimensions, no fragment content) so scroll position is preserved. An IntersectionObserver populates and clears page content as the user scrolls. Incremental updates (re-rendering only fingerprint-changed pages) avoid blink when the document model shifts.
type RenderPagesUpdateKind = 'incremental' | 'full';Variables(6)
FRAGMENT_CLASS_NAMES
CSS class names for fragment elements
FRAGMENT_CLASS_NAMES: {
fragment: string;
paragraph: string;
table: string;
image: string;
line: string;
run: string;
}IMAGE_CLASS_NAMES
CSS class names for image elements
IMAGE_CLASS_NAMES: {
image: string;
imageAnchored: string;
}IMAGE_LAYOUT_OPTIONS
Mirrors Word's Wrap Text menu — five directional options.
IMAGE_LAYOUT_OPTIONS: readonly ImageLayoutOptionDef[]LAYOUT_IMAGE_CLASSES
Image layout helpers shared between framework adapters (React, Vue, ...).
Everything here is framework-agnostic: pure DOM math + pure functions over OOXML wrap-type vocabulary. The corresponding UI bindings (right-click menu, toolbar dropdown) live in each framework adapter and call into these.
LAYOUT_IMAGE_CLASSES: {
readonly runImage: "layout-run-image";
readonly blockImage: "layout-block-image";
readonly pageFloatingImage: "layout-page-floating-image";
readonly cellFloatingImage: "layout-cell-floating-image";
readonly pageContent: "layout-page-content";
readonly paragraph: "layout-paragraph";
}TABLE_CLASS_NAMES
CSS class names for table elements
TABLE_CLASS_NAMES: {
table: string;
row: string;
cell: string;
cellContent: string;
resizeHandle: string;
rowResizeHandle: string;
tableEdgeHandleBottom: string;
tableEdgeHandleRight: string;
}TEXTBOX_CLASS_NAMES
CSS class names for text box elements
TEXTBOX_CLASS_NAMES: {
textBox: string;
}