import { type BoundingBox, type ResolvedConfig, type VDTDocument, type VDTBlock, type VDTLine, type VDTPage, type VDTColumn } from '../vdt.js'; import type { HeadingBreakParity } from '../types.js'; import { type PageMetrics } from './buildHelpers.js'; export interface PlacementCursor { pageIndex: number; columnIndex: number; } export declare function resetLinePositions(lines: VDTLine[], lineHeightPx: number): VDTLine[]; /** The layout `page` was created with (see `createPageWithColumns`). */ export declare function pageLayoutOf(page: VDTPage): ResolvedConfig['layout'] | undefined; export declare function createPageWithColumns(pageIndex: number, resolved: ResolvedConfig, contentArea: BoundingBox, pageWidthPx: number, pageHeightPx: number, pageIndexOffset?: number): VDTPage; /** Turn a freshly opened (empty) page into a part-divider page: a single * body column inset from the trim box by `parts.margins` (mirrored on the * pages `pageMirrored` names when `parts.margins.mirror` is on), * `page.contentArea` set to that area, `partInfo` stamped and the role * fixed to `'part'`. The opener design is laid out later by * `buildHeadersAndFooters` against the full trim box and never reserves * body space. The margins keep their names on the sheet, as the page * margins do: the area is cut from the physical trim box, mirrored there * and, on a vertical page, turned into the flow frame (a vertical * document's part page is vertical too; a right-to-left document's is * mirrored). */ export declare function createPartPage(page: VDTPage, metrics: Pick & Partial>, resolved: ResolvedConfig, info: { number: string; title: string; palette?: Record; titleSourceStart?: number; titleSourceEnd?: number; }, pageIndexOffset?: number): VDTPage; export declare function currentColumn(doc: VDTDocument, cursor: PlacementCursor): VDTColumn; /** Whether any column of the page — text or span — holds a placed block. * Floats live outside the columns and do not count. */ export declare function pageHasContent(page: VDTPage): boolean; /** Whether the page holds anything at all — column content, floats or a * comic page (a page carrying only a drained float band is occupied: the * next chapter must not open on it). */ export declare function pageIsOccupied(page: VDTPage): boolean; /** Text columns of `band` in reading order (span and side columns * excluded). */ export declare function bandColumns(page: VDTPage, band: number): VDTColumn[]; /** The float-only side column of `band`, if the page has one (a * one-and-a-half layout with `sideColumnRole: 'floats'`; part pages and * single / double layouts have none). */ export declare function sideColumnOf(page: VDTPage, band: number): VDTColumn | undefined; /** Every float-only side column of the page, band by band. */ export declare function sideColumns(page: VDTPage): VDTColumn[]; /** Bottom of what a side column already holds (absolute y): the top of * the column when it is still empty, else the foot of the last stacked * float — where the next `span: 'side'` float goes. */ export declare function sideUsedBottom(col: VDTColumn): number; /** Band the cursor's column belongs to (`0` for the plain single-band page). */ export declare function currentBand(page: VDTPage, cursor: PlacementCursor): number; /** True when every column of the band has consumed the same height (within * 0.5px) — the page top, right after an opener heading (which reserves its * band in every column), right after another span block, or right after a * top float band. Used height = `bbox.height − availableHeight`. */ export declare function isBandLevel(cols: readonly VDTColumn[]): boolean; /** Lowest used bottom (absolute y) across the band's columns — where a * full-width block can start without covering placed content. */ export declare function bandUsedBottom(cols: readonly VDTColumn[]): number; /** * Close the band formed by `cols` at `cutY`, insert a full-width span column * holding `block` right below the cut, and open a fresh band of text columns * under it. The band's columns are clamped to `cutY` — a column whose top * already sits at the cut becomes zero-height — and keep only the slack * between their used bottom and the cut in `availableHeight` (zero when the * band is level on the grid; whole lines when a band cap cut the band with * its last column short, so column balancing can still fill them). The span * column is `{ kind: 'span', band }` at `contentArea.x / width`, `needPx` * tall (the block plus its spacing, already rounded to the grid by the * caller); `block` is placed inside it at `spacingBefore` below the cut with * height `blockHeight` (default: the rest of the band), so `block.pageIndex` * / `columnIndex` address the span column. * * The new text columns (`band + 1`) copy each closed column's x / width and * keep its original bottom (so a bottom float band reserved on one column * still constrains its successor). A column with no room left (bottom * within 0.5px of the new band's top) is not created; when none is, the * cursor stays on the span column — which is full — so the next placement * advances to a new page. Otherwise the cursor moves to the first new * column. Returns the span column. */ export declare function closeBandAndInsertSpan(page: VDTPage, cols: readonly VDTColumn[], cutY: number, block: VDTBlock, needPx: number, cursor: PlacementCursor, spacingBefore?: number, blockHeight?: number): VDTColumn; /** * Close the band formed by `cols` at `cutY` and open a fresh band of text * columns (`band + 1`) starting at `newTop` (at or under the cut), as * {@link closeBandAndInsertSpan} does, with no span column between them: * what opens the new band (a page-span heading opened mid-page, #539) * spans the page from its first column. The closed columns keep the slack * between their used bottom and the cut; the new ones copy each closed * column's x / width and keep its bottom. The cursor moves to the first new * column. Returns false, changing nothing, when no column would have room * under `newTop`. */ export declare function closeBandAt(page: VDTPage, cols: readonly VDTColumn[], cutY: number, newTop: number, cursor: PlacementCursor): boolean; /** The next column of `page` after `columnIndex` that takes body text, or * -1: the flow skips float-only side columns, which never take any. */ export declare function nextFlowColumn(page: VDTPage, columnIndex: number): number; /** * Whether the text that leaves column `columnIndex` of `page` goes on to * another page: no later column of the page takes a line `minRoomPx` high. * The guard of `bodyText.hyphenateAcrossColumns: 'withinPage'` (#701) reads * it. It is the test {@link advanceToNextColumn} makes (it skips side * columns and opens a page after the last column), and it also counts out * a column with no room left, which the flow would enter only to leave. */ export declare function columnClosesPage(page: VDTPage, columnIndex: number, minRoomPx?: number): boolean; /** How many columns of `page` after `columnIndex` take a line of text * `minRoomPx` high. */ export declare function flowColumnsAfter(page: VDTPage, columnIndex: number, minRoomPx?: number): number; export declare function advanceToNextColumn(doc: VDTDocument, cursor: PlacementCursor, resolved: ResolvedConfig, contentArea: BoundingBox, pageWidthPx: number, pageHeightPx: number, onNewPage?: (page: VDTPage) => void): void; /** Advance `cursor` forward until it points to the first column of a new * page. No-op when the current page is empty (nothing placed yet) — avoids * emitting a blank leading page when a `:::pagebreak` directive lands * before any content. */ export declare function advanceToNextPageBoundary(doc: VDTDocument, cursor: PlacementCursor, resolved: ResolvedConfig, contentArea: BoundingBox, pageWidthPx: number, pageHeightPx: number, onNewPage?: (page: VDTPage) => void): void; /** After a forced page break, make sure the page we're sitting on has the * requested parity. If not, flag the current page as a blank-for-parity * placeholder and advance to the next page. Repeats until parity matches * (at most one extra page in practice). Parity uses `pageIndex + 1` * (page 1 = odd) — the same convention as header/footer parity. * * For the `always-*` modes, one mandatory blank page is inserted first * (flagged as `blankForForce`, belonging to the previous chapter) before * the parity check runs. This guarantees at least one separator page * between the previous content and the new chapter, regardless of * whether the natural next page already matched the requested parity. */ export declare function enforcePageParity(doc: VDTDocument, cursor: PlacementCursor, resolved: ResolvedConfig, contentArea: BoundingBox, pageWidthPx: number, pageHeightPx: number, parity: HeadingBreakParity): void; /** * Place an atomic block — a resource group (image / svg / table + caption, * issue #49 §7) or a callout frame — keeping it together as one unit. Atomic * blocks never split mid-content for v1: if the group's combined height fits * the remaining column space it is placed there; otherwise the cursor advances * to the next column/page (without emitting a leading blank page when the * current column is empty), and the block is force-placed there even when it * exceeds a single column's height. * * `spacingBefore` is consumed from the target column's available height before * placement (collapsed against the previous block's bottom margin by the * caller). Returns the height actually reserved for the block. */ /** Tolerance for "does this block fit" checks against a column's free * height, absorbing floating-point drift between grid multiples: a line * pitch that is no whole number of pixels (29 pt at 150 dpi is 60.41666… * px) leaves the running sums of grid lines about 1e-13 px off (#635). * Every comparison of a free height with a needed one takes it. */ export declare const FIT_EPS = 0.01; export declare function placeAtomicBlock(block: VDTBlock, groupHeight: number, spacingBefore: number, cursor: PlacementCursor, doc: VDTDocument, resolved: ResolvedConfig, contentArea: BoundingBox, pageWidthPx: number, pageHeightPx: number, /** Called with each page the block opens, before it is placed there (the * flow's float flush, see `advanceToNextColumn`). */ onNewPage?: (page: VDTPage) => void): number; /** @deprecated Use {@link placeAtomicBlock}. */ export declare const placeResourceBlock: typeof placeAtomicBlock; export declare function placeBlockInColumn(block: VDTBlock, blockHeight: number, col: VDTColumn, cursor: PlacementCursor): void; //# sourceMappingURL=placement.d.ts.map