import { FlowMode, Fragment, PageMargins, SourceAnchor, ResolvedLayout, ResolvedPage, ResolvedPaintItem, LayoutSourceIdentity, LayoutStoryLocator } from '../../../contracts/src/index.js'; import { PageStyles } from './styles.js'; import { PaintSnapshotStructuredContentBlockEntity, PaintSnapshotStructuredContentInlineEntity } from './sdt/snapshot.js'; export type { PaintSnapshotStructuredContentBlockEntity, PaintSnapshotStructuredContentInlineEntity, } from './sdt/snapshot.js'; /** * Layout mode for document rendering. * @typedef {('vertical'|'horizontal'|'book')} LayoutMode * - 'vertical': Standard page-by-page vertical layout (default) * - 'horizontal': Pages arranged horizontally side-by-side * - 'book': Book-style layout with facing pages */ export type LayoutMode = 'vertical' | 'horizontal' | 'book'; export type { FlowMode } from '../../../contracts/src/index.js'; /** * Interface for position mapping from ProseMirror transactions. * Used to efficiently update DOM position attributes without full re-render. */ export interface PositionMapping { /** Transform a position from old to new document coordinates */ map(pos: number, bias?: number): number; /** Array of step maps - length indicates transaction complexity */ readonly maps: readonly unknown[]; } export type RenderedLineInfo = { el: HTMLElement; top: number; height: number; }; /** * Input to `DomPainter.paint()`. * * The painter consumes only `resolvedLayout`. All fragment, geometry, and * page-level metadata it needs is reachable from `ResolvedPaintItem.fragment` * back-pointers and `ResolvedPage` fields. */ export type DomPainterInput = { resolvedLayout: ResolvedLayout; }; export type PageDecorationPayload = { fragments: Fragment[]; /** Resolved items aligned 1:1 with `fragments`. Same length, same order. */ items: ResolvedPaintItem[]; /** Minimum Y coordinate from layout; negative when content extends above y=0. */ minY?: number; height: number; /** Optional measured content height to aid bottom alignment in footers. */ contentHeight?: number; /** Decoration band origin in page-local Y. Producer is the sole source of truth (SD-2957). */ offset: number; marginLeft?: number; contentWidth?: number; headerFooterRefId?: string; sectionType?: string; /** True while this rendered header/footer story is the active editing surface. */ isActiveHeaderFooter?: boolean; box?: { x: number; y: number; width: number; height: number; }; hitRegion?: { x: number; y: number; width: number; height: number; }; }; /** * Provider function for page decorations (headers and footers). * Called for each page to generate header or footer content. * * @param {number} pageNumber - The page number (1-indexed) * @param {PageMargins} [pageMargins] - Page margin configuration * @param {ResolvedPage} [page] - Resolved page from the layout * @returns {PageDecorationPayload | null} Decoration payload containing fragments and layout info, or null if no decoration */ export type PageDecorationProvider = (pageNumber: number, pageMargins?: PageMargins, page?: ResolvedPage) => PageDecorationPayload | null; /** * Ruler configuration options for per-page rulers. */ export type RulerOptions = { /** Whether to show rulers on pages (default: false) */ enabled?: boolean; /** Whether rulers are interactive with drag handles (default: false for per-page) */ interactive?: boolean; /** Callback when margin handle drag ends (only used if interactive) */ onMarginChange?: (side: 'left' | 'right', marginInches: number) => void; }; type PainterOptions = { pageStyles?: PageStyles; layoutMode?: LayoutMode; flowMode?: FlowMode; /** Gap between pages in pixels (default: 24px for vertical, 20px for horizontal) */ pageGap?: number; headerProvider?: PageDecorationProvider; footerProvider?: PageDecorationProvider; virtualization?: { enabled?: boolean; window?: number; overscan?: number; /** Virtualization gap override (defaults to 72px; independent of pageGap). */ gap?: number; paddingTop?: number; }; /** Per-page ruler options */ ruler?: RulerOptions; /** Called with the paint snapshot after each paint cycle completes. */ onPaintSnapshot?: (snapshot: PaintSnapshot) => void; /** Render nonprinting formatting marks such as spaces, tabs, and paragraph marks. */ showFormattingMarks?: boolean; /** Built-in SDT chrome rendering mode. */ contentControlsChrome?: 'default' | 'none'; }; /** * Rendering context passed to fragment renderers containing page metadata. * Provides information about the current page position and section for dynamic content like page numbers. * * @typedef {Object} FragmentRenderContext * @property {number} pageNumber - Current page number (1-indexed) * @property {number} totalPages - Total number of pages in the document * @property {'body'|'header'|'footer'} section - Document section being rendered * @property {string} [pageNumberText] - Optional formatted page number text (e.g., "Page 1 of 10") */ export type FragmentRenderContext = { pageNumber: number; totalPages: number; section: 'body' | 'header' | 'footer'; story?: LayoutStoryLocator; pageNumberText?: string; pageIndex?: number; }; export type PaintSnapshotLineStyle = { paddingLeftPx?: number; paddingRightPx?: number; textIndentPx?: number; marginLeftPx?: number; marginRightPx?: number; leftPx?: number; topPx?: number; widthPx?: number; heightPx?: number; display?: string; position?: string; textAlign?: string; justifyContent?: string; }; export type PaintSnapshotMarkerStyle = { text?: string; leftPx?: number; widthPx?: number; paddingRightPx?: number; display?: string; position?: string; textAlign?: string; fontWeight?: string; fontStyle?: string; color?: string; sourceAnchor?: SourceAnchor; }; export type PaintSnapshotTabStyle = { widthPx?: number; leftPx?: number; position?: string; borderBottom?: string; }; export type PaintSnapshotAnnotationEntity = { element: HTMLElement; pageIndex: number; pmStart?: number; pmEnd?: number; fieldId?: string; fieldType?: string; type?: string; layoutSourceIdentity?: LayoutSourceIdentity; }; export type PaintSnapshotImageEntity = { element: HTMLElement; pageIndex: number; kind: 'inline' | 'fragment'; pmStart?: number; pmEnd?: number; blockId?: string; sourceAnchor?: SourceAnchor; layoutSourceIdentity?: LayoutSourceIdentity; }; export type PaintSnapshotEntities = { annotations: PaintSnapshotAnnotationEntity[]; structuredContentBlocks: PaintSnapshotStructuredContentBlockEntity[]; structuredContentInlines: PaintSnapshotStructuredContentInlineEntity[]; images: PaintSnapshotImageEntity[]; }; export type PaintSnapshotLine = { index: number; inTableFragment: boolean; inTableParagraph: boolean; style: PaintSnapshotLineStyle; markers?: PaintSnapshotMarkerStyle[]; tabs?: PaintSnapshotTabStyle[]; sourceAnchor?: SourceAnchor; layoutSourceIdentity?: LayoutSourceIdentity; }; export type PaintSnapshotPage = { index: number; pageNumber?: number; lineCount: number; lines: PaintSnapshotLine[]; }; export type PaintSnapshot = { formatVersion: 1; pageCount: number; lineCount: number; markerCount: number; tabCount: number; pages: PaintSnapshotPage[]; entities: PaintSnapshotEntities; }; /** * Stamp the editor-neutral layout-identity dataset (prep-001). * * Additive only — runs alongside the legacy `data-pm-*` / `data-block-id` * writes in `applyFragmentFrame` and `applyResolvedFragmentFrame`. v1 * consumers still read PM-shaped datasets; future editor-neutral consumers * read `data-layout-fragment-id` / `data-layout-story` / `data-layout-block-ref` * here. */ export declare function applyLayoutIdentityDataset(element: HTMLElement, identity: LayoutSourceIdentity | undefined): void; /** * DOM-based document painter that renders layout fragments to HTML elements. * Manages page rendering, virtualization, headers/footers, and incremental updates. * * @class DomPainter * * @remarks * The DomPainter is responsible for: * - Rendering layout fragments (paragraphs, lists, images, tables, drawings) to DOM elements * - Managing page-level DOM structure and styling * - Providing virtualization for large documents (vertical mode only) * - Handling headers and footers via PageDecorationProvider * - Incremental re-rendering when only specific blocks change * - Hyperlink rendering with security sanitization and accessibility * * @example * ```typescript * const painter = new DomPainter(blocks, measures, { * layoutMode: 'vertical', * pageStyles: { width: '8.5in', height: '11in' } * }); * painter.mount(document.getElementById('editor-container')); * painter.render(layout); * ``` */ export declare class DomPainter { private readonly options; private mount; private doc; private pageStates; private currentLayout; private changedBlocks; private readonly layoutMode; private readonly isSemanticFlow; private headerProvider?; private footerProvider?; private totalPages; private linkIdCounter; private sdtLabelsRendered; /** * WeakMap storing tooltip data for hyperlink elements before DOM insertion. * Uses WeakMap to prevent memory leaks - entries are automatically garbage collected * when the corresponding element is removed from memory. * @private */ private pendingTooltips; private pageGap; private virtualEnabled; private virtualWindow; private virtualOverscan; private virtualGap; private virtualPaddingTop; private topSpacerEl; private bottomSpacerEl; private virtualPagesEl; private virtualGapSpacers; private virtualPinnedPages; private virtualMountedKey; private pageIndexToState; private virtualHeights; private virtualOffsets; private virtualStart; private virtualEnd; private layoutVersion; private layoutEpoch; private processedLayoutVersion; /** Current transaction mapping for position updates (null if no mapping or complex transaction) */ private currentMapping; private onScrollHandler; private onWindowScrollHandler; private onResizeHandler; /** CSS zoom/scale factor applied to the mount element via transform: scale(). Defaults to 1 (no zoom). */ private zoomFactor; /** * External scroll container (an ancestor element with overflow-y: auto/scroll). * When set, updateVirtualWindow() uses this element's position to compute scrollY * relative to the scroll container instead of relative to the browser viewport. * This fixes the scroll offset calculation when SuperDoc is mounted inside a * wrapper div that owns scrolling rather than the window. */ private scrollContainer; /** * Cached offset (in px) from the scroll container's content top to the mount's top. * Used for stable scrollY calculation that avoids feedback loops from spacer DOM mutations. * Invalidated when the mount, scroll container, or zoom changes. */ private scrollContainerMountOffset; private paintSnapshotBuilder; private lastPaintSnapshot; private onPaintSnapshotCallback; private mountedPageIndices; /** Resolved layout for the next-gen paint pipeline. */ private resolvedLayout; private showFormattingMarks; private contentControlsChrome; constructor(options?: PainterOptions); setShowFormattingMarks(showFormattingMarks: boolean): void; setProviders(header?: PageDecorationProvider, footer?: PageDecorationProvider): void; private applyFormattingMarksClass; private invalidateRenderedContent; /** * Pins specific page indices so they remain mounted when virtualization is enabled. * * Used by selection/drag logic to ensure endpoints can be resolved via DOM * even when they fall outside the current scroll window. */ setVirtualizationPins(pageIndices: number[] | null | undefined): void; /** * Sets the CSS zoom/scale factor applied to the mount element. * * When the mount element has `transform: scale(zoom)`, getBoundingClientRect() * returns screen-space coordinates (scaled), but internal layout offsets are in * unscaled layout space. This factor is used to convert between the two spaces * during virtualization window calculations. * * @param zoom - The zoom/scale factor (e.g., 0.75 for 75% zoom). Defaults to 1. */ setZoom(zoom: number): void; /** * Sets the external scroll container element. * * When the scroll container is an ancestor element (e.g., a wrapper div with * overflow-y: auto), the default scrollY calculation using mount.getBoundingClientRect() * relative to the viewport produces an offset equal to the scroll container's distance * from the viewport top. This causes the virtualization window to be misaligned with the * actual visible area. * * Setting the scroll container allows updateVirtualWindow() to compute scrollY relative * to the scroll container instead, eliminating this offset. * * @param el - The scroll container element, or null to clear. */ setScrollContainer(el: HTMLElement | null): void; /** Returns the resolved page for a given index, or null if resolved data is unavailable. */ private getResolvedPage; /** * Returns the latest painter snapshot captured during the last paint cycle. */ getPaintSnapshot(): PaintSnapshot | null; /** * Returns the page indices that are currently mounted in the DOM. * * Unlike paint snapshots, this reflects virtualization remounts that happen * during scroll without waiting for a full paint cycle. */ getMountedPageIndices(): number[]; private createAllPageIndices; private setMountedPageIndices; private emitPaintSnapshot; private beginPaintSnapshot; private finalizePaintSnapshotFromBuilder; private capturePaintSnapshotLine; private collectPaintSnapshotFromDomRoot; paint(input: DomPainterInput, mount: HTMLElement, mapping?: PositionMapping): void; private renderVirtualized; private ensureVirtualizationSetup; private configureSpacerElement; private bindVirtualizationHandlers; private computeVirtualMetrics; private topOfIndex; private contentTotalHeight; private getMountPaddingTopPx; /** * Public method to trigger virtualization window update on scroll. * Call this from external scroll handlers when the scroll container * is different from the painter's mount element. */ onScroll(): void; private updateVirtualWindow; private updateSpacers; private updateSpacersForMountedPages; private clearGapSpacers; private renderHorizontal; private renderBookMode; private renderPage; /** * Render a ruler element for a page. * * Creates a horizontal ruler with tick marks and optional interactive margin handles. * The ruler is positioned at the top of the page and displays inch measurements. * * @param pageWidthPx - Page width in pixels * @param page - Page data containing margins and optional size information * @returns Ruler element, or null if this.doc is unavailable or page margins are missing * * Side effects: * - Creates DOM elements and applies inline styles * - May invoke the onMarginChange callback if interactive mode is enabled * * Fallback behavior: * - Uses DEFAULT_PAGE_HEIGHT_PX (1056px = 11 inches) if page.size.h is not available * - Defaults margins to 0 if not explicitly provided */ private renderPageRuler; private renderColumnSeparators; private getColumnSeparatorPositions; private renderDecorationsForPage; /** * Check if an anchored fragment has vRelativeFrom === 'page'. * Used to determine special Y positioning for page-relative anchored media * in header/footer decoration sections. */ private isPageRelativeAnchoredFragment; /** * Header/footer layout emits normalized anchor Y coordinates: * - headers: local to the header container origin * - footers: local to the top of the footer band (pageHeight - bottomMargin) * * Footer containers can grow upward when content overflows the reserved footer * band, so their top edge is not always the same as the footer band origin. * This helper returns the page-space origin that normalized anchor Y values * are measured from. */ private getDecorationAnchorPageOriginY; private renderDecorationSection; private resetState; private fullRender; private patchLayout; private patchPage; /** * Updates data-pm-start/data-pm-end attributes on all elements within a fragment * using the transaction's mapping. Skips header/footer content (separate PM coordinate space). * Also skips fragments that end before the edit point (their positions don't change). */ private updatePositionAttributes; private createPageState; private applySemanticPageOverrides; private getEffectivePageStyles; private renderFragment; /** * Renders a paragraph fragment with defensive error handling. * Falls back to error placeholder on rendering errors to prevent full paint failure. * * @param fragment - The paragraph fragment to render * @param context - Rendering context with page and column information * @param sdtBoundary - Optional SDT boundary overrides for multi-fragment containers * @returns HTMLElement containing the rendered fragment or error placeholder */ private renderParagraphFragment; /** * Creates an error placeholder element for failed fragment renders. * Prevents entire paint operation from failing due to single fragment error. * * @param blockId - The block ID that failed to render * @param error - The error that occurred * @returns HTMLElement showing the error */ private createErrorPlaceholder; private renderImageFragment; /** * Optionally wrap an image element in an anchor for DrawingML hyperlinks (a:hlinkClick). * * When `hyperlink` is present and its URL passes sanitization, returns an * `` wrapping `imageEl`. The existing EditorInputManager * click-delegation on `a.superdoc-link` handles both viewing-mode navigation and * editing-mode event dispatch automatically, with no extra wiring needed here. * * When `hyperlink` is absent or the URL fails sanitization the original element * is returned unchanged. * * @param imageEl - The image element (img or span wrapper) to potentially wrap. * @param hyperlink - Hyperlink metadata from the ImageBlock/ImageRun, or undefined. * @param display - CSS display value for the anchor: 'block' for fragment images, * 'inline-block' for inline runs. */ private buildImageHyperlinkAnchor; private renderDrawingFragment; private renderDrawingContent; private createVectorShapeElement; /** * Apply fill and stroke styles to a fallback shape container */ private applyFallbackShapeStyle; private hasShapeTextContent; private createShapeTextElement; private shouldUseWordArtTextRenderer; private createWordArtTextElement; private buildWordArtLines; private resolveShapeTextPartText; private getWordArtTextAnchor; private getWordArtTextX; private applyWordArtTextFormatting; /** * Create a fallback text element for shapes without SVG * @param textContent - Text content with formatting * @param textAlign - Horizontal text alignment * @param textVerticalAlign - Vertical text alignment (top, center, bottom) * @param textInsets - Text insets in pixels (top, right, bottom, left) * @param groupScaleX - Scale factor applied by parent group (for counter-scaling) * @param groupScaleY - Scale factor applied by parent group (for counter-scaling) */ private createFallbackTextElement; private tryCreatePresetSvg; /** * Creates an SVG string from custom geometry path data (a:custGeom). * Each path in the custom geometry has its own coordinate space (w × h) which is * mapped to the shape's actual dimensions via the SVG viewBox. */ private tryCreateCustomGeometrySvg; private parseSafeSvg; private stripUnsafeSvgContent; private getEffectExtentMetrics; private applyLineEnds; private findLineEndTarget; private ensureSvgDefs; private appendLineEndMarker; private createLineEndShape; private sanitizeSvgId; private applyVectorShapeTransforms; private createShapeGroupElement; private createGroupChildContent; private createDrawingPlaceholder; /** * Create an SVG chart element from a ChartDrawing block. * Delegates to the chart-renderer module for clean separation. */ private createChartElement; private resolveTableRenderData; private renderTableFragment; private renderLine; private createRunRenderContext; /** * Updates an existing fragment element's position and dimensions in place. * Used during incremental updates to efficiently reposition fragments without full re-render. * * @param el - The HTMLElement representing the fragment to update * @param fragment - The fragment data containing updated position and dimensions * @param section - The document section ('body', 'header', 'footer') containing this fragment. * Affects PM position validation - only body sections validate PM positions. * If undefined, defaults to 'body' section behavior. */ private updateFragmentElement; /** * Applies fragment positioning, dimensions, and metadata to an HTML element. * * @param el - The HTMLElement to apply fragment properties to * @param fragment - The fragment data containing position, dimensions, and PM position information * @param section - The document section ('body', 'header', 'footer') containing this fragment. * Controls PM position validation behavior: * - 'body' or undefined: PM positions are validated and required for paragraph fragments * - 'header' or 'footer': PM position validation is skipped (these sections have separate PM coordinate spaces) * When undefined, defaults to 'body' section behavior (validation enabled). */ private applyFragmentFrame; /** * Applies PM position data attributes from a legacy Fragment. * Extracted from applyFragmentFrame for use in the resolved wrapper path. * When a resolvedItem is provided, its fields take precedence over fragment fields. */ private applyFragmentPmAttributes; /** * Applies fragment wrapper positioning from a ResolvedFragmentItem. * Uses resolved data for spatial properties and delegates PM attributes to the legacy path. */ private isAnchoredMediaFragment; private shouldRenderBehindPageContent; private isHeaderWordArtWatermark; private isVmlTextWatermarkImage; private applyHeaderFooterTextWatermarkPreviewOpacity; /** * Only anchored images and drawings participate in explicit wrapper stacking. * Inline media intentionally rely on DOM order to preserve legacy paint order. */ private resolveFragmentWrapperZIndex; private applyFragmentWrapperZIndex; private applyResolvedFragmentFrame; /** * Estimates the height of a fragment when explicit height is not available. * * This method provides fallback height calculations for footer bottom-alignment * from resolved layout data, or using the fragment's height property for * tables, images, and drawings. * * @param fragment - The fragment to estimate height for * @returns Estimated height in pixels, or 0 if height cannot be determined */ private estimateFragmentHeight; } //# sourceMappingURL=renderer.d.ts.map