Files
Voiced/src/pdf/templates/monolith/tokens.ts
T
xavierk 3d42f383ca Add the Monolith and Cobalt Stripe templates
Standalone layouts for Canva templates 3 and 8, with the user's logo sized to the
placeholder box, a CapRule decor primitive, optional TableRows rule/heading options,
and a separate 1..45-row sweep test file.
2026-10-04 11:25:19 +05:30

143 lines
6.0 KiB
TypeScript

import type { Sides } from "../../engine/geometry";
import { defineRoles, type TypeToken } from "../../fonts/roles";
import type { GapRange } from "../neutral/variant";
const P = "Poppins" as const;
export const INK = "#000000";
/**
* Monolith's type (docs/templates/03-monolith.md): Poppins Bold for the title, headings, labels and the Total row,
* Medium for every data line, Regular for the date. Everything is black. Poppins ascent 1.05, descent 0.35.
*/
export const MONOLITH_ROLES = defineRoles({
/** "INVOICE" / "TAX INVOICE": 60 pt in the design, shrunk to the room the right-hand stack leaves. */
title: { face: P, weight: 700, size: 60.02, leading: 66, caps: true, minSize: 30 },
/** The vendor name (or its logo) at the top right. */
brand: { face: P, weight: 700, size: 12.5, leading: 21, minSize: 8 },
metaDate: { face: P, weight: 400, size: 12.5, leading: 21, minSize: 8 },
metaNo: { face: P, weight: 700, size: 12.5, leading: 21, minSize: 8 },
/** BILLED TO:, SUPPLIER:, PAYMENT INFORMATION :. */
heading: { face: P, weight: 700, size: 14.5, leading: 20, caps: true },
body: { face: P, weight: 500, size: 12.5, leading: 17.25, minSize: 8 },
bodyStrong: { face: P, weight: 700, size: 12.5, leading: 17.25, minSize: 8 },
colon: { face: P, weight: 400, size: 12.5, leading: 17.25 },
/** The footer's vendor name. */
vendor: { face: P, weight: 700, size: 12.5, leading: 20 },
tableHead: { face: P, weight: 700, size: 12.5, leading: 17.25, caps: true },
tableBody: { face: P, weight: 500, size: 12.5, leading: 17.25 },
tableSub: { face: P, weight: 500, size: 10, leading: 13.5 },
totalLabel: { face: P, weight: 700, size: 12.5, leading: 17.25 },
totalValue: { face: P, weight: 500, size: 12.5, leading: 17.25 },
total: { face: P, weight: 700, size: 12.5, leading: 17.25 },
small: { face: P, weight: 500, size: 10.5, leading: 14.5 },
smallStrong: { face: P, weight: 700, size: 10.5, leading: 14.5, caps: true },
signName: { face: P, weight: 700, size: 11, leading: 15 },
signDesignation: { face: P, weight: 500, size: 10, leading: 14 },
fine: { face: P, weight: 500, size: 9, leading: 12.5 },
footer: { face: P, weight: 500, size: 7 },
});
export type MonolithRoles = typeof MONOLITH_ROLES;
/**
* Canva A4 numbers (docs 03): content x 35..560.3, title origin 16 pt inside the left edge, the right-hand column
* ends on the right edge; y values are the design's distances turned into flow gaps.
*/
export interface MonolithVariant {
roles: MonolithRoles;
ink: string;
margins: Sides;
marginFloor: Sides;
/** Title origin inside the content edge. */
titleIndent: number;
/** Space between the title and the right-hand stack at its narrowest. */
titleGap: number;
stack: {
/** Narrowest the right-hand stack gets (the logo allowance's width). */
minW: number;
/** Widest share of the content width. */
maxShare: number;
/** Top of the vendor name's line box below the header's top (cap height lines up with the title's). */
nameTop: number;
/** Top of the logo box below the header's top: the allowance's top edge. */
logoTop: number;
/** Space between the logo box's bottom and the date line. */
logoGap: number;
/** Gap between a mark and the vendor name beside it, and the narrowest the name may get. */
markGap: number;
minNameW: number;
};
rule: number;
heavyRule: number;
/** Header bottom to its rule. */
headRuleGap: number;
/** The rule to the BILLED TO heading, and the last info line to the next rule. */
/** Heading to its first line. */
headingGap: number;
/** Width of the right-hand columns (supplier ids, footer vendor, signature) and the space beside them. */
sideW: number;
sideGap: number;
gaps: {
/** The empty band between the info row and the table (88 pt in the design). */
band: GapRange;
/** The rule to BILLED TO, and the last info line to the next rule. */
infoTop: GapRange;
infoBottom: GapRange;
/** The signature band to the footer, and the footer text to its bottom rule. */
closeBand: GapRange;
closeBottom: GapRange;
};
table: {
/** Header band height inside the heavy rules. */
headH: number;
descInset: number;
cols: { price: number; qty: number; amount: number };
/** Right padding of the price and amount columns. */
numPad: number;
/** Vertical row padding by item count: generous like the design for a few rows, tighter for many. */
padY: (rows: number) => number;
padYMin: number;
};
totals: { boxW: number; labelW: number; valueW: number; top: number; rowGap: number; ruleGap: number; totalGap: number; valuePad: number; leftGap: number; wordsMax: number };
closing: {
/** The footer's top rule to its heading, and the heading to its first line. */
top: number;
headingGap: number;
labelW: number;
colonW: number;
notesBesideMax: number;
signatureImageH: number;
};
footerLeftW: number;
}
export const MONOLITH_VARIANT: MonolithVariant = {
roles: MONOLITH_ROLES,
ink: INK,
margins: { top: 33, right: 35, bottom: 22, left: 35 },
marginFloor: { top: 24, right: 30, bottom: 18, left: 30 },
titleIndent: 15.7,
titleGap: 20,
stack: { minW: 160, maxShare: 0.46, nameTop: 17.6, logoTop: 21.75, logoGap: 4, markGap: 8, minNameW: 70 },
rule: 0.75,
heavyRule: 1.5,
headRuleGap: 3.15,
headingGap: 7.2,
sideW: 165,
sideGap: 24,
gaps: { band: { nominal: 88, min: 24 }, infoTop: { nominal: 21, min: 14 }, infoBottom: { nominal: 21.5, min: 14 }, closeBand: { nominal: 18, min: 8 }, closeBottom: { nominal: 14.8, min: 8 } },
table: {
headH: 39.3,
descInset: 9.4,
cols: { price: 98, qty: 74, amount: 98 },
numPad: 10,
padY: (rows) => (rows <= 3 ? 16 : rows <= 5 ? 12 : 8),
padYMin: 6,
},
totals: { boxW: 215.5, labelW: 108, valueW: 107.5, top: 6.15, rowGap: 5.75, ruleGap: 3, totalGap: 6.35, valuePad: 10, leftGap: 16, wordsMax: 120 },
closing: { top: 6.9, headingGap: 12.9, labelW: 114, colonW: 21, notesBesideMax: 110, signatureImageH: 45 },
footerLeftW: 336,
};
export const leading = (t: TypeToken) => t.leading as number;