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 = {}) => ({ fontFamily: FAMILY, fontSize: 10, lineHeight: "12pt", ...extra }); export function doc(children: ReactNode, o: { height?: number; width?: number; pad?: number | Record } = {}): 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, { onRender: (p: any) => (raw = p._INTERNAL__LAYOUT__DATA_) }); await renderToBuffer(withHook as never); return raw; }