/** * Pagination engine for creating a PDF.js-style paginated view from HTML output. * * This module provides client-side pagination that measures rendered content * and flows it across fixed-size page containers based on document dimensions. */ /** * Page dimensions extracted from HTML data attributes (in points). */ export interface PageDimensions { /** Page width in points */ pageWidth: number; /** Page height in points */ pageHeight: number; /** Content area width (page minus margins) in points */ contentWidth: number; /** Content area height (page minus margins) in points */ contentHeight: number; /** Top margin in points */ marginTop: number; /** Right margin in points */ marginRight: number; /** Bottom margin in points */ marginBottom: number; /** Left margin in points */ marginLeft: number; /** Header distance from top of page in points */ headerHeight: number; /** Footer distance from bottom of page in points */ footerHeight: number; } /** * Headers and footers for a specific section. */ export interface SectionHeaderFooter { /** Default header (used for odd pages or all pages) */ headerDefault?: HTMLElement; /** First page header */ headerFirst?: HTMLElement; /** Even page header */ headerEven?: HTMLElement; /** Default footer (used for odd pages or all pages) */ footerDefault?: HTMLElement; /** First page footer */ footerFirst?: HTMLElement; /** Even page footer */ footerEven?: HTMLElement; /** Measured height of default header in points */ headerDefaultHeight?: number; /** Measured height of first page header in points */ headerFirstHeight?: number; /** Measured height of even page header in points */ headerEvenHeight?: number; /** Measured height of default footer in points */ footerDefaultHeight?: number; /** Measured height of first page footer in points */ footerFirstHeight?: number; /** Measured height of even page footer in points */ footerEvenHeight?: number; } /** * Registry of headers and footers by section index. */ export type HeaderFooterRegistry = Map; /** * A measured content block with metadata for pagination decisions. */ export interface MeasuredBlock { /** The DOM element */ element: HTMLElement; /** Measured height in points (content + padding + border, excluding margins) */ heightPt: number; /** Top margin in points */ marginTopPt: number; /** Bottom margin in points */ marginBottomPt: number; /** Whether to keep this block with the next one */ keepWithNext: boolean; /** Whether to keep all lines of this block together */ keepLines: boolean; /** Whether to force a page break before this block */ pageBreakBefore: boolean; /** Whether this is a page break marker */ isPageBreak: boolean; } /** * Information about a rendered page. */ export interface PageInfo { /** 1-based page number */ pageNumber: number; /** Section index this page belongs to */ sectionIndex: number; /** Page dimensions */ dimensions: PageDimensions; /** The page container element */ element: HTMLElement; } /** * Result of pagination operation. */ export interface PaginationResult { /** Total number of pages */ totalPages: number; /** Array of page information */ pages: PageInfo[]; } /** * Options for the pagination engine. */ export interface PaginationOptions { /** Scale factor for rendering (1.0 = 100%). Default: 1 */ scale?: number; /** CSS class prefix used in the HTML. Default: "page-" */ cssPrefix?: string; /** Whether to show page numbers. Default: true */ showPageNumbers?: boolean; /** Gap between pages in pixels. Default: 20 */ pageGap?: number; /** * Whether ordinary paragraphs may be fragmented across page boundaries. * Defaults to false for direct PaginationEngine callers; read-only viewer * entry points opt in explicitly. */ fragmentParagraphs?: boolean; } /** * Pagination engine that converts HTML with pagination metadata * into a paginated view with fixed-size page containers. */ /** * Registry of footnotes by ID for per-page distribution. */ export type FootnoteRegistry = Map; export declare class PaginationEngine { private stagingElement; private containerElement; private scale; private cssPrefix; private showPageNumbers; private pageGap; private fragmentParagraphs; private hfRegistry; private footnoteRegistry; private pendingFootnoteContinuation; /** Per-section `w:pgNumType` (start / format), read off the section wrappers. */ private pageNumbering; /** * Creates a new pagination engine. * * @param staging - The staging element or its ID containing the content to paginate * @param container - The container element or its ID where pages will be rendered * @param options - Pagination options */ constructor(staging: HTMLElement | string, container: HTMLElement | string, options?: PaginationOptions); /** * Runs the pagination process. * * @returns PaginationResult with page information */ paginate(): PaginationResult; /** Read each section's `w:pgNumType` off its wrapper (see {@link SectionPageNumbering}). */ private parsePageNumbering; /** * Fill in the page-number fields inside every page's cloned header/footer. * * A header/footer is authored once and cloned onto each page, so a PAGE field's single cached * result would otherwise show the same number on every page — the whole reason the converter * marks these. `data-field-format` (the field's own `\*` switch) wins over the section's format * when present, which is exactly how Word resolves the two. * * Runs after layout because NUMPAGES cannot be known before the last page exists. The * substituted text can therefore be marginally wider than the cached result the header was * measured with; the header band clips, so the failure mode is a hair of overflow rather than * a layout that disagrees with itself. * * Scoped to the CLONED header/footer regions on purpose. A page-number field in body text is * ordinary run content that the editor may make editable, and committing an edited block writes * back whatever text the DOM holds — rewriting it here would mean a body field commits a number * the document never contained. Body content is also not cloned, so it does not have the problem * this method exists to solve. */ private substitutePageNumberFields; /** * Measures all content blocks in a section. */ private measureBlocks; /** * Measures one element in the same hidden staging context used for the source blocks. * This is intentionally DOM-based: table row heights cannot be inferred from individual * rows because wrapping and collapsed borders change the height of a fragment. */ private measureElement; /** * Returns the contiguous keep-with-next chain beginning at a block. * * A hard page break or a page-break-before directive is stronger than a * keep-with-next directive, so it terminates the chain. The caller only * keeps a chain together when the whole chain can fit on a fresh page. */ private getKeepWithNextChain; /** * Measures the visible body height of a keep-with-next chain using the same * collapsed-margin rules as normal block placement. The trailing margin is * intentionally excluded, matching the individual block fit check. */ private measureKeepWithNextChainBodyHeight; /** * Finds footnote references introduced by a sequence of blocks, preserving * document order and excluding references already assigned to the page. */ private collectNewFootnoteIds; /** * The shortest body available to a section's first, default, or even page. * A row fragment must fit every variant, otherwise a later header/footer could * send it through the oversized-block fallback again. */ private smallestEffectiveContentHeight; /** * Builds a clone of a simple table wrapper containing a contiguous run of rows. * Complex table features are deliberately rejected by the caller: a split across * merged cells, nested tables, or footnotes cannot be made correct by cloning rows. */ private createSimpleTableFragment; /** * Splits an oversized, ordinary table at row boundaries. This only participates * in the existing oversized-block fallback; unsupported tables keep the previous * overflow behavior rather than risking broken table semantics. */ private trySplitSimpleOversizedTable; /** * A DOM endpoint that can finish a paragraph fragment. Endpoints are chosen * after whitespace or at a run boundary so the paginator never deliberately * cuts through an ordinary word merely to fill a little more of a page. */ private paragraphFragmentEndpoints; /** * Whether a range contains visible text after ignoring bidi/zero-width marks. * Paragraph fragmentation deliberately excludes non-textual descendants, so * this is enough to reject empty head or tail fragments. */ private hasVisibleFragmentText; /** * Phase one only handles text-like paragraph descendants. Objects, explicit * line breaks, list markers, notes, and out-of-flow/inline-block content all * require their own line-layout rules and retain the established whole-block * fallback instead of risking broken content or duplicate anchors. */ private canFragmentParagraph; /** * Builds one range-cloned paragraph fragment. Only the leading fragment keeps * the source paragraph's addressability; continuations must not duplicate an * id/data-anchor in the rendered document. */ private createParagraphFragment; /** * Splits a simple paragraph at the largest DOM Range endpoint that fits the * currently available body space. The caller then processes the tail normally, * allowing it to fragment again on later pages when necessary. */ private tryFragmentParagraph; /** * Parses the header/footer registry from the staging element. * Also measures heights during parsing for lazy-loading compatibility. */ private parseHeaderFooterRegistry; /** * Parses the footnote registry from the staging element. */ private parseFootnoteRegistry; /** * Extracts footnote reference IDs from an element. */ private extractFootnoteRefs; /** * Measures the height of footnotes for given IDs (in points). * Creates a temporary container to measure the footnotes. * @param footnoteIds - IDs of footnotes to measure * @param contentWidth - Width for measurement * @param continuation - Optional continuation content to include first */ private measureFootnotesHeight; /** * Measures the height of just the continuation content (in points). */ private measureContinuationHeight; /** * Splits a footnote element into parts that fit within the available height. * Returns the elements that fit and the elements that need to continue. */ private splitFootnoteToFit; /** * Measures a single footnote's height. */ private measureSingleFootnoteHeight; /** * Adds footnotes to a page container, including continuation content. */ private addPageFootnotes; /** * Selects the appropriate header for a page based on section, page position, and page number. */ private selectHeader; /** * Selects the appropriate footer for a page based on section, page position, and page number. */ private selectFooter; /** * Computes effective header, footer, and content heights for a specific page position. * Uses pre-measured header/footer heights from the registry. * This method is deterministic - same inputs always produce same outputs. * This enables lazy loading compatibility since available height can be computed * for any page position without knowing the page's content. */ private getEffectiveHeights; /** * Measures the content height of a header or footer element. * This is needed because headers/footers can contain more content than fits in the margin area. */ private measureHeaderFooterHeight; /** * Flows measured blocks into page containers. * Implements a single-pass, forward-only algorithm that is compatible with future lazy loading. * Supports footnote continuation - long footnotes can split across pages. */ private flowToPages; /** * Strip block addressing from a header/footer node cloned into a page box. * * A running story is authored ONCE and cloned onto every page, so the clones all carry the same * `data-anchor` — on this document, 42 page boxes claiming one footer paragraph. Left editable, * committing any one of them writes back through that single shared anchor, and the per-page * page-number substitution makes it worse: each clone shows a DIFFERENT number, so a commit * writes that page's number into the story as literal text and destroys the PAGE field. * * Page-box header/footer content is presentation. The docked editing bands * (`editor-headerfooter.ts`) are the addressable affordance, and they exist precisely because a * cloned node cannot be uniquely addressed. */ private makeClonedStoryInert; /** * Creates a page container element. */ private createPage; } /** * Convenience function to paginate HTML content. * * @param html - HTML string with pagination metadata * @param container - Container element or ID where pages will be rendered * @param options - Pagination options * @returns PaginationResult * * @example * ```typescript * const html = await convertDocxToHtml(docx, { paginationMode: PaginationMode.Paginated }); * * // Create a container for the paginated view * const container = document.getElementById('viewer'); * * // Parse and paginate * container.innerHTML = html; * const staging = document.getElementById('pagination-staging'); * const pageContainer = document.getElementById('pagination-container'); * * const engine = new PaginationEngine(staging, pageContainer, { scale: 0.8 }); * const result = engine.paginate(); * * console.log(`Document has ${result.totalPages} pages`); * ``` */ export declare function paginateHtml(html: string, container: HTMLElement | string, options?: PaginationOptions): PaginationResult; //# sourceMappingURL=pagination.d.ts.map