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.
22 KiB
Template 1: Marble (marble)
Source: templates/Modern Neutral Invoice Template/1.pdf. All coordinates are in points from the top-left of the Canva page (595.5 x 842.25). Measured from the PDF content streams during design analysis.
Style
Airy editorial invoice. Letter-spaced Lustria serif caps (right-aligned 'INVOICE #1234', column heads, 'AMOUNT DUE'), a light geometric sans body (Now), pale-grey zebra rows, two mauve-grey hairlines around the parties band, and photographic white-marble bands at the top (56pt) and bottom (130pt). The bottom band carries the vendor contact footer. All text is pure black with no accent colour.
Fonts
Measured from the embedded subsets. Tracking is built into per-glyph Td moves (Tc=0), so the em values below are measured gaps.
- Lustria Regular. OFL 1.1 (name table: 'Copyright (c) 2011, Matthew Desmond ... with Reserved Font Name Lustria'; on Google Fonts), so it is safe to bundle. upm 1000, hhea ascent 955 / descent -327 / lineGap 0, capHeight 704. Used in ALL CAPS only:
- Title 'INVOICE #1234': 25.18pt, letterSpacing 4.83pt (0.192em)
- Table heads DESCRIPTION / HOURS / PRICE / TOTAL: 11.68pt, 2.24pt (0.192em)
- 'BILL TO:' / 'PAYABLE TO:': 12.65pt, 1.39pt (0.110em)
- 'AMOUNT DUE: $3500': 14.60pt, 2.80pt (0.192em)
- Now Regular. PROPRIETARY. Name table reads '(c)2015 Now Typeface is a trademark of Alfredo Marco Pradil. All rights reserved.', fsType 8. It is not OFL, so it cannot go into the xbps without a licence. upm 800, ascent 736 / descent -240 (0.92 / -0.30em), capHeight 0.75em. Used for:
- Issued/Due lines and the BILL TO / PAYABLE TO blocks: 11.55pt, 0.48pt (0.042em). Pitch is 17.5 for the meta lines and 18.2 for the blocks, so lineHeight 1.52 / 1.58.
- Table cells: 10.57pt, 0.46pt (0.044em)
- Footer: 9.60pt, 0.61pt (0.064em), pitch 15.3-15.4, so lineHeight 1.6 Substitute for Now: Poppins Regular (OFL, already needed by templates 2/3/7/8/11), about 0.5pt smaller with about 0.02em tracking. Advance widths per 1000 units, Now vs Poppins: n 560 vs 640, m 840 vs 1030, s 414 vs 534, u 524 vs 640, O 894 (round geometric). Poppins therefore sets typical strings 3-5% wider ('reallygreatsite.com' 9414 vs 9823 in Poppins Medium) and lowercase-heavy runs up to about 15% wider, so columns must flex. If one more family is acceptable, Jost (OFL, Futura-like, narrow s, circular O) is a closer look-alike. Rupee sign: Lustria (2011, Latin only) almost certainly lacks U+20B9. Any amount set in Lustria needs fontFamily ['Lustria','IBM Plex Sans']; the sign is verified present in the IBM Plex Sans and Montserrat TTF cmaps.
Colours
#FFFFFF page background. #000000 for all text (no greys anywhere). #F7F7F8 zebra row bands (rows 1, 3, 5, 7). #E7E1E6 hairline rules (mauve-grey, 0.75pt). Marble raster: very light warm grey with lilac-grey veins. Visual estimate only: base about #EEEDEF, veins about #CFCAD0. For a flat replacement band use #F2F1F3 or the zebra #F7F7F8. No accent colour. A brand accent could optionally replace #E7E1E6 on the rules.
Layout
Coordinates are pt from the top-left of the 595.5 x 842.25 visible page. y is the text baseline unless noted.
- Top marble band: y 7.6 to 63.8 (h 56.3), x 0 to 595.5. The 7.6pt white sliver above it comes from Canva's clip rectangle (starts at 7.56). Render it full-bleed from 0 to 64.
- Header, y 64 to 200. The left half (x 31 to 300) is empty and becomes the logo slot. The right column is right-aligned at x 549.0:
- 'INVOICE #1234', baseline 124.9. Canva box x 312.7-554.4, top 101.5; glyph run x 313.4-549.0, w 235.6.
- 'Issued: 06/09/2021' baseline 159.6 and 'Due: On Receipt' baseline 177.1 (pitch 17.5). Right edge 549.0, box x 426-550.
- Rule 1: y 219.4, x 31.5 to 549.8, 0.75pt #E7E1E6.
- Parties band, 3 columns with 2 used:
- Col A at x 38.5: 'BILL TO:' baseline 247.9, then lines at 269.0 / 287.2 / 305.4.
- Col B at x 211.4: 'PAYABLE TO:' baseline 247.9, same 3 line baselines.
- Col C (x about 380 to 549) is empty. Spacing: rule to label baseline 28.5, label to first line 21.1, last line to rule 17.8.
- Rule 2: y 323.2, same style. The band is 103.8pt between rules.
- Gap from 323 to 375 (about 52pt).
- Table header baseline 384.9, with no fill or rule. The first row band starts at 398.6, 13.7pt below the header baseline.
- 7 item rows, table x 31.2 to 549.6 (w 518.4). Zebra bands (h 32.0) start at 398.6 / 462.6 / 529.5 / 597.2. Row baselines are 417.2 / 448.7 / 481.2 / 515.7 / 548.1 / 583.3 / 615.8, an irregular 31.5-35.2pt pitch; normalise to 32.
- 'AMOUNT DUE: $3500' baseline 668.7. Glyph run x 337.2-531.8, box right edge 535.4, so it is right-aligned about 14pt inside the table edge, 29pt below the last band.
- Gap from 669 to 712.
- Bottom marble band: y 712.0 to 842.25 (h 130.3), full-bleed. Footer text starts about 35pt below the band top:
- col 1 at x 40.7: name / address line 1 / address line 2, baselines 746.9 / 762.2 / 777.6
- col 2 at x 197.8: phone / email / web, same baselines
- col 3 (x about 360 to 549) is empty Effective margins: bands and rules start at x 31.2 with text inset to 38.5-40.7. The right edge is 549.0-549.8, giving a 45.7pt right margin, so Canva's placement is asymmetric. Recommend symmetric 36pt page padding plus an 8pt text inset.
Items table
Column centres, measured from the header and value glyphs (Canva centres the numbers):
- DESCRIPTION: left-aligned (header x 38.5, cells x 40.7)
- HOURS: centre x about 355.5
- PRICE: centre x about 438.4
- TOTAL: centre x about 512.1 Column boxes inside table x 31.2 to 549.6 (w 518.4) that reproduce those centres:
- Description 31.2-309.2 (278.0, 53.6%)
- Hours 309.2-401.8 (92.6, 17.9%)
- Price 401.8-474.7 (72.9, 14.1%)
- Total 474.7-549.6 (74.9, 14.4%) Header: Lustria 11.68pt caps, tracking 2.24pt, #000, no background, no border. Rows: Now 10.57pt #000, tracking 0.46pt, height 32 (31.98). Zebra #F7F7F8 on the 1st/3rd/5th/7th rows (0-based even), white otherwise. No borders. Text is vertically centred (baseline 18.6pt below the band top) and the description is inset 9.5pt from the band's left edge. The Canva sample has 7 rows with values '5', '$100', '$500'. Recommendation: use minHeight 32 (not a fixed height) with paddingVertical about 9, and alternate the zebra by index. Keep qty centred, but right-align the rupee amounts (Rate and Amount) with a 9.5pt right inset so Indian-format numbers line up on the decimal. This is a deliberate deviation from Canva.
Totals and payment
Canva has one totals line: 'AMOUNT DUE: $3500' (Lustria 14.60pt caps, tracking 2.80pt, black), right-aligned at x about 535, baseline 668.7. It has no subtotal or tax rows, no bank block ('PAYABLE TO:' holds name/email/website), no notes, no amount in words, no signature and no thank-you. The footer band repeats the vendor name and address (col 1) and phone/email/web (col 2) in Now 9.6pt. Voiced version, keeping the same idiom:
- Add a right-aligned stack above AMOUNT DUE. Labels in Lustria 9pt caps (tracking 0.192em), values in the 10.57pt body font, pitch about 16pt, values right-aligned to x about 535. Rows: SUBTOTAL, DISCOUNT (if > 0), then CGST 9% + SGST 9% or IGST 18%.
- Then 'AMOUNT DUE: ' at 14.6pt, with the rupee sign from the fallback font.
- Amount in words on the left, on the AMOUNT DUE row: x 38.5, max width about 280, body 9.6pt, 'Rupees ... Only'.
- Notes under the words, 9.6pt.
- Signature or the computer-generated note in footer col 3, or just above the bottom band on the right.
Decoration and images
Raster: a single image XObject (X8), a 1321 x 1321px JPEG (DCTDecode, ICCBased sRGB, 286,646 bytes) showing light grey/white marble (Canva stock). It is drawn twice as a 700 x 700pt square and clipped: (a) Top band: clip x 0-595.5, y 7.56-63.83; image placed x -48.4 to 651.7, y -218.9 to 481.1. (b) Bottom band: clip x 0-595.5, y 711.97-842.25; image placed x -55.2 to 644.8, y 406.7 to 1106.7. That is about 1.9px/pt (about 136 dpi). It cannot be rebuilt as vector, and Canva's content licence does not allow redistributing stock media extracted into a template, so do NOT bundle the extracted JPEG. Options:
- a flat band in #F2F1F3 or #F7F7F8 (zero cost)
- an Svg LinearGradient (#F4F3F5 to #ECEAEE) for a soft sheen
- a self-sourced CC0 marble JPEG, pre-cropped to two strips (about 1600x150px top, 1600x350px bottom, 80 KB or less each), placed with <Image style={{objectFit:'cover'}}> Vector parts are trivial: 4 zebra rects #F7F7F8 at 518.4 x 32.0, and 2 hairlines #E7E1E6 0.75pt, 518.3 long. The alpha-0 rectangles in the page stream are tagged-PDF layout artefacts; ignore them.
Mapping to Voiced fields
Template region to Voiced field:
- Empty header-left (x 38.5 to 300, y about 92 to 136, bottom-aligned with the title baseline 124.9): vendor logo. The logo is a 3:1 PNG (2172x724); fit it in 150x50 with objectFit contain. vendorName can optionally go under it in Lustria 12.65 caps, tracking 0.11em.
- 'INVOICE #1234': 'INVOICE', or 'TAX INVOICE' when taxType is not none, as the 25.18pt title. Move the number to its own right-aligned line above 'Issued:', for example 'No. INV/2026-27/0042' in the 11.55pt body font, so the tracked title stays short.
- 'Issued:': invoiceDate.
- 'Due:': dueDate (already in types.ts); show 'On Receipt' when empty.
- BILL TO: clientName / clientAddress (multi-line) / 'GSTIN: ' + clientGstin (optional) / 'Place of supply: (
)' / 'PO: ' + poNumber. - PAYABLE TO: bank.accountName (fallback vendorName), bankName + branch, 'A/c ' + accountNo, 'IFSC ' + ifsc, 'UPI ' (planned).
- Empty parties col C (x about 380 to 549): vendor statutory IDs (GSTIN vendorGstin, PAN vendorPan, state code), with Lustria 9pt caps labels and body-font values.
- Table to items:
- description
- HOURS becomes 'QTY': quantity + unit; the header can stay HOURS when every unit is hour
- PRICE becomes 'RATE': rate, or a dash when mode = fixed
- TOTAL becomes 'AMOUNT': computeLineAmount
- AMOUNT DUE: total, preceded by the subtotal / discount / CGST / SGST / IGST rows.
- Footer col 1: vendorName + vendorAddress.
- Footer col 2: vendorPhone, vendorEmail. Website is not in Settings; drop it or add a field.
- Footer col 3 (empty): signature image (120x40 max) + 'Authorised signatory', or 'Computer-generated invoice; no signature required.' at 8pt. Put the 'Generated with Voiced' credit at 7pt along the band bottom. Voiced fields with no Canva slot, and where they go:
- GSTIN/PAN: parties col C
- Bank details: PAYABLE TO
- Place of supply: BILL TO
- CGST/SGST/IGST: totals stack
- Amount in words: left of AMOUNT DUE
- Notes: below the words
- Signature: footer col 3
- HSN/SAC (not in the current types.ts): a grey 9pt second line under the description ('SAC 998314'), or a 50pt column taken from Description
Overflow risks
- Title: right-aligned, tracked 25pt. 'INVOICE #1234' already fills 235.6pt of its 241pt box. 'TAX INVOICE #INV/2026-27/0042' (about 28 glyphs at about 19pt) is about 520pt and would run left across the logo zone. This is the same class of bug as the reported vendor-address/INVOICE overlap. Fix: put the number on its own line, auto-fit the font size, or both.
- Parties band: col A is 173pt wide (38.5 to 211.4), so long client names and addresses wrap, and PAYABLE TO grows from 3 lines to 5-6 (about 110pt). The rules must be borders of a flow View, not absolutely placed at 219.4/323.2.
- Unbreakable tokens at 11.55pt (emails, UPI IDs, 15-character GSTINs, websites): 'accounts@longcompanyname.co.in' is about 175pt, wider than the column. Add a hyphenation callback that allows breaks at @ . / - or shrink the text.
- Description column is about 278pt (about 45 chars per line at 10.57pt), so rows go multi-line; use minHeight 32.
- Vertical budget: the last row ends at 629, AMOUNT DUE sits at 669, the bottom band starts at 712. Adding Subtotal plus two tax rows (+48pt) and the words line means 6 or more items no longer fit on page 1 of an A4. Page 2 must still reserve the fixed bands: paddingTop at least 64 + gap, paddingBottom at least 130 + gap.
- Footer col 1 is about 150pt wide at 9.6pt. An Indian address (door no., street, area, city, state, PIN) wraps to 4-6 lines. From baseline 746.9 at 15.4pt pitch, 6 lines end at 823.9 (18pt from the edge), and 7 or more leave the page. Widen col 1 to about 210pt (shift col 2 to x about 260) and cap it at maxLines 5 with an ellipsis.
- Rupee sign in Lustria: the glyph is missing unless a fallback font is set.
US Letter adaptation
Letter is 612x792 against Canva's 595.5x842.25: 16.5pt wider and 50.25pt shorter. Do not scale the type.
- Horizontal: Page paddingHorizontal comes from the user margin, with a template default of 36. Canva's effective 31.2 left / 45.7 right is an asymmetry to normalise. Content width goes from 518.4 on A4 to 540 on Letter at 36. Set column widths as percentages (53.6 / 17.9 / 14.1 / 14.4).
- Bands: absolute, full-bleed and fixed. The top band is 0 to 64 on both sizes. The bottom band is 130pt on A4 and about 100-110pt on Letter (the footer text block is about 46pt plus about 30pt of top padding).
- Vertical: on Letter, shrink the 52pt gap above the table head to about 30 and the 43pt gap above the bottom band to about 24. Page paddingTop = 64 + userTop (about 28); paddingBottom = band height + userBottom (about 16).
- The margin setting changes only Page padding; decorations always ignore margins.
react-pdf notes
Structure (react-pdf 4.9 with yoga 3):
<Page size={size /* 'A4' | 'LETTER' */} style={{ paddingTop: 64 + 30, paddingBottom: BAND + 20, paddingHorizontal: m, fontFamily: BODY, fontSize: 11.55, lineHeight: 1.55, color: '#000' }}>
<View fixed style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 64, backgroundColor: '#F2F1F3' }} />
(or <Image fixed src={marbleTop} style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 64, objectFit: 'cover' }} />)
<View fixed style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: BAND, paddingTop: 30, paddingHorizontal: m + 4, flexDirection: 'row' }}> 3 footer columns
- Header: a row View with justifyContent 'space-between' and alignItems 'flex-end'. Left logo View: flexShrink 0, maxWidth '45%'. Right meta View: flexShrink 1, alignItems 'flex-end'.
- Parties: a row View with borderTopWidth and borderBottomWidth 0.75 in #E7E1E6, paddingTop 18, paddingBottom 16. Its 3 columns each need flex 1 with flexShrink 1, otherwise text in a row will not wrap.
- Table: give the header row minPresenceAhead of about 64 so it is never left alone at a page bottom. Rows:
items.map((it, i) => <View wrap={false} style={{ flexDirection: 'row', minHeight: 32, alignItems: 'center', paddingHorizontal: 9.5, backgroundColor: i % 2 === 0 ? '#F7F7F8' : undefined }}>)
- Wrap the totals stack, AMOUNT DUE and the words line in one .
- Absolute children of resolve against the page edge (padding ignored). The existing InvoiceDocument footer already relies on this (position absolute, bottom 24, left/right 40, with paddingHorizontal 40). The fixed bands must be matched by Page paddingTop/paddingBottom, or continuation pages will draw over them.
- Tracked caps: { fontFamily: 'Lustria', fontSize: 25.18, letterSpacing: 4.83, textTransform: 'uppercase' }. react-pdf adds letterSpacing to every glyph advance, including the last (verify in preview), so right-aligned tracked text sits one letterSpacing short of the edge. Compensate with marginRight: -4.83 (-2.80 for AMOUNT DUE, -2.24 for the table heads).
- Fonts: Font.register Lustria and the substitute from static TTF (or WOFF). The v4 docs list only TTF/WOFF and no variable fonts. Money: fontFamily: ['Lustria', 'IBM Plex Sans']; array fallback is supported in v4 and Helvetica is appended automatically.
- Continuation pages: a fixed element is cloned onto every page. Check in preview that a fixed table-header row lands correctly; if it does not, chunk the items per page and render the header for each chunk.
- Font.registerHyphenationCallback(word => splitAt(word, /[@./-]/)) lets emails, GSTINs and UPI IDs break instead of overflowing.
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.