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 Canva 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:
@@ -136,7 +136,14 @@ async function main() {
|
||||
for (const id of ids) {
|
||||
const slot = slotFor(id);
|
||||
const page = await renderPage(TEMPLATE_PDF[id]);
|
||||
const L = slot.lockup;
|
||||
// The region covers the placeholder and, for a text-only one, its (larger) allowance.
|
||||
const L = slot.textOnly
|
||||
? { x: Math.min(slot.lockup.x, slot.allowance.x), y: Math.min(slot.lockup.y, slot.allowance.y), w: 0, h: 0 }
|
||||
: slot.lockup;
|
||||
if (slot.textOnly) {
|
||||
L.w = Math.max(slot.lockup.x + slot.lockup.w, slot.allowance.x + slot.allowance.w) - L.x;
|
||||
L.h = Math.max(slot.lockup.y + slot.lockup.h, slot.allowance.y + slot.allowance.h) - L.y;
|
||||
}
|
||||
const region = {
|
||||
x: Math.max(0, Math.floor(L.x - PAD)),
|
||||
y: Math.max(0, Math.floor(L.y - PAD)),
|
||||
@@ -158,7 +165,7 @@ async function main() {
|
||||
ctx.font = "bold 16px sans-serif";
|
||||
ctx.fillText("Logo placement sign-off sheet (Canva placeholder, then four test logos)", 8, 22);
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillText("Red outline = measured placeholder box, blue = mark box. Dashed = opt-in area (no Canva placeholder). Green = logo box as placed.", 8, 40);
|
||||
ctx.fillText("Red = measured placeholder box, blue = mark box, orange dashed = allowance of a text-only placeholder, red dashed = opt-in area (no Canva placeholder). Green = logo box as placed.", 8, 40);
|
||||
const heads = ["Canva placeholder", ...logos.map((l) => `${l.label} (${l.aspect.toFixed(2)}:1, d ${l.density.toFixed(2)})`)];
|
||||
ctx.font = "bold 11px sans-serif";
|
||||
heads.forEach((t, i) => ctx.fillText(t, LABEL_W + GUTTER + i * (cellW + GUTTER), HEAD_H - 6));
|
||||
@@ -182,6 +189,7 @@ async function main() {
|
||||
const lines = [
|
||||
slot.optIn ? "opt-in, off by default" : "Canva placeholder",
|
||||
`lockup ${f(slot.lockup)}`,
|
||||
...(slot.textOnly ? [`text-only: allowance ${f(slot.allowance)}`] : []),
|
||||
`mark ${f(slot.mark)}`,
|
||||
`density ${slot.lockupDensity.toFixed(2)} / ${slot.markDensity.toFixed(2)}`,
|
||||
`${slot.anchor}, ${slot.vAlign}${slot.darkSurface ? ", dark surface" : ""}`,
|
||||
@@ -214,6 +222,7 @@ async function main() {
|
||||
if (slot.optIn) box(ctx, slot.lockup, region, "#dd0000", true);
|
||||
else {
|
||||
box(ctx, slot.lockup, region, "#dd0000");
|
||||
if (slot.textOnly) box(ctx, slot.allowance, region, "#e08a00", true);
|
||||
if (slot.mark && slot.mark !== slot.lockup && slot.mark.w !== slot.lockup.w) box(ctx, slot.mark, region, "#0066cc");
|
||||
}
|
||||
c.restore();
|
||||
|
||||
@@ -39,6 +39,30 @@ const SEARCH_RECTS = {
|
||||
13: { id: "citrus-split", rect: { x: 325, y: 65, w: 165, h: 55 }, kind: "lockup", anchor: "left", vAlign: "middle", dark: false },
|
||||
};
|
||||
|
||||
/*
|
||||
* Allowances for text-only placeholders: the placeholder's own box (46.8 x 9 pt, 82.5 x 14.8 pt) would
|
||||
* make a real logo unreadable, so the target box comes from the template docs and keeps the text's
|
||||
* anchor edge.
|
||||
* monolith docs/templates/03-monolith.md "Mapping to Voiced fields": 'CAVEIN' becomes the vendor logo,
|
||||
* right-aligned, at most 28 pt tall so the 3-line stack keeps its 21 pt pitch (a 3:1 PNG is
|
||||
* 84 x 28); up to 160 wide, ending at the same right edge (x 560); top at the text's cap top.
|
||||
* tangerine-ledger docs/templates/11-tangerine-ledger.md "Mapping": 'Rimberio' becomes the vendor logo, maxHeight 36
|
||||
* at x 59.5, y about 62; up to 220 wide, left edge kept.
|
||||
*/
|
||||
const ALLOWANCES = {
|
||||
monolith: { w: 160, h: 28, top: 54.75, vAlign: "top" },
|
||||
"tangerine-ledger": { w: 220, h: 36, top: 62, vAlign: "middle" },
|
||||
};
|
||||
|
||||
const r2 = (v) => Math.round(v * 100) / 100;
|
||||
|
||||
/** The allowance box for a text-only slot: its width and height from the docs, anchored where the text is. */
|
||||
function allowanceBox(id, lockup, anchor) {
|
||||
const a = ALLOWANCES[id];
|
||||
const x = anchor === "right" ? lockup.x + lockup.w - a.w : lockup.x;
|
||||
return { x: r2(x), y: a.top, w: a.w, h: a.h };
|
||||
}
|
||||
|
||||
const TEMPLATE_IDS = [
|
||||
"classic", "marble", "highlighter", "monolith", "linea", "monogram", "serenity", "crimson-grid",
|
||||
"cobalt-stripe", "slate-band", "teal-swoosh", "tangerine-ledger", "purple-pop", "citrus-split",
|
||||
@@ -125,8 +149,6 @@ function firstColumnGroup(mask, w, h, box) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const r2 = (v) => Math.round(v * 100) / 100;
|
||||
|
||||
function toPt(rect, box) {
|
||||
return {
|
||||
x: r2(rect.x + box.minX / SCALE),
|
||||
@@ -172,6 +194,7 @@ async function main() {
|
||||
}
|
||||
|
||||
const rounded = (v) => Math.round(v * 1000) / 1000;
|
||||
const textOnly = spec.kind === "text";
|
||||
slots[spec.id] = {
|
||||
page: { w: PAGE.w, h: PAGE.h },
|
||||
mark: markBox,
|
||||
@@ -179,8 +202,10 @@ async function main() {
|
||||
markDensity: rounded(markDensity),
|
||||
lockupDensity: rounded(lockupDensity),
|
||||
anchor: spec.anchor,
|
||||
vAlign: spec.vAlign,
|
||||
vAlign: textOnly ? ALLOWANCES[spec.id].vAlign : spec.vAlign,
|
||||
darkSurface: spec.dark,
|
||||
textOnly,
|
||||
allowance: textOnly ? allowanceBox(spec.id, lockupBox, spec.anchor) : lockupBox,
|
||||
};
|
||||
rows.push({ n, id: spec.id, mark: markBox, lockup: lockupBox, markDensity, lockupDensity, anchor: spec.anchor, dark: spec.dark, bg });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user