/** * Band caps — page-span blocks, stage 2 (mid-page bands). * * Stage 1 (`placement.ts`) only inserts a page-span block where the current * column band is *level*; a block arriving with the columns uneven moves to * the top of the next page. Stage 2 lets it land mid-page the way a * compositor would set it: the text above the box is cut level across all * columns, the box spans the page, and the columns resume below it. * * Rather than redistributing already-placed blocks in place (which would * bypass orphan / widow / keep-with-next), the engine re-runs the placement * pass with a **band cap**: the columns of the band that opens with a given * content block are shortened to `lines` grid lines, so they fill and * overflow into each other naturally — every placement rule keeps applying * — and end level at `bandTop + lines·grid`, where the span block then cuts * the band. A cap is proposed by the pass that saw the block arrive in an * uneven band with room below (`lines = ceil(Σ used px / N / grid)`), and * consumed by the next pass. When the capped band overflows differently * (e.g. keep-with-next moved a heading and the text no longer fits above * the cut), the cap is retried one line taller a bounded number of times, * then dropped — the block falls back to stage 1. */ import type { VDTColumn } from '../vdt.js'; /** Cap on the band that opens with content block `startContentIndex` * (part `startPart` — a split paragraph's continuation can open a band * too), keyed in `bandCaps` by the span block's own content index. */ export interface BandCap { /** `'span'`: proposed by a page-span block that wants to cut the band * mid-page (the box then sits on the cut). `'trailing'`: proposed at a * chapter / document boundary to level the closing band's columns; the * band ends inside the cap and nothing cuts it. */ kind: 'span' | 'trailing'; startContentIndex: number; /** Part index of the band's opening block (`0` unless it is the * continuation of a paragraph split across columns / pages). */ startPart: number; /** Bands the same opening block and part were offered to before this * one in the proposing pass (absent: none). A block offered to a band * that has no room for it — a page a float fills — moves on without * placing anything and opens the next band as well; the cap belongs to * the band it was measured in. */ startVisit?: number; /** Height of the capped band in lines: baseline-grid lines, or lines of * `pitchPx` when the cap carries one. */ lines: number; /** The height of one of `lines` (px) when it is not the baseline grid's: * the leading the band's own text is set on (see {@link bandLinePitch}, * #711). A trailing cap only; absent, the cut stands on a grid line. */ pitchPx?: number; /** How many times the cap may be grown by a line after an overflow, when * its lines are shorter than the grid's (#711): as far as three grid * lines reach, at most {@link MAX_PITCHED_CAP_RETRIES}. Absent: * {@link MAX_BAND_CAP_RETRIES}. */ retryLimit?: number; /** Times the cap has been grown by one line after an overflow. */ retries: number; /** Trailing caps only: the zone a chapter-closing fixed box takes at the * bottom of the band. The band's columns it meets (by position in the * band) are cut no lower than `top`, so the box fits below them; `lines` * is then the height of the other columns, which take the text the cut * displaces. */ zone?: BandCapZone; /** How far below its bare top the band started when the cap was * proposed, because every column of it had a float band at its head * (`lines` counts from under them). The capped pass can open the band * before those floats land, when the band's top is still the bare one; * the cut is then taken from `headPx` lower, where it was measured, or * it would come that much too high and the band never end level. */ headPx?: number; } export interface BandCapZone { /** Absolute page y of the zone's top edge (grid-snapped). */ top: number; /** Positions, in the band's column list, of the columns the box meets. */ columns: number[]; } /** Maximum number of extra placement passes the band-cap driver may run on * top of the initial pass (proposals + retries). */ export declare const MAX_BAND_PASSES = 4; /** Maximum number of `lines + 1` retries for a cap whose band overflowed. */ export declare const MAX_BAND_CAP_RETRIES = 3; /** The most retries a cap with a pitch of its own gets (#711). */ export declare const MAX_PITCHED_CAP_RETRIES = 6; /** The retries a cap of `pitchPx` lines may take to reach as far as * {@link MAX_BAND_CAP_RETRIES} grid lines do. */ export declare function capRetryLimit(pitchPx: number, gridPx: number): number; /** The share of a band's lines that must stand on its leading for the band * to be cut in lines of it ({@link bandLinePitch}). */ export declare const PITCHED_BAND_SHARE = 0.8; /** * The leading a band's own text is set on (#711): over the lines of the * flow blocks of `cols` that are not headings, not hidden and not inside a * box, the line height most of them share. The grid when that height is * the grid's, when no height is shared by more than half of the lines, or * when the band holds no such line: a band of body text is cut on the grid * as it always was, and so is one that mixes two leadings in equal parts. * * The lines must also stand on that leading: in each column, at least * {@link PITCHED_BAND_SHARE} of them a whole number of lines from one * another. References of three lines with a gap between entries, or an * index whose groups stand a hand-set margin apart, share a line height * and no lattice: a cut counted in their lines falls no better than one on * the grid, and such a band keeps the grid. */ export declare function bandLinePitch(cols: readonly VDTColumn[], gridPx: number): number; /** What a placement pass reports back to the band-cap driver. */ export interface BandPassReport { /** New caps, keyed by the proposing span block's content index. */ bandCapProposals: ReadonlyMap; /** Span blocks inserted in the band their cap was applied to — and, for * trailing caps, boundaries reached while the flow was still inside the * capped band (the content did not spill past the level cut). */ spanPlacedInBand: ReadonlySet; /** Caps whose opening block did open a band in this pass. */ bandCapsApplied: ReadonlySet; } /** Top edge of a band: the highest column top. A column whose top was * pushed down by a float band starts below it. */ export declare function bandTop(cols: readonly VDTColumn[]): number; /** Top edge of a band before the float bands at its columns' heads pushed * them down: `floatHead` gives the px reserved at a column's head. Equal * to {@link bandTop} unless every column holds such a float. */ export declare function bareBandTop(cols: readonly VDTColumn[], floatHead: (col: VDTColumn) => number): number; /** Cut every column of a (still empty) band level at `top + capPx` (`top` * defaults to {@link bandTop}), * remembering each column's true bottom in `uncappedBottoms` so the band * can be restored (`uncapBand`) when the span block cuts it. The cut is an * absolute line, so a column that starts lower (under a top float band) * gets a shorter height and the columns still end level. Idempotent; a * column already ending above the cut (bottom float) is left alone. A * column that already holds content keeps its used height — the cap then * only trims what is left. */ export declare function applyBandCap(cols: readonly VDTColumn[], capPx: number, uncappedBottoms: Map, zone?: BandCapZone, trailing?: boolean, top?: number): void; /** Give capped columns their true bottom back (the slack below the cap * returns to `availableHeight`). */ export declare function uncapBand(cols: readonly VDTColumn[], uncappedBottoms: Map): void; /** True bottom of a column — the remembered one while it is capped. */ export declare function columnBottom(col: VDTColumn, uncappedBottoms: ReadonlyMap): number; /** Grid lines (from the band top) a level cut of the band would need: the * content spread over the band's columns evenly, rounded up to whole * lines. A column starting under a top float band counts that offset as * used, so the cut lands at the same absolute height in every column; * `extraPx` adds space the band must also hold (float bands reserved at * column feet, which shortened the columns instead of filling them). */ export declare function bandCapLines(cols: readonly VDTColumn[], gridPx: number, extraPx?: number): number; /** Grid lines the columns of a closing band NOT under a fixed box's zone * need when the columns under it are cut at `zone.top`: the band's content * minus what fits above the zone, spread over the other columns and * rounded up to whole lines. `null` when no column is left to take the * displaced text, or when the level cut already clears the zone (a plain * level cap does the job), or when the other columns cannot hold it * within their true height (the box would not fit on the page anyway). */ export declare function bandCapLinesAroundZone(cols: readonly VDTColumn[], gridPx: number, zone: BandCapZone, trueBottom: (col: VDTColumn) => number): number | null; /** * Drive the band-cap passes. `initial` is the plain first pass; `runPass` * re-places the document with the given caps. Returns the layout to carry * into column balancing, the caps it was built with, and the number of * passes run (including the initial one). * * Each round first settles the caps of the previous pass — a cap whose * block was not inserted in its band is retried one line taller (while it * did apply and `retries` allows), otherwise dropped — then adopts the new * proposals (never one already tried). Rounds stop when nothing changes or * `MAX_BAND_PASSES` extra passes were run. The layout returned never carries * an undelivered cap: failing caps are removed and, when none survive, the * initial pass is returned untouched — a document whose span blocks all * fit stage 1 costs no extra pass at all. */ export declare function resolveBandCaps(initial: T, runPass: (bandCaps: ReadonlyMap) => T): { result: T; bandCaps: Map; passCount: number; }; /** Run a pass driver to completion synchronously. */ export declare function drainPasses(gen: Generator): R; /** {@link resolveBandCaps} as a generator that yields after every pass it * runs, so a host driving the build asynchronously can let a cancel land * (or the event loop turn) between passes. */ export declare function resolveBandCapsGen(initial: T, runPass: (bandCaps: ReadonlyMap) => T): Generator; passCount: number; }, void>; /** * Drive the trailing-cap passes (closing bands cut level at a chapter / * document boundary), after column balancing has settled: `initial` is the * balanced layout whose boundaries proposed the caps, `spanCaps` the caps * already in force, and `runPass` re-places the document with the balancing * hints frozen and the given caps. A trailing cap whose band overflowed * (applied, not delivered) is retried one line taller; one whose band * opened with a different block (not applied — an earlier cap's retry moved * the flow under it) is replaced by the fresh proposal that pass made for * the same boundary, else dropped. A boundary that proposes anew once the * caps before it have settled (a dropped one, or a closing band the earlier * caps uncovered) joins the next pass, so a cap that stumbles while another * is still being retried is not lost. Span caps must stay delivered — a * trailing cap that unsettles one is abandoned. Out of passes, the caps * still undelivered are dropped; one that was delivered and comes undone * once they are (the flow after them moved) is dropped in the next round, * and the others are kept. The layout returned never carries an * undelivered cap; when no trailing cap survives, `initial` is returned * untouched. */ export declare function resolveTrailingCaps(initial: T, spanCaps: ReadonlyMap, runPass: (bandCaps: ReadonlyMap) => T): { result: T; caps: Map; passCount: number; }; /** {@link resolveTrailingCaps} as a generator yielding after every pass. */ export declare function resolveTrailingCapsGen(initial: T, spanCaps: ReadonlyMap, runPass: (bandCaps: ReadonlyMap) => T): Generator; passCount: number; }, void>; //# sourceMappingURL=bandCaps.d.ts.map