Skip to content

Tech Debt: PDF generation refactor — extract layout components #3724

@TaprootFreak

Description

@TaprootFreak

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

  • support-pdf.service.ts no longer contains direct pdf.text/fontSize/font/moveTo/... calls; all primitive drawing lives in reusable components under src/shared/utils/pdf/ (or support/pdf/)
  • No manual y accumulator inside service methods; page breaks handled by helper
  • Existing endpoints (/support/:id/ip-log-pdf, /support/:id/transaction-pdf, POST /support/:id/onboarding-pdf) produce visually equivalent PDFs (byte diff acceptable, layout snapshot equal)
  • At least one regression-style test per PDF type that renders the document and asserts on extracted text content

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)

  • balance-pdf rendered via @react-pdf/renderer behind feature flag, parity with current output
  • Decision recorded: adopt fleet-wide, keep dual stack, or roll back

Recommendation

Start with Option 1 only. Reassess Option 2 when the next non-trivial PDF report appears on the roadmap.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions