Sjablonen voor thermische printers
Thermische sjablonen gebruiken een XML-formaat dat zowel een schermvoorbeeld als ESC/POS-printercommando's produceert vanuit hetzelfde sjabloon. Ze gebruiken dezelfde {{variable}}-placeholders als HTML-sjablonen voor databinding.
Kies deze engine als je een bonprinter hebt aangesloten via de Printerinstallatie.
XML-elementen
Root-element
Elk thermisch sjabloon begint met een <receipt>-root:
<receipt paper-width="48">
<!-- 48 characters = 80mm paper -->
<!-- 32 characters = 58mm paper -->
</receipt>
Tekst en opmaak
<text>Plain text</text>
<bold>Bold text</bold>
<underline>Underlined text</underline>
<invert>Inverted (white on black)</invert>
Uitlijning
<align mode="left">Left aligned</align>
<align mode="center">Centered</align>
<align mode="right">Right aligned</align>
Tekstgrootte
Schaal de breedte en hoogte van tekst onafhankelijk:
<size width="2" height="2">Double-size text</size>
<size width="2" height="1">Wide text</size>
<size width="1" height="2">Tall text</size>
Tabellay-out
Gebruik <row> en <col> voor uitgelijnde kolommen:
<row>
<col width="24">Item name</col>
<col width="8" align="right">Qty</col>
<col width="16" align="right">Price</col>
</row>
Kolombreedtes zijn in tekens. Gebruik width="*" voor een flexkolom die de resterende ruimte opneemt — hierdoor werken sjablonen op verschillende papierbreedtes zonder aanpassing:
<row>
<col width="*">{{name}}</col>
<col width="12" align="right">{{line_total_display}}</col>
</row>
Scheidingstekens en spatiëring
<line /> <!-- Default single rule -->
<line style="double" /> <!-- Printer-native double rule -->
<line style="dashed" /> <!-- Character dashes across the width -->
<line style="dotted" /> <!-- Character dots across the width -->
<feed lines="2" /> <!-- Blank lines -->
<line/> (of style="single") en style="double" drukken de native lijn van de printer af. dashed en dotted drukken op tekens gebaseerde scheidingstekens over de actieve kolombreedte af — handig wanneer je een zichtbaar scheidingsteken wilt dat een thermisch voorbeeld-screenshot overleeft.
Printercommando's
<cut /> <!-- Full paper cut -->
<cut mode="partial" /> <!-- Partial cut (leaves a tab) -->
<drawer /> <!-- Open cash drawer -->
Barcodes en QR-codes
<barcode type="code128" height="40">{{order.number}}</barcode>
<barcode type="qrcode" scale="3">{{fiscal.qr_payload}}</barcode>
Voorbeeld: eenvoudige 80mm-bon
<receipt paper-width="48">
<align mode="center">
<size width="2" height="2">{{store.name}}</size>
</align>
<feed lines="1" />
<align mode="center">
<text>{{store.address_1}}</text>
<text>{{store.city}} {{store.state}} {{store.postcode}}</text>
{{#store.phone}}<text>{{store.phone}}</text>{{/store.phone}}
</align>
<line />
<text>Order: #{{order.number}}</text>
<text>Date: {{order.created.datetime}}</text>
{{#cashier.name}}<text>Cashier: {{cashier.name}}</text>{{/cashier.name}}
<line />
<!-- Column headers -->
<row>
<col width="*"><bold>Item</bold></col>
<col width="4" align="right"><bold>Qty</bold></col>
<col width="12" align="right"><bold>Total</bold></col>
</row>
<line />
<!-- Line items -->
{{#lines}}
<row>
<col width="*">{{name}}</col>
<col width="4" align="right">{{qty}}</col>
<col width="12" align="right">{{line_total_display}}</col>
</row>
{{/lines}}
<line />
<!-- Totals -->
<row>
<col width="*">Subtotal</col>
<col width="16" align="right">{{totals.subtotal_display}}</col>
</row>
{{#totals.tax_total}}
<row>
<col width="*">Tax</col>
<col width="16" align="right">{{totals.tax_total_display}}</col>
</row>
{{/totals.tax_total}}
<row>
<col width="*"><bold>TOTAL</bold></col>
<col width="16" align="right"><bold>{{totals.total_display}}</bold></col>
</row>
<line />
{{#payments}}
<row>
<col width="*">{{method_title}}</col>
<col width="16" align="right">{{amount_display}}</col>
</row>
{{#tendered}}
<row>
<col width="*">Tendered</col>
<col width="16" align="right">{{tendered_display}}</col>
</row>
<row>
<col width="*">Change</col>
<col width="16" align="right">{{change_display}}</col>
</row>
{{/tendered}}
{{/payments}}
<feed lines="2" />
<align mode="center">
<text>Thank you for your purchase!</text>
</align>
<feed lines="3" />
<cut />
</receipt>
Star-breedte-kolommen
De width="*"-functie maakt sjablonen papierbreedte-onafhankelijk. In plaats van kolombreedtes hard te coderen voor een specifiek papierformaat, gebruik je * voor de kolom die zich moet uitrekken:
<!-- Works on 58mm (32 char) AND 80mm (48 char) printers -->
<row>
<col width="*">{{name}}</col>
<col width="10" align="right">{{line_total_display}}</col>
</row>
Op een 80mm-printer (48 tekens) krijgt de productnaam 38 tekens. Op een 58mm-printer (32 tekens) krijgt deze 22 tekens. Vaste kolommen blijven op beide even groot.
Sjabloonvoorbeeld
De sjablooneditor toont een live thermisch voorbeeld terwijl je bewerkt. Het voorbeeld rendert je XML als gestileerde monospace-HTML en simuleert hoe de bon op papier zal lijken. Wijzigingen worden na een korte vertraging bijgewerkt (debounced op 300ms).
Hetzelfde sjabloon produceert zowel het voorbeeld als de ESC/POS-printeruitvoer — geen apart "print"-sjabloon. Barcodes en QR-codes worden in het voorbeeld gerenderd als inline-SVG's en op de printer als native ESC/POS-commando's (of rasterafbeeldingen).
Tips en valkuilen bij het maken
Thermisch afdrukken heeft een paar valkuilen die niet bestaan in HTML. Dit zijn degene waar auteurs het vaakst tegenaan lopen.
Verpak gestileerde kopteksten in <text> voor een regeleinde
Gestileerde containers — <bold>, <size>, <underline>, <align> — produceren zelf geen regeleinde. Alleen <text> en blokelementen (<line/>, <row>, <feed>) doen dat.
<!-- ❌ Bug: the heading runs together with whatever follows -->
<bold>{{i18n.bill_to}}</bold>
{{customer.name}}
<!-- ✅ Fix: wrap the inner content in <text> -->
<bold><text>{{i18n.bill_to}}</text></bold>
<text>{{customer.name}}</text>
De meegeleverde 80mm-galerijsjablonen gebruiken dit patroon voor elke koptekst.
Vermijd <size width="2"> binnen smalle rijen
Tekst met dubbele breedte verdubbelt het effectieve aantal tekens. Een kop die op een 80mm-printer past, kan overlopen op een generieke 80mm-printer met 42 kolommen, en op 58mm-papier laat het slechts 16 tekens over.
Voor prominente waarden (grote totalen, keukenbestelnummers) maak je in plaats daarvan een losstaande geschaalde regel in plaats van deze binnen een <row> met meerdere kolommen te verpakken:
<!-- For store names, prefer normal-width, double-height -->
<bold><size height="2"><text>{{store.name}}</text></size></bold>
<!-- For a bold total, put it on its own line above the row -->
<align mode="right">
<bold><size height="2"><text>{{totals.total_display}}</text></size></bold>
</align>
Gebruik width="*" voor papierbreedte-onafhankelijke lay-outs
De width="*"-kolom (star) neemt de resterende breedte op na de kolommen met vaste breedte. Hetzelfde sjabloon rendert dan correct op printers met 32 kolommen (58mm), 42 kolommen (80mm standaard) en 48 kolommen (80mm breed) zonder aanpassing:
<!-- Works on 58mm AND 80mm without changes -->
<row>
<col width="*">{{name}}</col>
<col width="10" align="right">{{line_total_display}}</col>
</row>
Als je toch hardgecodeerde numerieke breedtes gebruikt, begroot ze dan op 42, niet op 48. Rijen die optellen tot 48 zullen aflopen op de gangbare 80mm-printers met 42 kolommen.
Gecentreerde gestileerde kopteksten binnen <align>-blokken
Binnen <align mode="center"> (of right) wordt een directe gestileerde koptekst — <bold>, <size>, <underline>, <invert> die rechtstreeks binnen <align> is geplaatst — gevolgd door een andere regel, automatisch op een eigen regel afgesloten. Een gecentreerde geschaalde winkelnaam boven een gecentreerde adresregel wordt netjes afgedrukt, zelfs zonder de expliciete <text>-wrap.
Buiten een uitlijningsblok houd je de <text>-wrap aan.
ESC/POS-interpunctienormalisatie
Wanneer de printertaal ESC/POS is, normaliseert de encoder typografische interpunctie naar veilige ASCII voordat er wordt geschreven:
- En-streepje, em-streepje, cijferstreepje, Unicode-minteken →
- - Krulletjes-aanhalingstekens → rechte aanhalingstekens
- Vaste spatie → gewone spatie
Zo worden Mon–Sat 9:00–18:00 en "open" correct afgedrukt, zelfs op printers zonder een Unicode-lettertype. Star-printers (star-line / star-prnt) behouden de oorspronkelijke typografie, dus auteur met tekens die het lettertype van de printer ondersteunt.
Niet-Latijnse en rechts-naar-links-scripts
Thermische printers drukken tekst af met een ingebouwd lettertype en een codepagina, dus Arabisch, Hebreeuws, Perzisch, Urdu en andere niet-Latijnse scripts worden alleen correct afgedrukt als de printer is ingesteld op een bijbehorende codepagina (bijv. CP864 / Windows-1256 voor Arabisch) — anders krijg je lege plekken of onleesbare tekens.
De betrouwbare aanpak voor deze scripts is Full receipt raster, waarbij de hele bon als afbeelding wordt gerenderd zodat deze precies wordt afgedrukt zoals ontworpen, ongeacht de ingebouwde lettertypen van de printer. Zie Printerinstallatie voor het inschakelen van de rastermodus.
Een thermische printer kan geen HTML-sjabloon op volledige pagina afdrukken — de taak moet worden gerenderd naar ESC/POS- of Star-commando's, die HTML niet kan uitdrukken. Gebruik een thermisch sjabloon voor thermische hardware; voor HTML op volledige A4/Letter-pagina druk je af naar een systeem-/PDF-printer of via PrintNode.
Logo's en afbeeldingen
Gebruik <image> om een logo in te sluiten:
<align mode="center">
<image src="data:image/png;base64,..." />
</align>
WCPOS rasteriseert afbeeldingen op de client — decoderen, transparantie afvlakken naar wit, formaat aanpassen aan het puntbudget van de printer, converteren naar monochroom (Atkinson-dithering voor logo's, drempel voor barcodes) — en stuurt vervolgens ESC/POS- of Star-afbeeldingscommando's.
- Puntbudgetten: 384 punten breed voor 58mm, 576 punten breed voor 80mm.
- Geaccepteerde bronnen:
data:image/png- endata:image/jpeg-data-URL's, absolutehttp(s)-URL's en root-relatieve paden van dezelfde oorsprong. Protocolrelatieve//, backslashes en percent-gecodeerde..-traversal worden geweigerd. - Aanbevolen formaat: PNG met hoog contrast met een transparante of witte achtergrond. JPEG werkt, maar compressie-artefacten kunnen als ruis worden afgedrukt.
- SVG wordt nog niet ondersteund voor onbewerkte thermische uitvoer.
Tips
- Begin vanuit een galerijsjabloon — de thermische sjablonen in de galerij gebruiken alle bovenstaande patronen en zijn gevalideerd tegen echte printers.
- Test beide papierformaten als je winkels verschillende printers gebruiken, of houd het bij
width="*"-kolommen. - Gebruik
_display-velden voor valuta — die zijn al locale-bewust. - Houd het eenvoudig — thermisch heeft van nature minder opmaakhulpmiddelen dan HTML. Leun op
<row>+<col width="*">en laat de printer zijn werk doen.