Add the Neutral template family, template picker and text-only logo allowances

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.
This commit is contained in:
2026-10-04 07:27:12 +05:30
parent eddf27629f
commit aa7a407944
61 changed files with 3278 additions and 110 deletions
+65 -6
View File
@@ -17,6 +17,10 @@ const logo = (over: Partial<SlotLogoInput>): SlotLogoInput => ({
});
const Sample_P = logo({ aspect: 4.62, density: 0.437, kind: "wordmark", includesName: true });
/** The box a logo is fitted to: the allowance for a text-only slot, else the lockup (or mark) box. */
const lockupTarget = (s: TemplateSlot): Box => (s.textOnly ? s.allowance : s.lockup!);
const markTarget = (s: TemplateSlot): Box => (s.textOnly ? s.allowance : (s.mark ?? s.lockup)!);
const inside = (inner: Box, outer: Box, eps = 1e-6) =>
inner.x >= outer.x - eps && inner.y >= outer.y - eps && inner.x + inner.w <= outer.x + outer.w + eps && inner.y + inner.h <= outer.y + outer.h + eps;
const closeBox = (a: Box, b: Box, tol = 0.5) => {
@@ -39,6 +43,17 @@ describe("slots.generated", () => {
}
});
it("text-only placeholders (Monolith, Tangerine) carry a bigger allowance anchored at the text's edge", () => {
expect(measured.filter(([, s]) => s.textOnly).map(([id]) => id).sort()).toEqual(["monolith", "tangerine-ledger"]);
const mono = SLOTS.monolith!;
expect(mono.allowance).toEqual({ x: 400, y: 54.75, w: 160, h: 28 });
expect(mono.allowance.x + mono.allowance.w).toBeCloseTo(mono.lockup!.x + mono.lockup!.w, 6);
const tang = SLOTS["tangerine-ledger"]!;
expect(tang.allowance).toEqual({ x: 60.5, y: 62, w: 220, h: 36 });
expect(tang.allowance.x).toBeCloseTo(tang.lockup!.x, 6);
for (const [id, s] of measured) if (!s.textOnly) expect(s.allowance, id).toEqual(s.lockup);
});
it("only Slate Band is a dark surface", () => {
expect(measured.filter(([, s]) => s.darkSurface).map(([id]) => id)).toEqual(["slate-band"]);
});
@@ -63,7 +78,7 @@ describe("slots.optin", () => {
describe("computeLogoPlacement identity: the placeholder itself comes back as its own box", () => {
for (const [id, s] of measured) {
it(`${id} lockup`, () => {
const l = s.lockup!;
const l = lockupTarget(s);
const p = computeLogoPlacement({
logo: logo({ aspect: l.w / l.h, density: s.lockupDensity, kind: "wordmark", includesName: true }),
slot: s,
@@ -71,7 +86,7 @@ describe("computeLogoPlacement identity: the placeholder itself comes back as it
expect(p.slotKind).toBe("lockup");
closeBox(p.box, l);
});
if (s.mark) {
if (s.mark && !s.textOnly) {
it(`${id} mark`, () => {
const m = s.mark!;
const p = computeLogoPlacement({ logo: logo({ aspect: m.w / m.h, density: s.markDensity }), slot: s });
@@ -82,6 +97,48 @@ describe("computeLogoPlacement identity: the placeholder itself comes back as it
}
});
describe("computeLogoPlacement text-only allowances", () => {
it("Test Vendor in Monolith is 28 pt tall, about 129 wide, and keeps the right edge", () => {
const s = SLOTS.monolith!;
const p = computeLogoPlacement({ logo: Sample_P, slot: s });
expect(p.h).toBeCloseTo(28, 6);
expect(p.w).toBeGreaterThanOrEqual(128);
expect(p.w).toBeLessThanOrEqual(130);
expect(p.x + p.w).toBeCloseTo(s.lockup!.x + s.lockup!.w, 6);
expect(p.y).toBeCloseTo(s.allowance.y, 6);
expect(p.slotKind).toBe("lockup");
});
it("a very wide logo in Monolith is capped by the 160 pt width", () => {
const s = SLOTS.monolith!;
const p = computeLogoPlacement({ logo: logo({ aspect: 10, density: 0.3, kind: "wordmark", includesName: true }), slot: s });
expect(p.w).toBeCloseTo(160, 6);
expect(p.h).toBeCloseTo(16, 6);
expect(p.x + p.w).toBeCloseTo(560, 6);
});
it("Test Vendor in Tangerine fills 36 pt of height (about 166 wide) at the left edge", () => {
const s = SLOTS["tangerine-ledger"]!;
const p = computeLogoPlacement({ logo: Sample_P, slot: s });
expect(p.h).toBeCloseTo(36, 6);
expect(p.w).toBeCloseTo(36 * 4.62, 6);
expect(p.x).toBeCloseTo(60.5, 6);
});
it("a square mark in Tangerine is 36 x 36 and vertically centred in the allowance", () => {
const s = SLOTS["tangerine-ledger"]!;
const p = computeLogoPlacement({ logo: logo({ aspect: 1, density: 0.3 }), slot: s });
expect(p.w).toBeCloseTo(36, 6);
expect(p.y + p.h / 2).toBeCloseTo(62 + 18, 6);
});
it("the weight factor still uses the text's density: a dense logo shrinks to 70 % at most", () => {
const s = SLOTS["tangerine-ledger"]!;
const p = computeLogoPlacement({ logo: logo({ aspect: 4, density: 5, kind: "wordmark", includesName: true }), slot: s });
expect(p.h).toBeCloseTo(36 * 0.7, 6);
});
});
describe("computeLogoPlacement on every slot", () => {
const all: [TemplateId, TemplateSlot][] = [...measured, ...optIn];
@@ -89,8 +146,8 @@ describe("computeLogoPlacement on every slot", () => {
for (const [id, s] of all) {
const p = computeLogoPlacement({ logo: Sample_P, slot: s });
expect(p.slotKind).toBe("lockup");
expect(inside(p.box, s.lockup!), id).toBe(true);
if (!s.optIn) {
expect(inside(p.box, lockupTarget(s)), id).toBe(true);
if (!s.optIn && !s.textOnly) {
const ink = p.w * p.h * Sample_P.density;
const placeholderInk = s.lockup!.w * s.lockup!.h * s.lockupDensity;
expect(ink, `${id}: ${ink.toFixed(1)} vs ${placeholderInk.toFixed(1)}`).toBeLessThanOrEqual(placeholderInk * 1.05);
@@ -103,7 +160,7 @@ describe("computeLogoPlacement on every slot", () => {
for (const [id, s] of all) {
for (const l of shapes) {
const p = computeLogoPlacement({ logo: l, slot: s });
expect(inside(p.box, (s.mark ?? s.lockup)!), `${id} aspect ${l.aspect}`).toBe(true);
expect(inside(p.box, markTarget(s)), `${id} aspect ${l.aspect}`).toBe(true);
expect(p.w / p.h).toBeCloseTo(l.aspect, 6);
}
}
@@ -114,7 +171,7 @@ describe("computeLogoPlacement on every slot", () => {
expect(s.mark).toBeNull();
const p = computeLogoPlacement({ logo: logo({ aspect: 1 }), slot: s });
expect(p.slotKind).toBe("lockup");
expect(inside(p.box, s.lockup!)).toBe(true);
expect(inside(p.box, s.allowance)).toBe(true);
});
it("a denser logo is drawn smaller, at most down to 70 %, and a lighter one is not enlarged", () => {
@@ -169,6 +226,8 @@ describe("computeLogoPlacement legibility floor", () => {
anchor: "left",
vAlign: "middle",
darkSurface: false,
textOnly: false,
allowance: { x: 50, y: 50, w: 100, h },
});
it("raises a logo that the weight factor made too short to 14 pt", () => {