Pular para o conteúdo principal
Versão: 1.x

Modelos para Impressoras Térmicas

Os modelos térmicos utilizam um formato XML que gera tanto uma pré-visualização na tela quanto comandos ESC/POS para impressora a partir do mesmo modelo. Eles utilizam os mesmos placeholders {{variable}} dos modelos HTML para vinculação de dados.

Escolha este mecanismo se você possui uma impressora de recibos conectada pela Configuração de Impressora.

Elementos XML​

Elemento Raiz​

Todo modelo térmico começa com um <receipt> raiz:

<receipt paper-width="48">
<!-- 48 characters = 80mm paper -->
<!-- 32 characters = 58mm paper -->
</receipt>

Texto e Formatação​

<text>Plain text</text>
<bold>Bold text</bold>
<underline>Underlined text</underline>
<invert>Inverted (white on black)</invert>

Alinhamento​

<align mode="left">Left aligned</align>
<align mode="center">Centered</align>
<align mode="right">Right aligned</align>

Tamanho do Texto​

Dimensione a largura e a altura do texto de forma independente:

<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>

Layout Tabular​

Use <row> e <col> para colunas alinhadas:

<row>
<col width="24">Item name</col>
<col width="8" align="right">Qty</col>
<col width="16" align="right">Price</col>
</row>

As larguras das colunas são em caracteres. Use width="*" para uma coluna flexível que absorve o espaço restante — isso faz com que os modelos funcionem em diferentes larguras de papel sem modificação:

<row>
<col width="*">{{name}}</col>
<col width="12" align="right">{{line_total_display}}</col>
</row>

Separadores e Espaçamento​

<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/> (ou style="single") e style="double" imprimem a linha nativa da impressora. dashed e dotted imprimem separadores baseados em caracteres na largura ativa da coluna — útil quando você deseja um separador visível que resista a uma captura de tela da pré-visualização térmica.

Comandos da impressora​

<cut /> <!-- Full paper cut -->
<cut mode="partial" /> <!-- Partial cut (leaves a tab) -->
<drawer /> <!-- Open cash drawer -->

Códigos de barras e QR Codes​

<barcode type="code128" height="40">{{order.number}}</barcode>
<barcode type="qrcode" scale="3">{{fiscal.qr_payload}}</barcode>

Exemplo: recibo simples de 80mm​

<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>

Colunas com largura asterisco​

O recurso width="*" torna os modelos independentes da largura do papel. Em vez de definir larguras de coluna fixas para um tamanho de papel específico, use * para a coluna que deve se expandir:

<!-- 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>

Em uma impressora de 80mm (48 caracteres), o nome do item ocupa 38 caracteres. Em uma impressora de 58mm (32 caracteres), ocupa 22 caracteres. As colunas fixas mantêm o mesmo tamanho em ambas.

Pré-visualização do modelo​

O editor de modelos exibe uma pré-visualização térmica em tempo real durante a edição. A pré-visualização renderiza o XML como HTML monoespaçado estilizado, simulando a aparência do recibo no papel. As alterações são atualizadas após um breve atraso (debounce de 300ms).

O mesmo modelo gera tanto a pré-visualização quanto a saída ESC/POS para a impressora — não há um modelo de "impressão" separado. Códigos de barras e QR Codes são renderizados como SVGs inline na pré-visualização e como comandos ESC/POS nativos (ou imagens rasterizadas) na impressora.

Dicas e armadilhas na criação de modelos​

A impressão térmica possui algumas armadilhas que não existem em HTML. Estas são as mais comuns encontradas pelos autores.

Envolva cabeçalhos estilizados em <text> para quebra de linha​

Contêineres estilizados — <bold>, <size>, <underline>, <align> — não produzem uma quebra de linha por si só. Apenas <text> e elementos de bloco (<line/>, <row>, <feed>) produzem.

<!-- ❌ 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>

Os modelos de galeria de 80mm inclusos utilizam esse padrão para todos os cabeçalhos.

Evite <size width="2"> em linhas estreitas​

Texto com largura dupla dobra a contagem efetiva de caracteres. Um título que cabe em uma impressora de 80mm pode estourar em uma impressora genérica de 80mm com 42 colunas, e em papel de 58mm restam apenas 16 caracteres.

Para valores em destaque (totais grandes, números de pedido na cozinha), emita uma linha escalada independente em vez de envolvê-la dentro de uma estrutura multicoluna <row>:

