Linea (the vendor's original format.pdf design), Monogram and Serenity are rebuilt from the reference PDFs as one parametric family on the engine, with the user's logo in the measured slot, a home for every GST field and A4/Letter support. Adds static Montserrat, Open Sans and Poppins fonts and a generated Ms Madi outline for Serenity's vertical word. Text-only logo placeholders (Monolith, Tangerine) use a doc-derived allowance box. A Carbon TemplatePicker with committed thumbnails opens from the page setup controls.
100 lines
4.2 KiB
TypeScript
100 lines
4.2 KiB
TypeScript
import type { ComponentType } from "react";
|
|
import { effectiveMargins, type MarginPreset, type PageFrame, type Sides } from "../engine/geometry";
|
|
import type { Rect } from "../engine/layoutTree";
|
|
import type { SlotLogoPlacement } from "../engine/logo";
|
|
import type { RoleName, RoleTable, TypeToken } from "../fonts/roles";
|
|
import type { DecorItem } from "../blocks/FirstPageHeader";
|
|
import type { RenderModel } from "../model/build";
|
|
import type { RenderPrefsV1 } from "../model/prefs";
|
|
import type { TemplateSlot } from "./slots.types";
|
|
|
|
/** A logo after fitting and the stack-or-side-by-side decision (engine/logo.ts). */
|
|
export interface PlacedLogo {
|
|
dataUri: string;
|
|
w: number;
|
|
h: number;
|
|
stacked: boolean;
|
|
/** Set for slot-based templates: where the reference placeholder was and how the logo was fitted to it. */
|
|
placement?: SlotLogoPlacement;
|
|
/** Where the fitted logo sits inside the slot's target box (pt from its top-left), so a flow layout can reproduce the alignment. */
|
|
slotOffset?: { x: number; y: number };
|
|
}
|
|
|
|
/** What the page itself carries besides the flow: the paper, the ink and every-page art. */
|
|
export interface TemplateDecor {
|
|
/** Page fill (the Page backgroundColor), e.g. Serenity's paper. Omitted: white. */
|
|
background?: string;
|
|
/** Default text colour of the page. */
|
|
ink: string;
|
|
/** Fixed every-page decor in page space; must stay clear of the content column. */
|
|
everyPage?: (frame: PageFrame) => DecorItem[];
|
|
/** Page-space boxes the flow must stay out of (audited as `keepout`). */
|
|
keepOuts?: (frame: PageFrame) => Rect[];
|
|
}
|
|
|
|
/** Footer and page-label chrome of a template. */
|
|
export interface TemplateChrome {
|
|
color: string;
|
|
/** Hairline above the footer (Classic); null for none. */
|
|
rule: string | null;
|
|
/** Footer type (a render-prop role: no leading). Default: the "footer" role. */
|
|
role?: RoleName | TypeToken;
|
|
/** The footer text starts and ends this far inside the content column. */
|
|
inset?: { left: number; right: number };
|
|
/** Width the left footer text (supplier name) is fitted to. */
|
|
leftWidth: number;
|
|
/** Height reserved at the foot of every page for the footer line; default FOOTER_RESERVE (it includes Classic's rule). */
|
|
reserve?: number;
|
|
}
|
|
|
|
export interface TemplateProps {
|
|
model: RenderModel;
|
|
frame: PageFrame;
|
|
logo: PlacedLogo | null;
|
|
prefs: RenderPrefsV1;
|
|
}
|
|
|
|
export interface TemplateDefinition {
|
|
id: string;
|
|
name: string;
|
|
family: string;
|
|
/** Static PNG for the template picker, served from /public (written by scripts/thumbnails.mjs). */
|
|
thumbnail: string;
|
|
/** Shown on the picker tile. */
|
|
badge?: "Matches your original format";
|
|
/** The template's own type roles (Classic's are TYPE_ROLES). */
|
|
roles: RoleTable;
|
|
decor: TemplateDecor;
|
|
chrome: TemplateChrome;
|
|
/** Bump when the layout changes in a way that alters pages; it goes into the PDF Creator string. */
|
|
version: number;
|
|
/** The template's own physical margins (the "template" margin preset). */
|
|
margins: Sides;
|
|
/** No margin preset may go below this, per side; the printer-safe floor still applies on top. */
|
|
marginFloor: Sides;
|
|
logo: {
|
|
/** The largest box the logo may fill, pt. */
|
|
box: { w: number; h: number };
|
|
/** The logo moves above the text when the text beside it would be narrower than this. */
|
|
stackBelowTextWidth: number;
|
|
defaultOn: boolean;
|
|
/** Width of the column the logo shares with the supplier text, for this frame. */
|
|
slotWidth: (frame: PageFrame) => number;
|
|
/**
|
|
* The measured reference placeholder (or opt-in area) the user's logo replaces, from slotFor(id).
|
|
* Null for Classic, which fits its logo with computeClassicLogoPlacement.
|
|
*/
|
|
slot: TemplateSlot | null;
|
|
/** The template has no placeholder: the logo is shown only when the user turns it on (prefs.logo.show). */
|
|
optIn?: boolean;
|
|
/** Room the layout can really give the logo; the slot's target box shrinks to it. */
|
|
available?: (frame: PageFrame) => { w: number; h: number };
|
|
};
|
|
Layout: ComponentType<TemplateProps>;
|
|
}
|
|
|
|
/** Physical margins for a template under a margin preset. */
|
|
export function marginsFor(template: TemplateDefinition, preset: MarginPreset): Sides {
|
|
return effectiveMargins(preset === "template" ? template.margins : preset, template.marginFloor);
|
|
}
|