Context
PDF generation across the API uses pdfkit directly through hand-rolled coordinate math. Analysis showed the largest service (support-pdf.service.ts) carries the most pain; the rest is acceptable.
| Service |
Lines |
pdfkit primitive calls |
support-pdf.service.ts |
524 |
73 |
swiss-qr.service.ts (uses swissqrbill) |
896 |
19 |
ocp-sticker.service.ts |
413 |
18 |
balance-pdf.service.ts |
208 |
6 |
custody-pdf.service.ts |
170 |
6 |
realunit.service.ts (delegates) |
1366 |
0 |
Shared utility: src/shared/utils/pdf.util.ts already centralizes drawLogo / drawTable / drawFooter / translate / formatting / generateGiroCode. The pattern works — it just isn't applied consistently inside support-pdf.
Pain points
- Manual
x / y coordinates and mm2pt calls
- Page breaks handled by hand:
if (y > pdf.page.height - 100) pdf.addPage()
- Layout and business logic interleaved
- 73 primitive calls in
support-pdf.service.ts make changes risky and reviews hard
Out of scope
swiss-qr.service.ts / swissqrbill — Swiss QR-Bill is regulatorily validated, do not touch
- Typst migration — evaluated and rejected: no Swiss QR-Bill integration available, requires external compiler or WASM, would introduce two parallel render pipelines
Option 1 — Layout components inside pdfkit (recommended)
Decompose support-pdf.service.ts into small composable classes that take a PDFDocument + data and render themselves:
Header, Section, KeyValueList, LogTable, Footer
- Shared
flow() helper for automatic page breaks (kill the manual y > page.height - 100 checks)
- Reuse
PdfUtil.drawLogo / drawTable / drawFooter consistently
Stays in-process, no new dependencies, no regression risk on swissqrbill.
Acceptance criteria
Option 2 — @react-pdf/renderer PoC (only if more reports are planned)
Introduce JSX/TSX templates for one report (balance-pdf) as a PoC. Friendly to designers, decouples layout from data, keeps pdfkit for swissqrbill.
Risks:
- Two render engines in the codebase
- Only worth doing if a steady pipeline of new reports is planned
Acceptance criteria (if pursued)
Recommendation
Start with Option 1 only. Reassess Option 2 when the next non-trivial PDF report appears on the roadmap.
References
Context
PDF generation across the API uses
pdfkitdirectly through hand-rolled coordinate math. Analysis showed the largest service (support-pdf.service.ts) carries the most pain; the rest is acceptable.support-pdf.service.tsswiss-qr.service.ts(usesswissqrbill)ocp-sticker.service.tsbalance-pdf.service.tscustody-pdf.service.tsrealunit.service.ts(delegates)Shared utility:
src/shared/utils/pdf.util.tsalready centralizesdrawLogo/drawTable/drawFooter/translate/ formatting /generateGiroCode. The pattern works — it just isn't applied consistently insidesupport-pdf.Pain points
x/ycoordinates andmm2ptcallsif (y > pdf.page.height - 100) pdf.addPage()support-pdf.service.tsmake changes risky and reviews hardOut of scope
swiss-qr.service.ts/swissqrbill— Swiss QR-Bill is regulatorily validated, do not touchOption 1 — Layout components inside pdfkit (recommended)
Decompose
support-pdf.service.tsinto small composable classes that take aPDFDocument+ data and render themselves:Header,Section,KeyValueList,LogTable,Footerflow()helper for automatic page breaks (kill the manualy > page.height - 100checks)PdfUtil.drawLogo/drawTable/drawFooterconsistentlyStays in-process, no new dependencies, no regression risk on
swissqrbill.Acceptance criteria
support-pdf.service.tsno longer contains directpdf.text/fontSize/font/moveTo/...calls; all primitive drawing lives in reusable components undersrc/shared/utils/pdf/(orsupport/pdf/)yaccumulator inside service methods; page breaks handled by helper/support/:id/ip-log-pdf,/support/:id/transaction-pdf,POST /support/:id/onboarding-pdf) produce visually equivalent PDFs (byte diff acceptable, layout snapshot equal)Option 2 —
@react-pdf/rendererPoC (only if more reports are planned)Introduce JSX/TSX templates for one report (
balance-pdf) as a PoC. Friendly to designers, decouples layout from data, keepspdfkitforswissqrbill.Risks:
Acceptance criteria (if pursued)
balance-pdfrendered via@react-pdf/rendererbehind feature flag, parity with current outputRecommendation
Start with Option 1 only. Reassess Option 2 when the next non-trivial PDF report appears on the roadmap.
References
src/shared/utils/pdf.util.tssrc/subdomains/generic/support/support-pdf.service.ts