<!-- 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>

Use width="*" para layouts independentes da largura do papel​

A coluna width="*" (asterisco) absorve a largura restante após as colunas de largura fixa. O mesmo modelo é renderizado corretamente em impressoras de 32 colunas (58mm), 42 colunas (80mm padrão) e 48 colunas (80mm largo) sem modificação:

<!-- Works on 58mm AND 80mm without changes -->
<row>
<col width="*">{{name}}</col>
<col width="10" align="right">{{line_total_display}}</col>
</row>

Se você usar larguras numéricas fixas, planeje-as para 42, não 48. Linhas que somam 48 serão quebradas nas impressoras comuns de 80mm com 42 colunas.

Cabeçalhos estilizados centralizados dentro de blocos <align>​

Dentro de <align mode="center"> (ou right), um cabeçalho estilizado direto — <bold>, <size>, <underline>, <invert> colocado diretamente dentro <align> — seguido por outra linha é automaticamente fechado em sua própria linha. Um nome de loja centralizado e redimensionado acima de uma linha de endereço centralizada é impresso corretamente mesmo sem o <text> fechamento explícito.

Fora de um bloco de alinhamento, mantenha o <text> fechamento explícito.

Normalização de pontuação ESC/POS​

Quando o idioma da impressora é ESC/POS, o codificador normaliza a pontuação tipográfica para ASCII seguro antes da escrita:

  • Travessão curto, travessão longo, travessão numérico, sinal de menos Unicode → -
  • Aspas curvas → aspas retas
  • Espaço não separável → espaço regular

Portanto, Mon–Sat 9:00–18:00 e "open" são impressos corretamente mesmo em impressoras sem fonte Unicode. Impressoras Star (star-line / star-prnt) preservam a tipografia original, então utilize caracteres compatíveis com a fonte da impressora.

Scripts não latinos e da direita para a esquerda​

Impressoras térmicas imprimem texto usando uma fonte e code page integrados, então árabe, hebraico, persa, urdu e outros scripts não latinos só são impressos corretamente se a impressora estiver configurada com um code page correspondente (ex.: CP864 / Windows-1256 para árabe) — caso contrário, aparecem espaços em branco ou caracteres ilegíveis.

A abordagem confiável para esses scripts é Recibo completo em raster, que renderiza o recibo inteiro como imagem para que seja impresso exatamente como projetado, independentemente das fontes integradas da impressora. Consulte Configuração de Impressora para ativar o modo raster.

Hardware térmico requer um template térmico

Uma impressora térmica não consegue imprimir um template HTML de página inteira — o trabalho precisa ser renderizado em comandos ESC/POS ou Star, que o HTML não consegue expressar. Use um template térmico para hardware térmico; para HTML em página inteira A4/Carta, imprima em uma impressora de sistema/PDF ou via PrintNode.

Logotipos e imagens​

Use <image> para inserir um logotipo:

<align mode="center">
<image src="data:image/png;base64,..." />
</align>

O WCPOS rasteriza imagens no cliente — decodificando, achatando transparência para branco, redimensionando conforme o limite de pontos da impressora, convertendo para monocromático (dithering Atkinson para logotipos, limiar para códigos de barras) — e então enviando comandos de imagem ESC/POS ou Star.

  • Limites de pontos: 384 pontos de largura para 58mm, 576 pontos de largura para 80mm.
  • Fontes aceitas: URLs de dados data:image/png e data:image/jpeg, URLs absolutas http(s), e caminhos relativos à raiz de mesma origem. URLs relativas ao protocolo //, barras invertidas e travessia .. com codificação percentual são rejeitados.
  • Formato recomendado: PNG de alto contraste com fundo transparente ou branco. JPEG funciona, mas artefatos de compressão podem ser impressos como ruído.
  • SVG ainda não é suportado para saída térmica raw.

Dicas​

  • Comece a partir de um modelo da galeria — os modelos térmicos na galeria utilizam todos os padrões acima e são validados com impressoras reais.
  • Teste ambos os tamanhos de papel se suas lojas utilizam impressoras diferentes, ou use apenas colunas com width="*".
  • Use os campos _display para valores monetários — eles já consideram a localização.
  • Mantenha a simplicidade — o formato térmico possui menos ferramentas de formatação que o HTML por design. Apoie-se em <row> + <col width="*"> e deixe a impressora fazer o trabalho.