# 热敏打印机模板

热敏模板使用 XML 格式，可从同一模板生成屏幕预览和 ESC/POS 打印机命令。它们使用与 [HTML 模板](/zh-CN/receipts/html-templates.md)相同的 `{{variable}}` 占位符进行数据绑定。

如果收据打印机已通过[打印机设置](/zh-CN/hardware/printers.md)连接，请选择此引擎。

## XML 元素[​](#xml-elements "直接链接到 XML 元素")

### 根元素[​](#root-element "直接链接到 根元素")

每个热敏模板都以一个 `<receipt>` 根元素开头：

```
<receipt paper-width="48">

  <!-- 48 characters = 80mm paper -->

  <!-- 32 characters = 58mm paper -->

</receipt>
```

### 文本和格式[​](#text-and-formatting "直接链接到 文本和格式")

```
<text>Plain text</text>

<bold>Bold text</bold>

<underline>Underlined text</underline>

<invert>Inverted (white on black)</invert>
```

### 对齐方式[​](#alignment "直接链接到 对齐方式")

```
<align mode="left">Left aligned</align>

<align mode="center">Centered</align>

<align mode="right">Right aligned</align>
```

### 文本大小[​](#text-size "直接链接到 文本大小")

分别缩放文本宽度和高度：

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

### 表格布局[​](#tabular-layout "直接链接到 表格布局")

使用 `<row>` 和 `<col>` 用于对齐列：

```
<row>

  <col width="24">Item name</col>

  <col width="8" align="right">Qty</col>

  <col width="16" align="right">Price</col>

</row>
```

列宽以字符为单位。使用 `width="*"` 设置一个弹性列来吸收剩余空间，这样模板无需修改即可适配不同纸张宽度：

```
<row>

  <col width="*">{{name}}</col>

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

</row>
```

### 分隔符和间距[​](#separators-and-spacing "直接链接到 分隔符和间距")

```
<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/>` (或 `style="single"`) 和 `style="double"` 打印打印机的原生分隔线。 `dashed` 和 `dotted` 在当前列宽内打印基于字符的分隔符，用于在热敏预览截图中保留可见分隔线。

### 打印机命令[​](#printer-commands "直接链接到 打印机命令")

```
<cut />              <!-- Full paper cut -->

<cut mode="partial" /> <!-- Partial cut (leaves a tab) -->

<drawer />           <!-- Open cash drawer -->
```

### 条形码和二维码[​](#barcodes-and-qr-codes "直接链接到 条形码和二维码")

```
<barcode type="code128" height="40">{{order.number}}</barcode>

<barcode type="qrcode" scale="3">{{fiscal.qr_payload}}</barcode>
```

## 示例：简单的 80mm 收据[​](#example-simple-80mm-receipt "直接链接到 示例：简单的 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>
```

## 星号宽度列[​](#star-width-columns "直接链接到 星号宽度列")

`width="*"` 功能使模板不依赖纸张宽度。无需为特定纸张尺寸硬编码列宽，而是对需要拉伸的列使用 `*`：

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

在 80mm 打印机（48 个字符）上，商品名称可获得 38 个字符。在 58mm 打印机（32 个字符）上，则可获得 22 个字符。固定列在两者上保持相同大小。

## 模板预览[​](#template-preview "直接链接到 模板预览")

模板编辑器会在编辑时显示**实时热敏预览**。预览会将 XML 渲染为带样式的等宽 HTML，模拟收据打印在纸上的效果。更改会在短暂延迟后更新（300ms 防抖）。

同一个模板会同时生成预览和 ESC/POS 打印机输出，没有单独的“打印”模板。条形码和二维码在预览中渲染为内联 SVG，在打印机上则渲染为原生 ESC/POS 命令（或光栅图像）。

## 编写提示和注意事项[​](#authoring-tips-and-pitfalls "直接链接到 编写提示和注意事项")

热敏打印有一些 HTML 中不存在的陷阱。以下是作者最常遇到的问题。

### 将带样式的标题包在 `<text>` 中以换行[​](#wrap-styled-headings-in-text-for-a-line-break "直接链接到 wrap-styled-headings-in-text-for-a-line-break")

带样式的容器 — `<bold>`, `<size>`, `<underline>`, `<align>` — **本身不会输出换行**。只有 `<text>` 和块级元素（`<line/>`, `<row>`, `<feed>`）会。

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

随附的 80mm 图库模板会对每个标题使用这种模式。

### 避免在窄行内使用 `<size width="2">`[​](#avoid-size-width2-inside-narrow-rows "直接链接到 avoid-size-width2-inside-narrow-rows")

双倍宽度文本会**使有效字符数翻倍**。在 80mm 打印机上能放下的标题，在 42 列通用 80mm 打印机上可能会溢出，而在 58mm 纸张上只会留下 16 个字符。

对于醒目的值（大额合计、厨房订单号），请输出单独的缩放行，而不是将其包在多列中 `<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>
```

### 使用 `width="*"` 创建与纸宽无关的布局[​](#use-width-for-paper-width-agnostic-layouts "直接链接到 use-width-for-paper-width-agnostic-layouts")

`width="*"`（星号）列会吸收固定宽度列之外的剩余宽度。因此，同一个模板无需修改即可在 32 列（58mm）、42 列（80mm 标准）和 48 列（80mm 宽幅）打印机上正确渲染：

```
<!-- Works on 58mm AND 80mm without changes -->

