@eigenpal/docx-editor-core/layout-painter/renderPage
Page Renderer
Renders a single page from Layout data to DOM elements. Each page contains positioned fragments within a content area.
This file owns the single-page orchestrator (`renderPage`) plus page-level styling (background, borders, content area) and floating-image extraction from paragraphs. Header/footer rendering lives in ./renderPage/headerFooter.ts, footnote area rendering in ./renderPage/footnotes.ts, and the multi-page virtualization / IntersectionObserver layer in ./renderPage/virtualization.ts.
Functions(23)
applyPageStyles
Apply page styles to an element. Exported because virtualization.ts uses it to size lightweight shells before content lands in them.
declare function applyPageStyles(element: HTMLElement, width: number, height: number, options: RenderPageOptions): void;floatingImageIsBehindDoc
declare function floatingImageIsBehindDoc(img: {
wrapType?: string;
}): boolean;floatingImageWrapsText
Whether a floating image record reserves space in the text-wrap calculation. Records reaching this predicate have already passed `isFloatingImageRun`, so `wrapType=undefined` implies a `cssFloat`-driven float that wraps text.
declare function floatingImageWrapsText(img: {
wrapType?: string;
}): boolean;h
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;i
Create a new LayoutPainter instance
declare function createPainter(options?: PainterOptions): LayoutPainter;isFloatingImageRun
Check if an image run is a floating image positioned at page/cell level.
declare function isFloatingImageRun(run: ImageRun): 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;j
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;k
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;l
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;m
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;n
Render an image fragment to DOM
declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;o
Render a single line
declare function renderLine(block: ParagraphBlock, line: MeasuredLine, alignment: 'left' | 'center' | 'right' | 'justify' | undefined, doc: Document, options?: RenderLineOptions): HTMLElement;p
Render a paragraph fragment
declare function renderParagraphFragment(fragment: ParagraphFragment, block: ParagraphBlock, measure: ParagraphMeasure, context: RenderContext, options?: RenderParagraphOptions): HTMLElement;q
Render a table fragment to DOM
declare function renderTableFragment(fragment: TableFragment, block: TableBlock, measure: TableMeasure, context: RenderContext, options?: RenderTableFragmentOptions): HTMLElement;r
Render a fragment to DOM
declare function renderFragment(fragment: Fragment, context: RenderContext, options?: RenderFragmentOptions): HTMLElement;renderFloatingImagesLayer
Render a layer of positioned floating images. Used at both page level and inside table cells; the variant differs only in class names and sizing.
declare function renderFloatingImagesLayer(floatingImages: FloatingImagePaintRecord[], doc: Document, options: FloatingImagesLayerOptions): 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;s
Render a text box fragment to DOM
declare function renderTextBoxFragment(fragment: TextBoxFragment, block: TextBoxBlock, measure: TextBoxMeasure, context: RenderContext, options?: RenderTextBoxFragmentOptions): HTMLElement;t
Slice runs for a specific line
declare function sliceRunsForLine(block: ParagraphBlock, line: MeasuredLine): Run[];u
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)
f
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(11)
a
Block lookup entry for painter
interface BlockLookupEntry| Member | Type | Summary |
|---|---|---|
| block | FlowBlock | |
| measure | Measure | |
| version? | string |
c
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`. |
e
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. |
FloatingImagePaintRecord
Minimum fields the floating-image painter needs. Page-level and cell-level float records both satisfy this shape.
interface FloatingImagePaintRecord| Member | Type | Summary |
|---|---|---|
| alt? | string | |
| cropBottom? | number | |
| cropLeft? | number | |
| cropRight? | number | |
| cropTop? | number | wp:srcRect crop fractions in [0, 1]. |
| height | number | |
| opacity? | number | a:alphaModFix - CSS opacity. |
| pmEnd? | number | |
| pmStart? | number | |
| src | string | |
| transform? | string | |
| width | number | |
| x | number | |
| y | number |
FloatingImagesLayerOptions
interface FloatingImagesLayerOptions| Member | Type | Summary |
|---|---|---|
| itemClass | string | |
| layerClass | string | |
| layerMode | 'front' | 'behind' | `behind` skips z-index so DOM order keeps the layer below body fragments. |
| sizing | 'inset0' | 'fullSize' | `inset0` sizes the layer with `top/right/bottom/left = 0` (used at page level). `fullSize` uses `width/height = 100%` and adds `overflow: hidden` (used inside table cells). |
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 |
P
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)
B
Block lookup map type
type BlockLookup = Map<string, BlockLookupEntry>;d
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(7)
b
Mirrors Word's Wrap Text menu — five directional options.
IMAGE_LAYOUT_OPTIONS: readonly ImageLayoutOptionDef[]F
CSS class names for fragment elements
FRAGMENT_CLASS_NAMES: {
fragment: string;
paragraph: string;
table: string;
image: string;
line: string;
run: string;
}g
CSS class names for text box elements
TEXTBOX_CLASS_NAMES: {
textBox: string;
}I
CSS class names for image elements
IMAGE_CLASS_NAMES: {
image: string;
imageAnchored: string;
}L
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";
}PAGE_CLASS_NAMES
CSS class names for page elements
PAGE_CLASS_NAMES: {
page: string;
content: string;
header: string;
footer: string;
}T
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;
}