import { ColumnLayout } from './index.js'; /** * Resolved geometry for a single column. `x` and `separatorX` are CONTENT-RELATIVE (measured from * the content-area left edge); add the content-left / left margin to get an absolute page x. This * is the single source every column consumer should read for positioning. (SD-2629) */ export type ColumnGeometry = { index: number; x: number; width: number; /** Gap after this column; 0 for the last column. */ gapAfter: number; /** Separator x (content-relative); present only when a separator line is drawn after this column. */ separatorX?: number; }; export type NormalizedColumnLayout = ColumnLayout & { width: number; /** * The content-area width the layout was normalized against, in px. * * Only RTL geometry reads it, and it exists because `width` above is the WIDEST column, not the * strip: explicit widths are deliberately not scaled to fill the content area (Word renders an * authored 2880tw column as 2880tw and leaves the slack), so a strip of explicit columns can be * narrower — or wider — than the area it sits in. Mirroring such a strip about its own span would * keep it pinned to the LEFT margin and only swap the columns inside it, which is not what Word * does: the first column belongs against the RIGHT margin and the slack falls on the left. * * Optional because `getColumnGeometry` also accepts hand-built layouts (column balancing assembles * one directly). When absent, RTL mirrors about the strip's own span, which is exact whenever the * columns fill the area — always true in equal mode. */ contentWidth?: number; }; export declare function widthsEqual(a?: number[], b?: number[]): boolean; /** * Resolved column mode. Explicit ONLY when `equalWidth === false` AND at least one usable child * width exists; otherwise equal mode. In equal mode Word ignores any child `w:col/@w` and divides * the content area evenly, so this is the single explicit/equal decision shared by extraction, * normalization, and geometry. (SD-2324 / SD-2629) */ export declare function resolveColumnMode(input: ColumnLayout | undefined): 'explicit' | 'equal'; /** * Resolved column count and the SINGLE authority for "how many columns exist": the raw `w:num` * (default 1, floored, min 1) clamped to the usable explicit-width count in explicit mode (Word * renders min(num, valid-width count)). Both `normalizeColumnLayout` (width math) and the paginator * fill loop read this, so the two tracks cannot disagree: a section that declares more columns * than it supplies widths (e.g. w:num="4" with two ) neither pads surplus columns to ~0px * slivers nor advances the fill into non-existent columns. Content-width-independent. (SD-2324 F8 / * SD-2629) */ export declare function resolveColumnCount(input: ColumnLayout | undefined): number; export declare function cloneColumnLayout(columns?: ColumnLayout): ColumnLayout; /** * Resolve an authored column config to what actually renders: count clamped to resolveColumnCount, * and per-column data reconciled with the mode. In explicit mode widths/gaps are sliced to the * resolved count (drop surplus); in equal mode they are dropped entirely, because Word ignores * child widths/spaces and divides evenly, and consumers like the DOM painter treat any `widths` as * explicit. NOT scaled to a content width; that is normalizeColumnLayout's job. Use for * render-facing metadata (page.columns / layout.columns / columnRegions) so it never advertises * phantom columns or stray explicit widths, e.g. count:4 with two widths becomes count:2. (SD-2629) */ export declare function resolveColumnLayout(input: ColumnLayout): ColumnLayout; export declare function normalizeColumnLayout(input: ColumnLayout | undefined, contentWidth: number, epsilon?: number): NormalizedColumnLayout; /** * Resolve per-column geometry for an already-normalized layout. This is the SD-2629 consumer API: * fill/positioning/separators/hit-testing/footnotes/floating anchors/balancing should read this * single source rather than re-deriving from `widths`/`gap`. Geometry uses the resolved (unscaled) * widths and per-column `gaps`, falling back to the uniform gap when no per-column gaps exist. */ export declare function getColumnGeometry(normalized: NormalizedColumnLayout): ColumnGeometry[]; /** Width of the column at `index` (px). */ export declare function getColumnWidth(geometry: ColumnGeometry[], index: number): number; /** Left edge of the column at `index`, as `originX + content-relative x`. */ export declare function getColumnX(geometry: ColumnGeometry[], index: number, originX?: number): number; /** Gap after the column at `index` (0 for the last column). */ export declare function getColumnGapAfter(geometry: ColumnGeometry[], index: number): number; /** Absolute x of each separator line (only columns that draw one), as `originX + content-relative`. */ export declare function getColumnSeparatorPositions(geometry: ColumnGeometry[], originX?: number): number[]; /** * Index of the column whose OWN span contains absolute `x`, or `null` when `x` lies in no column at * all — a gutter, the page margins, or something that is not column flow in the first place. * * This is the strict counterpart to `getColumnAtX` below, and the two exist because paint-time and * hit-testing want opposite answers. A click has to select something, so `getColumnAtX` clamps and * hands a gap to its neighbouring column. Asking "is there content in a later column" must not * clamp: `page.items` carries page-anchored objects, and a full-width watermark belongs to no * column, so answering with one makes it evidence for chrome Word does not draw. * * Direction-agnostic by construction. It tests containment in each column's own span instead of * comparing against a boundary, so it does not care whether `x` ascends or descends with the index, * and — unlike an edge test — it is not fooled by a fragment WIDER than its column. An over-wide * table is placed at its column's left edge and overflows rightward in both directions, so its * origin still identifies its column while its trailing edge does not. * * Spans are half-open — `[x, x + width)` — so that adjacent columns authored with no gutter at all * (`w:space="0"`) do not both claim the boundary they share. That boundary is exactly where the * later column's own content is placed, and an inclusive upper bound would hand it to the earlier * column instead. Columns are scanned in fill order and the first containing span wins, which * after that only matters for an overfull explicit strip whose columns genuinely overlap. */ export declare function findColumnContaining(geometry: ColumnGeometry[], x: number, originX?: number): number | null; /** * Index of the column containing absolute `x` (clicks in a gap map to the preceding column). * * The walk is direction-aware and cannot assume ascending `x`: in an RTL section column 0 sits on * the right, so `x` DESCENDS with the index. The mirrored branch keeps the same rule the LTR branch * states — a point in a gap belongs to the column that precedes it in FILL order — which is what * makes a drag that crosses the gutter keep extending from the column it is leaving instead of * jumping. Direction is read off the geometry rather than taken as an argument, so every existing * caller keeps working unchanged. * * Both branches test a HALF-OPEN span, so this agrees with `findColumnContaining` on every boundary * the two can both answer. The LTR branch gets that from `cx >= col.x`: a point on a shared edge is * the later column's, because that is where the later column's content begins. The mirrored branch * has to say the same thing from the other side — the shared edge is the EARLIER fill column's left * edge there — which is `cx < col.x + col.width`, exclusive. An inclusive bound handed that point to * the later column, contradicting the half-open span the geometry places content in, and it also * pulled in the point one pixel-width past a column's trailing edge, which is gutter and belongs to * the preceding column. With `w:space="0"` the two coincide and every column boundary in an RTL * section resolved one column too far. */ export declare function getColumnAtX(geometry: ColumnGeometry[], x: number, originX?: number): number; /** Structural equality of two column layouts, including per-column `gaps`. */ export declare function columnLayoutsEqual(a?: ColumnLayout, b?: ColumnLayout): boolean; /** * Render equality: true when two column configs produce the SAME rendered layout even if their raw * fields differ. Compares the canonical render form for today's renderer (resolved mode + count, * scalar gap, withSeparator, and in explicit mode the sliced widths) and deliberately ignores raw * `equalWidth` and the surplus count/widths that resolution discards. Per-column `gaps` are * intentionally ignored until geometry/separators consume them (step 4), so a gaps-only authored * delta does not split regions or invalidate the normalized-columns cache before it becomes * paint-significant. Use for region/cache change detection so e.g. `{num:4, widths:[a,b]}` vs * `{num:2, widths:[a,b]}`, or `equalWidth:true` vs an omitted equalWidth, do not split into * separate regions. (SD-2629) */ export declare function columnRenderLayoutsEqual(a?: ColumnLayout, b?: ColumnLayout): boolean;