<row>

  <col width="*">{{name}}</col>

  <col width="10" align="right">{{line_total_display}}</col>

</row>
```

如果确实使用硬编码的数字宽度，**请按 42 进行分配**，而不是 48。总和为 48 的行会在常见的 42 列 80mm 打印机上换行。

### `<align>` 块内居中的带样式标题[​](#centred-styled-headings-inside-align-blocks "直接链接到 centred-styled-headings-inside-align-blocks")

在 `<align mode="center">` (或 `right`), 直接使用带样式的标题 — `<bold>`, `<size>`, `<underline>`, `<invert>` 直接放在其中 `<align>` — 后面跟着另一行时，会自动闭合到其独立的一行。居中缩放的店铺名称位于居中的地址行上方，即使没有显式的 `<text>` 换行。

在对齐块之外，保留 `<text>` 换行。

### ESC/POS 标点规范化[​](#escpos-punctuation-normalisation "直接链接到 ESC/POS 标点规范化")

当打印机语言为 ESC/POS 时，编码器会在写入前将排版标点规范化为安全的 ASCII：

* 短破折号、长破折号、数字破折号、Unicode 减号 → `-`
* 弯引号 → 直引号
* 不换行空格 → 普通空格

因此，`Mon–Sat 9:00–18:00` 和 `"open"` 即使在没有 Unicode 字体的打印机上也能正确打印。Star 打印机（`star-line` / `star-prnt`）**保留**原始排版，因此编写内容时应使用打印机字体支持的字符。

### 非拉丁和从右到左书写的文字[​](#non-latin-and-rtl-scripts "直接链接到 非拉丁和从右到左书写的文字")

热敏打印机使用内置字体和代码页打印文本，因此阿拉伯语、希伯来语、波斯语、乌尔都语以及其他非拉丁文字只有在打印机设置为匹配的代码页（例如阿拉伯语使用 CP864 / Windows-1256）时才能正确打印，否则会出现空白或乱码。

这些文字的可靠做法是使用 **完整收据光栅化**，它会将整张收据渲染为图像，因此无论打印机内置字体如何，都能按设计效果打印。请参阅[打印机设置](/zh-CN/hardware/printers.md)了解如何启用光栅模式。

热敏硬件需要热敏模板

热敏打印机无法打印整页 **HTML** 模板，打印任务必须渲染为 ESC/POS 或 Star 命令，而 HTML 无法表达这些命令。热敏硬件请使用热敏模板；如需整页 A4/Letter HTML，请打印到系统/PDF 打印机，或通过 [PrintNode](/zh-CN/receipts/cloud-printing.md#setup-printnode) 打印。

### 徽标和图像[​](#logos-and-images "直接链接到 徽标和图像")

使用 `<image>` 来嵌入徽标：

```
<align mode="center">

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

</align>
```

WCPOS 会在客户端对图像进行光栅化：解码、将透明区域展平为白色、调整尺寸以适应打印机的点数预算、转换为单色（徽标使用 Atkinson 抖动，条形码使用阈值处理），然后发送 ESC/POS 或 Star 图像命令。

* **点数预算**：58mm 宽度为 384 点，80mm 宽度为 576 点。
* **接受的来源**：`data:image/png` 和 `data:image/jpeg` 数据 URL、绝对 `http(s)` URL，以及同源根相对路径。协议相对的 `//`、反斜杠以及百分号编码的 `..` 遍历都会被拒绝。
* **推荐格式**：高对比度 PNG，使用透明或白色背景。JPEG 也可使用，但压缩伪影可能会打印成噪点。
* 原始热敏输出**尚不支持 SVG**。

### 提示[​](#tips "直接链接到 提示")

* **从图库模板开始**——图库中的热敏模板使用了以上所有模式，并已通过真实打印机验证。
* 如果你的店铺使用不同的打印机，**请测试两种纸张尺寸**；也可以坚持使用 `width="*"` 列。
* 货币请**使用 `_display` 字段**——它们已支持本地化格式。
* **保持简单**——热敏打印在设计上比 HTML 的格式化工具更少。多依靠 `<row>` + `<col width="*">` 让打印机完成自己的工作。
