Add per-template specifications measured from the Canva PDFs
This commit is contained in:
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
# Canva template analysis
|
||||
|
||||
Per-template specifications measured from the 13 Canva PDFs in `templates/Modern Neutral Invoice Template/`. They drive the template implementations in `src/pdf/templates/`.
|
||||
|
||||
| # | id | Name | Style |
|
||||
|---|---|---|---|
|
||||
| 1 | `marble` | Marble | Airy editorial invoice |
|
||||
| 2 | `highlighter` | Highlighter | Bold form-style invoice |
|
||||
| 3 | `monolith` | Monolith | Stark monochrome Poppins |
|
||||
| 4 | `linea` | Linea (could also be labelled 'Original': this is the vendor's own format.pdf design) | Airy, all-charcoal minimalism |
|
||||
| 5 | `monogram` | Monogram | The Linea grid restyled in Open Sans |
|
||||
| 6 | `serenity` | Serenity | A boutique-stationery look |
|
||||
| 7 | `crimson-grid` | Crimson Grid | Bold corporate boxed grid |
|
||||
| 8 | `cobalt-stripe` | Cobalt Stripe | Friendly cobalt-blue business style |
|
||||
| 9 | `slate-band` | Slate Band | Modern geometric |
|
||||
| 10 | `teal-swoosh` | Teal Swoosh | A clean corporate Montserrat layout |
|
||||
| 11 | `tangerine-ledger` | Tangerine Ledger | A crisp business ledger with Poppins Bold display text and Inter body |
|
||||
| 12 | `purple-pop` | Purple Pop | A playful Memphis style on cream: an oversized purple 'Invoice' wordmark; purple, yellow and grey pill-shaped rows and labels; 8-arm asterisk motifs (solid purple, plus huge pale-pink watermarks); and a yellow dome shape |
|
||||
| 13 | `citrus-split` | Citrus Split | A modern split layout on blush pink: a heavy 'INVOICE' wordmark, a light-grey right column, an orange SERVICE header cell and TOTAL bar, orange labels, an icon contact list and faint orange halftone-dot chevrons |
|
||||
|
||||
## Group notes
|
||||
|
||||
CONVENTIONS
|
||||
- All coordinates are pt from the top-left of Canva's 595.5 x 842.25 visible page. The MediaBox is [0 7.83 595.5 850.08]; its y-offset is already subtracted.
|
||||
- Canva lays out in 794 x 1123 CSS px, so pt = px x 0.75.
|
||||
- react-pdf 'A4' is 595.28 x 841.89 (at most 0.4pt smaller), so use size 'A4' directly. 'LETTER' is 612 x 792.
|
||||
- To get the top of a line box from a baseline, subtract ascent x size: Poppins 1.05em, Lustria 0.955em, Now 0.92em, Montserrat 0.968em.
|
||||
|
||||
EXTRACTION FACTS
|
||||
- All text is real Type0/Identity-H text with ToUnicode maps, so it is copy-pasteable.
|
||||
- Letter-spacing is done by per-glyph positioning (Tc=0), so the em values given are measured gaps.
|
||||
- Colours come from the rg/RG operators, sizes from Tf x CTM, and positions from the full form-XObject CTM chain.
|
||||
- Nothing in 1-3 uses blend modes or soft masks. The alpha-0 rectangles are tagged-PDF artefacts. Both selectable-text and rasterised ('flattened') export are therefore straightforward.
|
||||
|
||||
FONTS AND LICENSING (bundling all fonts in the xbps)
|
||||
- Templates 1-3 need Lustria, Poppins 400/500/700 and Montserrat 400. All are OFL; the name tables confirm it. Ship static TTF files plus OFL.txt.
|
||||
- The react-pdf v4 docs say only TTF and WOFF are supported and variable fonts do not work. Google ships Montserrat as a variable font, so take the static instances. The existing code already registers .woff.
|
||||
- 'Now' (template 1) is proprietary ('All rights reserved', fsType 8). Substitute Poppins Regular, or Jost as an optional extra.
|
||||
- The same caution applies across the set. Gotham (12/13) is commercial (H&Co). Check the name tables of TheBigmaker, BuongiornoRastellino, SimpleSerenityScript and BDScript before bundling. DM Sans, Inter, Open Sans and Open Sauce One are OFL.
|
||||
- Rupee sign: verified in the local IBM Plex Sans and Montserrat TTF cmaps. Always render money with fontFamily [templateFont, 'IBM Plex Sans'].
|
||||
- Canva stock rasters (template 1's marble, 6's paper texture) must not be extracted and bundled.
|
||||
|
||||
SET-LEVEL DUPLICATES
|
||||
- 4.pdf and format.pdf are the same design: 9 of 10 inflated streams are byte-identical, and the odd one out is metadata. It is not a new template.
|
||||
- 7 and 11 share one layout (red vs orange accent).
|
||||
- 9 and 10 share one body (slate header band vs teal swoosh corners).
|
||||
- 12 and 13 share content and fonts but have different layouts.
|
||||
- 4, 5 and 6 are one family: tracked-caps title, ISSUED TO / PAY TO, ruled table, right-hand totals, script signature.
|
||||
|
||||
CROSS-CUTTING IMPLEMENTATION RULES
|
||||
(1) Only decorations are positioned absolutely; data always flows.
|
||||
(2) Every text container inside a flexDirection 'row' needs flexShrink 1 (or flex 1, or an explicit width). Yoga's default flexShrink is 0. The vendor text View in the existing InvoiceDocument header (inside the row next to the logo, parent maxWidth 58%) has none of these, which is the likely cause of the reported vendor-address/INVOICE overlap.
|
||||
(3) Headers are a row: a title with auto-fitted size, plus a meta block with a fixed max width.
|
||||
(4) Item rows use wrap={false}; table heads use minPresenceAhead; closing blocks (totals, words, signature) use wrap={false}.
|
||||
(5) Margins come from settings as Page padding; decorations ignore margins. Absolute positions are relative to the page edge, as the existing footer already assumes.
|
||||
(6) types.ts has no client phone/email, vendor website or HSN/SAC. Drop the Canva slots for those or add the fields. dueDate already exists.
|
||||
|
||||
SUGGESTED BUILD ORDER (by effort): 3 (closest to the existing component), then 2, then 1.
|
||||
|
||||
**4.pdf vs format.pdf: the same design, not just a similar one.** The two files differ only in metadata:
|
||||
- Info/XMP title: 'Beige Aesthetic Simple Minimalist Business Invoice' (format.pdf) vs 'Modern Neutral Invoice Template' (4.pdf).
|
||||
- Export timestamps: 15:51:57 vs 19:01:44 UTC on 2026-10-03.
|
||||
- Canva design ID in /Keywords: DAHW94kj5Xs vs DAHW-lfqmdE (both share account/brand ID BAG54HyEGfI).
|
||||
- xref offsets and the trailer /ID.
|
||||
|
||||
Page content streams, font subsets, coordinates and the structure tree are byte-identical: 1500 lines each, 10 differing blocks, all of them metadata. So format.pdf is the same Canva page exported from a separate design, and it still carries Canva's placeholder data (Daniel Gallego / Richard Sanchez / Borcele Bank). The vendor's 'original invoice' is this Canva template with nothing customised. All 13 template PDFs are pages of a single Canva design (DAHW-lfqmdE, exported 19:01:43-46 UTC).
|
||||
|
||||
**The current in-app src/pdf/InvoiceDocument.tsx loosely adapts that design; it is not a copy.** Differences:
|
||||
1. **Type and colour:** the app uses IBM Plex Sans 9.5pt in several greys (#161616, #393939, #525252, #6f6f6f, #e0e0e0). The design uses Montserrat 10pt in one colour, #3D3B3A, with 0.05em tracking.
|
||||
2. **Header:** the app puts logo plus vendor block (18pt name, address, contact, PAN, GSTIN; maxWidth 58%) on the left, and an 18pt semibold right-aligned 'INVOICE'/'TAX INVOICE' with meta rows (maxWidth 40%) on the right. That shared row is where the reported address-vs-INVOICE overlap happens. 4.pdf has no vendor block at all. It has a 38pt Montserrat Regular 'INVOICE' (0.2em tracking) top-left with a 0.75pt rule running to the right edge, and its meta block sits much lower, opposite ISSUED TO.
|
||||
3. **Client block:** the app adds a 1pt #e0e0e0 divider and a grey 7.5pt 'Billed To' label. The design uses a 10pt bold 'ISSUED TO:'.
|
||||
4. **Bank block:** the app's 'Pay To' sits bottom-left beside the signature. In 4.pdf, 'PAY TO:' sits directly under ISSUED TO.
|
||||
5. **Table:** the app has a 1.4pt top border, a 7.5pt grey header, a 0.6pt divider under every row, and flex 5:2:2:2 columns with right-aligned numbers. The design has no top border, a 10pt caps header, one 0.75pt rule, borderless 21.46pt rows, a closing rule, and centred unit price and qty.
|
||||
6. **Totals:** the app uses a 250pt box on the right with a 13pt semibold total over a 1.4pt rule. The design uses a full-width SUBTOTAL row and a right-anchored Tax/TOTAL pair, all 10pt, with the total set apart only by bold weight.
|
||||
7. **Extra fields in the app:** amount in words (italic), notes, PAN/GSTIN, place of supply, PO, discount, CGST/SGST/IGST, the signature image or 'computer-generated' note, and a fixed 'Generated with Voiced' footer.
|
||||
8. **Signature:** 4.pdf's signature is a 60pt script-font name.
|
||||
|
||||
**Recommendation.** A faithful build of 4.pdf gives the vendor the look of the format he actually chose, so it is not redundant with the in-app template. Keep the existing template as 'Voiced Standard' (IBM Plex), since the request was for the Canva templates to be added alongside it. Add 4.pdf as 'Linea' (or label it 'Original') and consider making it the vendor's default. Whether to keep both is a product decision for the parent.
|
||||
|
||||
**4, 5 and 6 are one template family.** They share the grid, rules and spacing. 5 swaps Montserrat for Open Sans and adds a monogram; 6 adds the texture, script and stamp and shifts the grid right. Implement one NeutralTemplate driven by a theme object: fonts, title style, letterSpacing (0.5 or 1), row padding (4 or 5.7), header slot, background and column offset. Share the InfoRow, ItemsTable, Totals and BottomRow components across the three.
|
||||
|
||||
**Applies to all three:**
|
||||
- **Vendor identity:** none of the three has a supplier block, yet supplier name, address and GSTIN are mandatory on a GST invoice. Each entry names a slot in its own row, so long addresses push content down instead of overlapping the title.
|
||||
- **Tax rows:** each design shows 'Tax 10%' (the rate only). Voiced needs CGST+SGST or IGST rows with amounts, plus the amount in words.
|
||||
- **Fonts to bundle** in public/fonts (and so in the xbps):
|
||||
- Montserrat 400/700 (the local v9.000 has ₹), Open Sans 400/500/700, and optionally Poppins 400. All are OFL; ship OFL.txt with them.
|
||||
- BuongiornoRastellinoCyr-Script, TheBigmaker and SimpleSerenityScript are Canva-licensed and must not be extracted. Use the vendor's logo and signature images, SVG outlines for 6's decorative word (after confirming the Canva licence allows that reuse), or OFL scripts.
|
||||
- Keep IBM Plex Sans as the second entry in fontFamily arrays so ₹ always renders.
|
||||
- **Signatures:** use the image or the computer-generated note. Never a typed name in a script font.
|
||||
- **Page size:** Canva's 595.5x842.25 is A4 within 0.2-0.4pt, so the coordinates map 1:1 onto react-pdf A4.
|
||||
- **How these numbers were measured:**
|
||||
- I decoded the content streams with a small standard-library PDF interpreter (transform tracking, ToUnicode, CID widths).
|
||||
- All coordinates are in pt from the top-left and accurate to about ±0.1pt.
|
||||
- Letter-spacing comes from the gaps between glyphs, since Canva positions every glyph separately.
|
||||
- 6.pdf's background colour comes from decoding the JPEG's DC coefficients as YCbCr.
|
||||
|
||||
Method: I used a read-only content-stream interpreter of my own (tracks CTM and text matrices, nested form XObjects, ToUnicode maps, /W widths and ExtGState alpha) and checked against the rendered pages. Coordinates are accurate to about ±0.3pt. Every y value is measured from the page top: top = 850.08 - y_pdf. Canva's unit chain: the page cm is 0.24 with a y-flip, and inner forms scale by 3.126178, so 1 Canva px = 0.7503pt. That makes Tf 16 = 12pt and Tf 14.66 = 11pt; one form also applies a 3.888 scale (Tf 13.33 -> 12.44pt).
|
||||
|
||||
Shared geometry: 7, 8 and 9 all use a 59.55pt side margin (exactly 10% of the width, about 21mm), giving 476.4pt of content width. That is the natural default for a margin setting.
|
||||
|
||||
Transparency: only 9 uses alpha. Its transparency-group XObjects are drawn at ca 0.5 and 0.53, and the inner /G3 (ca 1) does not cancel that, so flatten to #EEEFF1 and #C1C5CE. All three contain invisible ca=0 rectangles tagged /Image for accessibility; they are not decorations.
|
||||
|
||||
Raster content: 7, 8 and 9 have none (vector shapes and live text only). Selectable-text export is react-pdf's native output. A flattened export means rasterising each rendered page (for example pdf.js to a 300 dpi canvas, then an image-only PDF), which works the same whichever template is chosen.
|
||||
|
||||
Fonts these three need: Poppins 400/600/700, Open Sans 400/700, Montserrat 400/500/700/800. All are SIL OFL. Bundle them as static TTFs in the app assets and in the xbps; react-pdf loads font files by URL, never through fontconfig, and cannot use WOFF2 or variable fonts. BD Script (template 8) is Canva-licensed, so do not ship it; the signature slot becomes an image.
|
||||
|
||||
₹ (U+20B9) coverage:
|
||||
- Montserrat 9.0, installed locally: confirmed with fc-list ':charset=20b9'.
|
||||
- Poppins and Open Sans are not installed here, so check them after downloading.
|
||||
- Fallback: render the ₹ glyph in IBM Plex Sans (already registered) through a nested Text with that fontFamily.
|
||||
|
||||
react-pdf 4.9.0 behaviour, checked in node_modules (layout 5.2.0, textkit 7.0.1, render):
|
||||
1. splitNodes puts fixed children into both the current and the next page fragment. Page-level fixed bands and footers therefore repeat, and a fixed header row inside a table View should repeat on continuation pages. Confirm the latter in one render.
|
||||
2. renderLine places the baseline at line top + ascent, with no half-leading, and any extra lineHeight goes below the line. So Text top = baseline - ascent x size (Poppins 1.05, Open Sans 1.069, Montserrat 0.968).
|
||||
3. The style props maxLines and textOverflow: 'ellipsis' are supported.
|
||||
4. Page sizes: 'A4' = [595.28, 841.89], 'LETTER' = [612, 792].
|
||||
5. Font.registerHyphenationCallback exists; the default inserts hyphens.
|
||||
6. textkit bug in scalePositions: the isLast check (i === positions.length) is never true, so letterSpacing is also added after the last glyph. Right-aligned tracked text shifts left by the letterSpacing, and centred text by half of it. Compensate with a negative marginRight.
|
||||
7. wrap={false}, minPresenceAhead, and widows/orphans (default 2) are available.
|
||||
|
||||
Guarding against the header-overlap bug in every template: build the header as a flex row. The title gets flexShrink 0; the vendor or brand block gets flex 1 with minWidth 0 so it wraps; nothing in the header is absolutely positioned. None of 7/8/9 puts the vendor address in the header (7 has it in a body column, 8 in the footer, 9 in the From block), which is worth keeping.
|
||||
|
||||
Voiced fields missing from all three: due date, PAN, both GSTINs, place of supply, HSN/SAC, discount, the CGST/SGST/IGST split (9 has no tax row at all), amount in words, IFSC/branch/UPI, and a real signature. Build shared sub-components (PartyBlock, TaxSummaryRows, AmountInWords, BankBlock, SignatureBlock, and a ContactFooter using Carbon icon paths) and have each template only theme them.
|
||||
|
||||
How the full set groups (from a quick visual look at all 13 plus format.pdf, to avoid duplicate work across workers):
|
||||
- 7 ≈ 11: ruled grid; accent, font and header mirroring differ.
|
||||
- 9 ≈ 10: same body; header artwork differs.
|
||||
- 4 = format.pdf: visually identical but different bytes, i.e. a re-export.
|
||||
- 5 and 6 are siblings of 4/format.pdf: ISSUED TO plus INVOICE NO/DATE/DUE DATE, a minimal ruled table and a script signature. 6 adds a raster paper texture, a vertical script 'invoice' and a round stamp.
|
||||
- 12 and 13 share the Northline Group content model (DM Sans/Gotham) with different looks.
|
||||
- 1 (letter-spaced serif, raster marble bands), 2 (yellow ruled blank form with an icon list), 3 (monochrome Poppins with rules; the closest to the in-app InvoiceDocument.tsx) and 8 have no near-duplicate in the set.
|
||||
About 9 parametric components would cover all 13.
|
||||
|
||||
**Method.** I ran 10–13 through a stdlib-only PDF content-stream interpreter: CTM 0.24/−0.24 (Canva px to pt), nested form XObjects, ExtGState alpha and /LW. All numbers are page pt with a top-left origin on the 595.5 × 842.25 Canva page (≈A4; the MediaBox y-offset of 7.83 is removed). For text, y is the baseline. Font sizes come from Tf × CTM and are rounded. Letter-spacing is the measured gap between glyphs, which equals Canva's letter-spacing setting. Colours drawn with opacity are also given as flat pre-blended values.
|
||||
|
||||
**Canva quirks.**
|
||||
- Every glyph is positioned individually with no kerning data, so expect width differences of about ±2% under react-pdf's kerning.
|
||||
- 12 and 13 draw every 'effect' string twice (an invisible copy at opacity 0 plus the visible one) and add 30% raster shadow images, which garbles their text extraction. A vector react-pdf re-implementation gives clean selectable text, which suits the copy-pasteable export. A flattened export should rasterise the rendered page rather than reuse Canva effects.
|
||||
|
||||
**Template families in the set:**
|
||||
- 9 ≈ 10: Montserrat band layout.
|
||||
- 7 ≈ 11: grid ledger.
|
||||
- 12 ~ 13: Northline family (same data, different layouts).
|
||||
|
||||
Build one component per family with accent and decor props. None of 10–13 duplicates format.pdf or the current IBM Plex in-app template; 11 is the closest structurally.
|
||||
|
||||
**Fonts.** Per fc-list, none of the template fonts are installed on this machine, so all must be bundled in the app (and therefore in the .xbps and the NSIS installer).
|
||||
- Needed: Montserrat 400/700/800, Inter 400/600/700, Poppins 700, DM Sans 400/700.
|
||||
- Display faces Open Sauce One Bold (12) and Now Bold (13): check the licences or substitute.
|
||||
- Gotham Book/Bold (12, 13) is commercial: substitute Montserrat 400/700 or DM Sans.
|
||||
- Use static TTF instances, not variable fonts or WOFF2.
|
||||
|
||||
**₹ glyph.** react-pdf supports fontFamily arrays as a per-glyph fallback (confirmed in the diegomura/react-pdf docs, font-family-fallback example). Use, for example, fontFamily: ['Montserrat','IBM Plex Sans']. IBM Plex Sans is already bundled and contains U+20B9 (confirmed locally with fc-list :charset=20b9).
|
||||
|
||||
**Fields Voiced needs that these templates lack.** No template has slots for GSTIN/PAN lines, place of supply, HSN/SAC, the CGST+SGST vs IGST split, amount in words, IFSC/branch/UPI, a signature or computer-generated note, or a PO number. 10 has no subtotal or tax row at all, and 10 and 11 have no due date. Each template entry says where to put these.
|
||||
|
||||
**Money columns.** Right-align them everywhere; the sources centre them in 10, 12 and 13. Give amounts at least 100pt at 11–12pt type, and put the rate unit on a second small line.
|
||||
|
||||
**Multi-page.**
|
||||
- Decorations: fixed, absolute, anchored to page edges or corners.
|
||||
- Everything else is flow.
|
||||
- Item rows: wrap={false}.
|
||||
- Totals and payment blocks: wrap={false} plus minPresenceAhead.
|
||||
- The table header repeats on pages after the first via a fixed element whose render function checks pageNumber > 1.
|
||||
|
||||
**Header overlap.** Every new template keeps the vendor address out of the title's row, or gives it a fixed-width wrapping column. Apply the same rule when fixing the current template's header overlap.
|
||||
|
||||
**Page sizes.** True A4 is 595.28 × 841.89 versus Canva's 595.5 × 842.25, a negligible difference. US Letter is 612 × 792 (16.5 wider, 50.25 shorter), so every template needs about 50pt of vertical compression (row pitch, gaps) or extra pages.
|
||||
|
||||
**To check.** Confirm how react-pdf positions absolute children relative to Page padding. The existing footer assumes offsets from the page edge; corner decorations with configurable margins depend on the same behaviour.
|
||||
Reference in New Issue
Block a user