Skip to content

Ticket & receipt printing

This document explains how printing works on the frontend, why thermal printing (58/80 mm) is problematic, and what the solutions are.

The core of printing lives in src/lib/print-pdf.ts. All functions rely on the same technique: load the document into a hidden <iframe>, then call print() on that iframe’s window.

window.print() prints the current HTML page (the app), not an arbitrary file. To print a PDF/HTML/ticket, we load it into a dedicated iframe and print its window.

Function Input Use
imprimerPdfBlob(blob) PDF (blob) PDF receipts/invoices, contracts, payslips, reports, exports
imprimerPdfOctets(octets) PDF (bytes) Audit export
imprimerImageBlob(blob) Image (blob) Barcode labels
imprimerHtml(html, 58|80) HTML Thermal receipt
  1. The backend returns HTML (not a PDF) via GET /api/v1/facturation/factures/{id}/ticket?largeur=58|80.
  2. printFactureTicket() (src/core/api/facturation.ts) fetches that HTML then calls imprimerHtml().
  3. imprimerHtml():
    • extracts the <style> and <body> from the HTML,
    • builds a complete HTML document constrained to largeurMm wide,
    • loads it into a hidden iframe (src = blob URL),
    • waits for load + fonts, measures the real content height,
    • injects @page { size: <width>mm <height>mm; margin: 0 },
    • calls contentWindow.print().
  4. The iframe is cleaned up when focus returns to the main window (print dialog closed) or via a 60 s safety net.

Symptom: the print dialog proposes US Letter (215.9 × 279.4 mm) instead of 58 or 80 mm.

The browser cannot force a physical printer’s paper size. This is a Web limitation, not a code bug.

  • The CSS @page { size } rule only works for virtual PDF printers (“Save as PDF”, “Microsoft Print to PDF”). With a real thermal printer, Chrome uses the paper size configured in the printer driver, not the CSS one.
  • Chrome also has a known behavior: it ignores the default paper size set in the driver and falls back to US Letter. That is exactly the observed symptom.
  • “Save as PDF” → the PDF comes out correctly at 58/80 mm.
  • The layout width of the content (the HTML is constrained to 58/80 mm).

It does not fix printing on a physical thermal printer.

A. Configure the driver (low effort, one-time)

Section titled “A. Configure the driver (low effort, one-time)”

Set the 58/80 mm size directly in Windows, then pick it in the Chrome print dialog.

In Windows: Settings → Bluetooth & devices → Printers & scanners → [printer] → Printing preferences → Paper size = 80 mm (or 58 mm).

In the Chrome print dialog: More settings →

  • Destination = the thermal printer,
  • Paper size = 80 mm (roll) / 80 mm continuous / 58 mm,
  • Scale = 100 %,
  • Margins = None,
  • Headers and footers = disabled.

These settings can be saved as a preset for cashiers.

B. Raw printing via a local bridge (robust, POS) — ✅ implemented

Section titled “B. Raw printing via a local bridge (robust, POS) — ✅ implemented”

Bypasses the browser print dialog entirely by talking directly to the system print service through a local agent installed on the checkout machine: QZ Tray (https://qz.io — the most common for web POS).

Implementation (src/lib/qz-tray-client.ts):

  • printFactureTicket() (src/core/api/facturation.ts) first checks whether a QZ Tray printer is configured on this workstation (src/lib/imprimante-thermique-store.ts, localStorage — a machine-local hardware setting, never sent to the backend). If yes, it prints directly via imprimerTicketQZ(): the same ticket HTML is sent to QZ in pixel/html mode, with width/height enforced at the QZ config level (thus the driver, via Java) — independent of the Chrome bug documented in section 2.
  • If QZ Tray is not configured, or the call fails (agent closed, printer unplugged…), it silently falls back to imprimerHtml() (method A) — never a hard failure for the cashier.
  • Workstation setup: print-button menu → “Paramètres d’impression…” (parametres-impression-dialog.tsx) — detects the agent, lists the system printers it exposes, lets you choose + test.
  • Security: unsigned mode (qz.security.setCertificatePromise/ setSignaturePromise resolve empty). QZ Tray shows a one-time local confirmation per workstation (“Allow this site?”, “Remember” checkbox), no certificate to manage — sufficient for an internal workstation fleet with an operator; signing would only matter for a kiosk deployment with no manual confirmation.

Workstation prerequisite: install the QZ Tray agent (https://qz.io/download), then in “Paramètres d’impression…” pick the system printer matching the 58/80mm ticket.

The backend produces a PDF at 58/80 mm directly (like the current PDF receipt). The PDF carries the correct page size, but printing on a physical printer remains subject to the driver (see section 2): the Chrome dialog can still propose US Letter as long as the driver is not configured.

Approach Size control Depends on driver? Effort
@page { size } (method A) PDF/preview only Yes (physical) Already in place
Windows driver config Yes, via Windows Yes (but set) Low
QZ Tray (method B) Total (enforced width) No ✅ Implemented
Backend-side PDF Correct PDF size Yes (physical) Medium

QZ Tray (B) is in place and used automatically as soon as a printer is configured in “Paramètres d’impression…”. Without an agent installed on the workstation, the app falls back to method A (driver config as a troubleshooting path).