Add the Marble and Highlighter templates, completing the 13 Canva templates

Marble uses a flat-to-gradient band in place of the marble photo and bundles Lustria (OFL).
Both logo slots are opt-in. Quirk 13 documents a fontkit glyph-cache issue that the
tests work around with warm-up renders; the engine fix follows separately.
This commit is contained in:
2026-10-04 12:47:05 +05:30
parent 385d7974cb
commit a2152ae3c6
30 changed files with 3190 additions and 27 deletions
+18
View File
@@ -160,3 +160,21 @@ Structure (react-pdf 4.9 with yoga 3):
## Similarity to other templates
No near-duplicate in the set. This is the only template that combines photographic texture bands, zebra rows and a right-aligned tracked serif title (6 also uses a raster paper texture, with a different layout). It shares the tracked-caps title idea with 4/format.pdf and 9/10, and the grey-band idea with 9/10. It has nothing in common with the in-app IBM Plex template beyond the data blocks. Effort is medium: new header, parties band, zebra table, band decorations and a font substitution.
## Implementation notes
Built as `src/pdf/templates/marble/`: a standalone template (`tokens.ts` the type roles, palette and Canva distances, `plan.ts` the pure planner, `Layout.tsx` the body, `marble.tsx` the definition and the page art) on the shared blocks. Measured against `1.pdf` with `npm run templates:compare -- marble --rows=3` (the sample images are in `/tmp/invoice-c6b/`, left Canva, right ours; `--numbers` prints the baselines). Lustria is bundled for this template (`public/fonts/lustria`, OFL, regular only; the upright file is registered as its italic, like the other families without one).
**Matches the design (measured, A4):** the title baseline 124.9 with its ink ending on the right edge (Canva 124.9 / 549.0); Issued and Due at 159.6 and 177.1 (159.6 / 177.1), ending on the same edge; the vendor name at x 38.5, level with the title; BILL TO and PAYABLE TO at 247.9 with the first lines at 269.0 and the 18.2 pt pitch after them (247.9 / 269.0 / 287.2 / 305.4), the columns at x 38.5, 211.4 and 380; the table heading 32.3 pt above the first row baseline (32.3) and the first band 8.85 pt under it; rows of 32 pt in `#F7F7F8` on the 1st, 3rd, 5th row, the description 9.5 pt inside the band's edge; qty centred on x 355.1 (355.5); the hairlines `#E7E1E6` at 0.75 pt from x 31.5 to 549.5; AMOUNT DUE ending on x 535.4 (535.4); the footer lines at 746.9 / 762.2 / 777.5 with the second column at x 197.8 while the address is short (746.9 / 762.2 / 777.5, 197.8). Every colour is one of the design's (a test scans the sources); the type is Lustria 25.18 / 12.65 / 11.68 / 14.6 pt at the measured tracking and Jost at the sizes of the design.
**Intentional deviations, and why:**
- **The marble photograph** is not bundled (stock media). Both bands are full-bleed, 64 pt at the top and 130 pt at the foot, filled with a soft grey sheen (a static Svg gradient from `#F4F3F5` through `#ECEAEE` back to `#F4F3F5`), on every page. The flow starts at y 86 (under the band and the continuation line) and stops 12 pt above the foot band; Letter keeps the same band, so its missing 50 pt come out of the gaps.
- **Now is drawn in Jost.** Jost sets about 12% narrower than Now, so the body roles carry 0.10 to 0.13 em of tracking on top of the design's 0.04 em; that brings a string such as "Description of service goes here" to the Canva width (180 pt). The right-aligned tracked lines are drawn one tracking step into the margin, so their ink ends on the edge.
- **Header.** The number goes on its own line ("Invoice No. INV/2026-001") between the title and Issued, so a "TAX INVOICE" title never meets a long number. The title shrinks (to 13 pt at the least) when the stack is narrow; a number too wide for the stack shrinks, then splits at `@ . / -`. Due shows "On Receipt" when the invoice has no due date. A cancelled invoice has a red CANCELLED line above the number and its sentence at the foot.
- **Left corner.** The design leaves it empty: it holds the vendor name in Lustria caps (level with the title), or the opt-in logo. The logo is off by default (the template has no placeholder), 150 x 50 at x 38.5 with its bottom edge on y 136; a wordmark replaces the name, a mark keeps it under the mark.
- **Parties band.** Three columns: BILL TO (name, address, GSTIN, place of supply, PO), PAYABLE TO (the account holder, then the bank lines as "label: value", the account number as "A/c No.") and SUPPLIER (GSTIN and PAN). A column with nothing to show is left out and the next one moves up; the band grows with the tallest one.
- **Table.** HOURS / PRICE / TOTAL read Qty / Rate / Total. The rate keeps its unit on its own line ("₹1,200.00/hr") while the column can grow to take it (the column then widens, taking the room from the description); qty stays centred, the rate and total are right-aligned (9.5 pt inside the edge) so Indian-format numbers line up, which Canva does not do (it centres them). HSN/SAC is an 8.5 pt sub-line under the description; wrapped descriptions grow the row (the zebra fill is the row's own).
- **Totals.** The stack (SUBTOTAL, DISCOUNT, TAXABLE VALUE, CGST / SGST / IGST) is Lustria 9 pt caps labels with the values right-aligned, over AMOUNT DUE in the design's 14.6 pt (the rupee sign comes from the fallback font). The amount in words and the reverse-charge line are at the left, level with the first row; they never reach the stack.
- **Closing.** The notes (when 90 pt tall or less) sit at the left and the signature (the user's image, or the signatory and "Computer-generated invoice; no signature is required.") at the right, above the foot band, never inside it. Longer notes flow as a block of their own before the closing; a block that would start with less than a line of room starts on the next page.
- **Foot band.** The vendor name and address (column 1, x 40.7, up to 190 pt wide, five lines on A4 and four on Letter, an ellipsis after that) and the phone and e-mail (column 2, one fitted line each, shrunk before an ellipsis) are page art, printed on every page. Column 2 stays at x 197.8 while the address is short and moves right of column 1 when it is not. A name or address with one unbreakable word is fitted to one line. The full supplier identity is in the parties band, so nothing is lost to the cap. The "Generated with Voiced" line and the page label sit inside the band, 14 pt off the page foot.
- **Margins.** 31.5 pt left and 45.8 pt right (the design's asymmetry), 86 pt at the top (the logo slot's top), 24 pt plus the 118 pt reserve at the foot; the narrow and normal presets replace the sides and the foot margin with 36 and 54 pt (the content then stops higher above the band) and the top stays at its 86 pt floor. Elastic gaps (Letter, and A4 when a one-page invoice overflows by less than the slack): header to hairline (36.8 to 14 pt), the hairlines' spacing, hairline to table heading (50.2 to 22), row padding (down to 3.5 pt a side), last row to the totals and the totals to the signature.
+16
View File
@@ -155,3 +155,19 @@ Voiced fields with no Canva slot, and where they go:
## Similarity to other templates
Closest siblings are 7 and 11 (Poppins, a colour-filled header on a boxed grid table, boxed totals) and 8 (filled blue header plus an icon contact row). Only 2 has underlined fill-in fields, a detached header bar and 3pt frames. It is unrelated to the in-app template and to format.pdf/4.pdf. If 7 and 11 are built too, share one 'boxed grid table' component with accent colour and frame weight as parameters.
## Implementation notes
Built as `src/pdf/templates/highlighter/`: a standalone template (`tokens.ts` the type roles, palette and Canva distances, `plan.ts` the pure planner, `Layout.tsx` the body, `highlighter.tsx` the definition) on the shared blocks and the Carbon icon paths (`ContactIcon`). Measured against `2.pdf` with `npm run templates:compare -- highlighter --rows=3` (images in `/tmp/invoice-c6b/`, left Canva, right ours; `--numbers` prints the baselines). The Canva page is a blank form, so the comparison sample here fills the fields and rows; every distance below was measured with the design's own content shape (no due date, no address, a two-line payment block).
**Matches the design (measured, A4):** the title baseline 98.0 at x 60.3 (98.0 / 60.3); the fields at 132.2 / 166.3 / 200.4 (132.2 / 166.0 / 200.4) with their label at x 60.3 and the value 5 pt after it on a 0.75 pt `#423869` underline ending at x 266; "Payment Method:" at 162.9 and the bold bank lines at 185.1 and the 16.5 pt pitch (162.9 / 185.1 / 201.6), x 352.5; the table bar 38.4 pt tall (frame included) with the Qty and Price headings centred on x 277.4 and 360.8 (277.4 / 360.8) and the Total heading 1.9 pt right of the design's (the design sets it 2.4 pt left of its column centre); a 4.5 pt gap under the bar, the body's own 3 pt frame, hairlines between the rows and columns; rows of 30.65 pt; the totals box one price-column wide plus one total column, its total label cell yellow; the sign-off and its ruled lines 212.3 pt wide ending 1.5 pt inside the frame's edge. Every colour is one of the design's four (a test scans the sources); the type is Poppins 400/700 and Montserrat 400 at the design's sizes.
**Intentional deviations, and why:**
- **Content box.** The 3 pt strokes are centred on x 59.6 and 536.0, so the frames reach 1.5 pt outside them: the content box is the frame's outer edge (58.1 to 537.5, 479.4 wide), and everything is measured from it. Margins are 58.1 / 57.8 pt at the sides, 50 pt at the top (the title's line box is the design's 64 pt zone) and 36 pt plus the footer reserve at the foot; the narrow and normal presets replace the margins with 36 and 54 pt, and the frame, the table columns and the totals box follow the content width.
- **Fields.** Date, Invoice number, a Due date field in the same style (only when the invoice has one), then Billed to with the client's name on its underline; the address, GSTIN, place of supply and PO number are 11 pt lines under it. A value that does not fit its underline wraps (the underline moves down) or, for an unbreakable token, shrinks and then splits at `@ . / -`. Pitch is a steady 34.1 pt (the design's is 33.8 / 34.4). The fields and the payment block sit side by side and the table starts under the taller one, at least 18 pt below it (the design leaves 64 pt, which Letter and a longer client block take back, down to 14 pt).
- **Top right.** The vendor's name (Poppins Bold 13), GSTIN and PAN, right-aligned in x 352 to 536, or the opt-in logo (off by default; 150 x 50, right-aligned, its top on y 50) above them; a wordmark replaces the name, a mark keeps it under the mark. The zone is 64 pt tall and grows with a logo, which moves the rest down. A cancelled invoice has a red CANCELLED line there and its sentence at the foot.
- **Payment block.** The account holder's name is added as its own line, and the bank lines read "Bank Name : ...", "Account No : ..." (then Branch and IFSC).
- **Table.** The design's rows are empty; ours carry the item: description left (9 pt in) with an HSN/SAC sub-line, qty centred, price and total right-aligned 9 pt inside their cells (the unit stays on the rate's line, "₹1,200.00/hr", while the column can take it). The form look pads with empty rows to 8 on A4 and 7 on Letter, but the pad rows give way first: they are dropped before the sign-off would have to move to a second page, and a long invoice has none. The 3 pt frame is closed at the foot after the last row only; where a table breaks onto a second page the page ends on the row's hairline (the frame's sides run to it), and the next page repeats the bar and the body's top rule.
- **Totals.** A stack of two-cell rows (label cell | value cell) lined up with the price and total columns: Subtotal, Discount, Taxable value, CGST / SGST / IGST ("CGST @ 9% :") with 0.75 pt rules between the rows and the 3 pt frame around them, and the "total :" row with the yellow label cell and a bold value. When a label needs more room than the price column has, the box grows to the left. The amount in words and the reverse-charge line are at its left, level with its first row.
- **Sign-off.** THANK YOU and the phone, e-mail and address with Carbon icons (the design's own Material paths are not used, so the app keeps one icon set); the phone and e-mail are one fitted line each (a wrapped phone number can break after a bracket), the address wraps. The ruled lines carry the notes, one wrapped line per ruled line (three lines at least; more than six lines and the notes flow as a block of their own before the sign-off), and the signature block (the image or "Computer-generated invoice; no signature is required.", the rule, the name and designation) takes the last one. The sign-off sits at the foot of the last page.
- **Elastic gaps** (Letter, and A4 when a one-page invoice overflows by less than the slack): fields to table bar (down to 14 pt), the row padding (down to 4.5 pt a side) and the totals to the sign-off (24 to 12 pt). The type is never scaled.