TableGuard, BottomSpacer, FirstPageHeader and PageChrome implement the verified pagination rules; renderCore returns bytes, a normalised layout tree, audit issues and a layout fingerprint. auditLayout detects overlaps, ink overflow, truncation, header-only pages and split blocks. Quirk tests pin the react-pdf behaviours the design depends on, and a child-process test pins the page-level absolute Svg hang.
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
import { cloneElement, createElement as h, type ReactElement, type ReactNode } from "react";
|
|
import { Document, Page, renderToBuffer } from "@react-pdf/renderer";
|
|
import { computeFrame, PAGE_SIZES, type PageFrame } from "../engine/geometry";
|
|
import { walk, type LayoutNode, type LayoutPage } from "../engine/layoutTree";
|
|
import { stack } from "../fonts/register";
|
|
|
|
/** Small fixtures for tests that need a tiny react-pdf document. */
|
|
export const FAMILY = stack("IBM Plex Sans");
|
|
export const PAGE_W = 400;
|
|
export const PAD = 20;
|
|
|
|
export const txt = (extra: Record<string, unknown> = {}) => ({ fontFamily: FAMILY, fontSize: 10, lineHeight: "12pt", ...extra });
|
|
|
|
export function doc(children: ReactNode, o: { height?: number; width?: number; pad?: number | Record<string, number> } = {}): ReactElement {
|
|
const pad = o.pad ?? PAD;
|
|
return h(
|
|
Document,
|
|
null,
|
|
h(
|
|
Page,
|
|
{ size: { width: o.width ?? PAGE_W, height: o.height ?? 300 }, style: typeof pad === "number" ? { padding: pad } : pad },
|
|
children,
|
|
),
|
|
);
|
|
}
|
|
|
|
/** A frame matching `doc()`: uniform padding `PAD`, optional footer reserve inside the bottom padding. */
|
|
export function frameFor(o: { height?: number; width?: number; footerReserve?: number; continuationReserve?: number } = {}): PageFrame {
|
|
return computeFrame({
|
|
page: { ...PAGE_SIZES.A4, width: o.width ?? PAGE_W, height: o.height ?? 300 },
|
|
margins: { top: PAD, right: PAD, bottom: PAD, left: PAD },
|
|
footerReserve: o.footerReserve ?? 0,
|
|
continuationReserve: o.continuationReserve ?? 0,
|
|
letterMode: false,
|
|
});
|
|
}
|
|
|
|
export function find(page: LayoutPage, pred: (n: LayoutNode) => boolean): LayoutNode[] {
|
|
const out: LayoutNode[] = [];
|
|
walk(page.nodes, (n) => {
|
|
if (pred(n)) out.push(n);
|
|
});
|
|
return out;
|
|
}
|
|
|
|
export const byId = (page: LayoutPage, id: string) => find(page, (n) => n.id === id);
|
|
|
|
/** react-pdf's own layout tree, for facts the normalised tree hides (style values, raw props). */
|
|
export async function rawLayout(el: ReactElement): Promise<{ type: string; children: any[] }> {
|
|
let raw: any;
|
|
const withHook = cloneElement(el as ReactElement<any>, { onRender: (p: any) => (raw = p._INTERNAL__LAYOUT__DATA_) });
|
|
await renderToBuffer(withHook as never);
|
|
return raw;
|
|
}
|