feat:取消系统自带软键盘设计,改为系统自用键盘组件-初步
feat:将打印分为图片打印和命令式打印-初步
This commit is contained in:
1 parent
b1b67b4dc7
commit
ddf2af76a5
9 files changed
+962
-60
No files matched your search
+7
@@ -115,6 +115,13 @@ public class HardInvoiceEntity extends Model<HardInvoiceEntity> {
|
||||
@Schema(description="打印图片")
|
||||
private String pic;
|
||||
|
||||
/**
|
||||
* 后端拼装的凭证打印指令流(Base64 的 ESC/POS 字节),供终端指令打印直写 USB。
|
||||
* 历史任务和图片生成失败时为空,终端指令模式下回退 pic 图片打印。
|
||||
*/
|
||||
@Schema(description="打印指令流Base64")
|
||||
private String printCmd;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
package com.bsd.pjcloud.hard.api.utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 面向 80mm 热敏打印机的 ESC/POS 文本指令拼装器,实现 {@link ReceiptRenderer}。
|
||||
*
|
||||
* <p>与 {@link ThermalReceiptImage} 保持同一套版式语义:相同的调用顺序在图片模式产出
|
||||
* PNG、在指令模式产出 ESC/POS 字节流,终端按本机设置二选一直写 USB。指令模式跳过
|
||||
* 图片栅格化与传输,出纸速度显著快于图片打印。</p>
|
||||
*
|
||||
* <p>排版约定:80mm 纸按 48 个 ANK 字符列计算(中文字符占 2 列);中文通过 ESC/POS
|
||||
* 汉字模式(FS & / FS .)以 GBK 编码输出,每行打印后立即关闭汉字模式避免状态泄漏。
|
||||
* 输出内容不含初始化以外的走纸与切刀命令,末尾走纸和切刀由终端按本机配置统一包装。</p>
|
||||
*/
|
||||
public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
|
||||
/** 80mm 纸标准 ANK 字符列数,与终端 EscGenerator 的 80mm 每行列数一致。 */
|
||||
public static final int CHARS_PER_LINE = 48;
|
||||
|
||||
/** ESC/POS 左对齐参数值。 */
|
||||
private static final int ALIGN_LEFT = 0;
|
||||
|
||||
/** ESC/POS 居中对齐参数值。 */
|
||||
private static final int ALIGN_CENTER = 1;
|
||||
|
||||
/** ESC/POS 右对齐参数值。 */
|
||||
private static final int ALIGN_RIGHT = 2;
|
||||
|
||||
/** GBK 字符集;绝大多数国产热敏打印机默认按 GBK 解析中文。 */
|
||||
private static final Charset GBK = Charset.forName("GBK");
|
||||
|
||||
/** 指令字节累积缓冲;toBase64 前不做任何 IO。 */
|
||||
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(2048);
|
||||
|
||||
/**
|
||||
* 创建指令拼装器并输出 ESC @ 初始化,确保终端写入前打印机残留样式被复位。
|
||||
*/
|
||||
public EscPosReceiptCommand() {
|
||||
writeRaw(0x1B, 0x40);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染居中加粗放大标题;超宽时自动降为单倍宽度保证一行放下。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand title(String text) {
|
||||
printCenteredScaled(text, true, 2);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染上下横线包围的居中加粗提示,近似图片模式的描边横幅。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand banner(String text) {
|
||||
printLine("-".repeat(CHARS_PER_LINE), ALIGN_LEFT, false, 1);
|
||||
printCenteredScaled(text, true, 2);
|
||||
printLine("-".repeat(CHARS_PER_LINE), ALIGN_LEFT, false, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染居中三倍加粗短码;超宽时降为双倍再单倍。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand verificationCode(String shortCode) {
|
||||
printCenteredScaled(shortCode == null || shortCode.isBlank() ? "--" : shortCode, true, 3);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染键值行;值放不下整行时标签独占一行,值从下一行起按整行宽度折行。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand keyValue(String label, String value) {
|
||||
String safeLabel = (label == null ? "" : label) + ":";
|
||||
String safeValue = value == null || value.isBlank() ? "--" : value;
|
||||
int valueCapacity = CHARS_PER_LINE - displayWidth(safeLabel);
|
||||
if (displayWidth(safeValue) <= valueCapacity) {
|
||||
printLine(safeLabel + safeValue, ALIGN_LEFT, false, 1);
|
||||
return this;
|
||||
}
|
||||
printLine(safeLabel, ALIGN_LEFT, false, 1);
|
||||
for (String line : wrapByWidth(safeValue, CHARS_PER_LINE)) {
|
||||
printLine(line, ALIGN_LEFT, false, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染左对齐加粗区块小标题。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand sectionTitle(String text) {
|
||||
printLine(text == null ? "" : text, ALIGN_LEFT, true, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染合并批次中单笔订单摘要:标签与订单号同行加粗,日期餐段随行,订单间短线分隔。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherOrderSummary(int index, int total, String orderNo,
|
||||
String orderDate, String mealName) {
|
||||
int safeTotal = Math.max(1, total);
|
||||
int safeIndex = Math.min(Math.max(1, index), safeTotal);
|
||||
String tag = "订单 " + safeIndex + "/" + safeTotal;
|
||||
String safeOrderNo = orderNo == null || orderNo.isBlank() ? "--" : orderNo;
|
||||
int orderCapacity = CHARS_PER_LINE - displayWidth(tag) - 2;
|
||||
List<String> orderLines = wrapByWidth(safeOrderNo, Math.max(8, orderCapacity));
|
||||
printTwoColumns(tag, orderLines.get(0), true);
|
||||
for (int lineIndex = 1; lineIndex < orderLines.size(); lineIndex++) {
|
||||
printLine(" ".repeat(displayWidth(tag) + 2) + orderLines.get(lineIndex), ALIGN_LEFT, true, 1);
|
||||
}
|
||||
String summary = "订餐日期:" + (orderDate == null || orderDate.isBlank() ? "--" : orderDate)
|
||||
+ " 餐段:" + (mealName == null || mealName.isBlank() ? "--" : mealName);
|
||||
for (String line : wrapByWidth(summary, CHARS_PER_LINE)) {
|
||||
printLine(line, ALIGN_LEFT, false, 1);
|
||||
}
|
||||
if (safeIndex < safeTotal) {
|
||||
printLine("-".repeat(32), ALIGN_LEFT, false, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染商品或套餐组成项:名称左、数量右,组成项缩进两列并加“-”前缀。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand item(String name, String quantity, boolean child, boolean emphasized) {
|
||||
String prefix = child ? " - " : "";
|
||||
String safeName = prefix + (name == null || name.isBlank() ? "未命名商品" : name);
|
||||
String safeQuantity = quantity == null || quantity.isBlank() ? "x0" : quantity;
|
||||
printNameTrailing(safeName, safeQuantity, 12, emphasized);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染发票商品行:名称左、数量加金额摘要右。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand invoiceItem(String name, String quantity, String amount) {
|
||||
String trailing = String.format("%s %s", quantity, amount);
|
||||
printNameTrailing(name == null || name.isBlank() ? "未命名商品" : name, trailing, 20, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片,数量为整数份数。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherProduct(String name, int quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
return voucherProduct(name, "x" + Math.max(0, quantity), unitPrice, subtotal, combo, componentItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片:类型标签与名称加粗同行,单价数量与小计两栏,组合内容缩进说明。
|
||||
*
|
||||
* <p>图片模式以描边卡片区分商品;指令模式用商品块后的空行近似同样的视觉间隔。</p>
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherProduct(String name, String quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
String typeLabel = combo ? "[组合商品]" : "[单品]";
|
||||
String safeName = name == null || name.isBlank() ? "未命名商品" : name;
|
||||
String safeQuantity = quantity == null || quantity.isBlank() ? "x0" : quantity;
|
||||
String priceText = "单价 ¥" + (unitPrice == null || unitPrice.isBlank() ? "--" : unitPrice)
|
||||
+ " " + safeQuantity;
|
||||
String subtotalText = "小计 ¥" + (subtotal == null || subtotal.isBlank() ? "--" : subtotal);
|
||||
|
||||
int nameCapacity = CHARS_PER_LINE - displayWidth(typeLabel) - 1;
|
||||
List<String> nameLines = wrapByWidth(safeName, nameCapacity);
|
||||
printLine(typeLabel + " " + nameLines.get(0), ALIGN_LEFT, true, 1);
|
||||
for (int lineIndex = 1; lineIndex < nameLines.size(); lineIndex++) {
|
||||
printLine(" ".repeat(displayWidth(typeLabel) + 1) + nameLines.get(lineIndex), ALIGN_LEFT, true, 1);
|
||||
}
|
||||
printTwoColumns(priceText, subtotalText, false);
|
||||
|
||||
List<String> safeComponents = componentItems == null ? List.of() : componentItems.stream()
|
||||
.filter(item -> item != null && !item.isBlank()).toList();
|
||||
if (combo && !safeComponents.isEmpty()) {
|
||||
printLine("组合内容", ALIGN_LEFT, true, 1);
|
||||
for (String component : safeComponents) {
|
||||
printLine(" - " + component, ALIGN_LEFT, false, 1);
|
||||
}
|
||||
}
|
||||
printLine("", ALIGN_LEFT, false, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染上下横线包围的商品合计;双倍宽度放不下时自动降为单倍。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherTotal(String totalAmount) {
|
||||
String safeAmount = totalAmount == null || totalAmount.isBlank() ? "--" : totalAmount;
|
||||
printLine("-".repeat(CHARS_PER_LINE), ALIGN_LEFT, false, 1);
|
||||
String label = "商品合计";
|
||||
String amount = "¥" + safeAmount;
|
||||
if (displayWidth(label) + displayWidth(amount) <= CHARS_PER_LINE / 2) {
|
||||
printTwoColumnsScaled(label, amount, 2);
|
||||
} else {
|
||||
printTwoColumns(label, amount, true);
|
||||
}
|
||||
printLine("-".repeat(CHARS_PER_LINE), ALIGN_LEFT, false, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染整行横线分隔。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand divider() {
|
||||
printLine("-".repeat(CHARS_PER_LINE), ALIGN_LEFT, false, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出 Base64 编码的完整指令流,供 MQTT 与打印待办接口下发给终端直写 USB。
|
||||
*
|
||||
* @return Base64 指令字符串;调用方直接持久化或下发,不需要再编码
|
||||
*/
|
||||
public String toBase64() {
|
||||
return java.util.Base64.getEncoder().encodeToString(buffer.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出居中放大文本;放大倍数放不下时逐级降为 2 倍再 1 倍,保证单行完整展示。
|
||||
*/
|
||||
private void printCenteredScaled(String text, boolean bold, int scale) {
|
||||
String safeText = text == null ? "" : text;
|
||||
int actualScale = scale;
|
||||
while (actualScale > 1 && displayWidth(safeText) > CHARS_PER_LINE / actualScale) {
|
||||
actualScale--;
|
||||
}
|
||||
printLine(safeText, ALIGN_CENTER, bold, actualScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出名称左、摘要右的两栏明细;名称按剩余宽度折行,摘要始终在首行右对齐。
|
||||
*
|
||||
* @param name 左侧名称文本
|
||||
* @param trailing 首行右对齐摘要
|
||||
* @param trailingWidth 右侧预留列数
|
||||
* @param bold 是否加粗
|
||||
*/
|
||||
private void printNameTrailing(String name, String trailing, int trailingWidth, boolean bold) {
|
||||
List<String> lines = wrapByWidth(name, CHARS_PER_LINE - trailingWidth);
|
||||
printTwoColumns(lines.get(0), trailing, bold);
|
||||
for (int index = 1; index < lines.size(); index++) {
|
||||
printLine(lines.get(index), ALIGN_LEFT, bold, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出单倍宽度两栏文本;左右间距不足 1 列时右侧文本自动折到下一行右对齐。
|
||||
*/
|
||||
private void printTwoColumns(String left, String right, boolean bold) {
|
||||
int gap = CHARS_PER_LINE - displayWidth(left) - displayWidth(right);
|
||||
if (gap >= 1) {
|
||||
printLine(left + " ".repeat(gap) + right, ALIGN_LEFT, bold, 1);
|
||||
return;
|
||||
}
|
||||
printLine(left, ALIGN_LEFT, bold, 1);
|
||||
for (String line : wrapByWidth(right, CHARS_PER_LINE)) {
|
||||
printLine(line, ALIGN_RIGHT, bold, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出双倍宽度两栏文本;倍宽后每行容量减半,放不下时右栏折行右对齐。
|
||||
*/
|
||||
private void printTwoColumnsScaled(String left, String right, int scale) {
|
||||
int capacity = CHARS_PER_LINE / scale;
|
||||
int gap = capacity - displayWidth(left) - displayWidth(right);
|
||||
if (gap >= 1) {
|
||||
printLine(left + " ".repeat(gap) + right, ALIGN_LEFT, true, scale);
|
||||
return;
|
||||
}
|
||||
printLine(left, ALIGN_LEFT, true, scale);
|
||||
for (String line : wrapByWidth(right, capacity)) {
|
||||
printLine(line, ALIGN_RIGHT, true, scale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出单行文本:对齐、加粗、放大、汉字模式开、GBK 编码、换行、汉字模式关。
|
||||
*
|
||||
* <p>每个样式指令在行内声明,行后仅复位放大与加粗,对齐由下一行重新声明,
|
||||
* 保证任意指令流截断或拼接都不会让打印机停留在未知样式状态。</p>
|
||||
*
|
||||
* @param text 行文本
|
||||
* @param align ESC a 对齐参数 0 左 1 中 2 右
|
||||
* @param bold 是否加粗
|
||||
* @param scale 放大倍数 1 单倍 2 双倍 3 三倍
|
||||
*/
|
||||
private void printLine(String text, int align, boolean bold, int scale) {
|
||||
writeRaw(0x1B, 0x61, align);
|
||||
if (bold) {
|
||||
writeRaw(0x1B, 0x45, 1);
|
||||
}
|
||||
if (scale > 1) {
|
||||
// GS ! n:高 4 位为高度倍数、低 4 位为宽度倍数,宽度决定每行容量。
|
||||
writeRaw(0x1D, 0x21, (scale << 4) | scale);
|
||||
}
|
||||
writeRaw(0x1C, 0x26);
|
||||
writeText(text);
|
||||
writeRaw('\n');
|
||||
writeRaw(0x1C, 0x2E);
|
||||
if (scale > 1) {
|
||||
writeRaw(0x1D, 0x21, 0x01);
|
||||
}
|
||||
if (bold) {
|
||||
writeRaw(0x1B, 0x45, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加文本的 GBK 字节;GBK 为 JDK 内置字符集,不会抛出编码异常。
|
||||
*/
|
||||
private void writeText(String text) {
|
||||
buffer.writeBytes(text.getBytes(GBK));
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加原始指令字节。
|
||||
*/
|
||||
private void writeRaw(int... bytes) {
|
||||
for (int value : bytes) {
|
||||
buffer.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文本的显示列数:中文字符按 2 列、其余按 1 列,与打印机汉字模式宽度一致。
|
||||
*/
|
||||
static int displayWidth(String text) {
|
||||
int width = 0;
|
||||
for (int index = 0; index < text.length(); index++) {
|
||||
width += text.charAt(index) > 0x7F ? 2 : 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按显示列数折行;单个超宽字符仍至少独占一行,与图片绘制器的折行语义一致。
|
||||
*/
|
||||
static List<String> wrapByWidth(String text, int maxWidth) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
StringBuilder line = new StringBuilder();
|
||||
int lineWidth = 0;
|
||||
String safeText = text == null ? "" : text;
|
||||
for (int index = 0; index < safeText.length(); index++) {
|
||||
char character = safeText.charAt(index);
|
||||
int charWidth = character > 0x7F ? 2 : 1;
|
||||
if (lineWidth > 0 && lineWidth + charWidth > maxWidth) {
|
||||
lines.add(line.toString());
|
||||
line.setLength(0);
|
||||
lineWidth = 0;
|
||||
}
|
||||
line.append(character);
|
||||
lineWidth += charWidth;
|
||||
}
|
||||
if (line.length() > 0 || lines.isEmpty()) lines.add(line.toString());
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.bsd.pjcloud.hard.api.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 打印凭证版式渲染器契约,发票与兑换券的数据拼装只依赖本接口。
|
||||
*
|
||||
* <p>当前实现有两个:{@link ThermalReceiptImage} 渲染 576px PNG 图片供图片栅格打印;
|
||||
* {@link EscPosReceiptCommand} 直接拼装 ESC/POS 文本指令供指令打印,出纸更快。
|
||||
* 业务层用同一份调用序列驱动两个渲染器,保证两种输出方式的凭证内容始终一致。</p>
|
||||
*
|
||||
* <p>实现类的方法返回类型允许返回自身具体类型以保留链式调用(协变返回),拼接顺序
|
||||
* 由调用方保证,本接口不承载状态查询。</p>
|
||||
*/
|
||||
public interface ReceiptRenderer {
|
||||
|
||||
/**
|
||||
* 渲染居中大号标题,例如“智慧食堂发票”。
|
||||
*
|
||||
* @param text 标题文本
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer title(String text);
|
||||
|
||||
/**
|
||||
* 渲染醒目的整行提示,例如“第 1 次打印 · 2 笔预约”。
|
||||
*
|
||||
* @param text 提示文本
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer banner(String text);
|
||||
|
||||
/**
|
||||
* 渲染醒目的核销或取件短码。
|
||||
*
|
||||
* @param shortCode 短码文本;空值由实现决定占位展示
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer verificationCode(String shortCode);
|
||||
|
||||
/**
|
||||
* 渲染一行“字段名:字段值”信息;值过长时由实现负责折行。
|
||||
*
|
||||
* @param label 字段名
|
||||
* @param value 字段值;空白由实现决定占位展示
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer keyValue(String label, String value);
|
||||
|
||||
/**
|
||||
* 渲染区块小标题,例如“用户信息”、“商品明细”。
|
||||
*
|
||||
* @param text 标题文本
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer sectionTitle(String text);
|
||||
|
||||
/**
|
||||
* 渲染多订单合并兑换券中的单笔订单摘要。
|
||||
*
|
||||
* @param index 当前订单在合并批次中的一号起始序号
|
||||
* @param total 合并批次订单总数
|
||||
* @param orderNo 对外展示的完整订单号
|
||||
* @param orderDate 预约订餐日期
|
||||
* @param mealName 餐段名称
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherOrderSummary(int index, int total, String orderNo,
|
||||
String orderDate, String mealName);
|
||||
|
||||
/**
|
||||
* 渲染商品或套餐组成项,数量在首行右对齐。
|
||||
*
|
||||
* @param name 商品名称
|
||||
* @param quantity 数量文本
|
||||
* @param child 是否为套餐组成项
|
||||
* @param emphasized 是否加粗强调
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer item(String name, String quantity, boolean child, boolean emphasized);
|
||||
|
||||
/**
|
||||
* 渲染带数量和金额摘要的发票商品行。
|
||||
*
|
||||
* @param name 商品名称
|
||||
* @param quantity 数量或重量文本
|
||||
* @param amount 含货币符号的商品总价
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer invoiceItem(String name, String quantity, String amount);
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片,数量为整数份数。
|
||||
*
|
||||
* @param name 商品快照名称
|
||||
* @param quantity 剩余可履约数量
|
||||
* @param unitPrice 商品快照单价,单位元且不含货币符号
|
||||
* @param subtotal 剩余可履约数量对应的小计,单位元且不含货币符号
|
||||
* @param combo 是否为组合商品
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;可为空
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherProduct(String name, int quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems);
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片,数量为已格式化文本(份数“x2”或称重“0.5/kg”)。
|
||||
*
|
||||
* @param name 商品快照名称
|
||||
* @param quantity 数量展示文本
|
||||
* @param unitPrice 商品快照单价,单位元且不含货币符号
|
||||
* @param subtotal 商品小计,单位元且不含货币符号
|
||||
* @param combo 是否为组合商品
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;可为空
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherProduct(String name, String quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems);
|
||||
|
||||
/**
|
||||
* 渲染商品合计,形成票据金额层级。
|
||||
*
|
||||
* @param totalAmount 商品合计金额,单位元且不含货币符号
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherTotal(String totalAmount);
|
||||
|
||||
/**
|
||||
* 渲染一条横向分隔线。
|
||||
*
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer divider();
|
||||
}
|
||||
+4
-1
@@ -17,8 +17,11 @@ import java.util.List;
|
||||
* 面向 80mm、576px ESC/POS 打印的动态高度 PNG 绘制器。
|
||||
*
|
||||
* <p>商品名称按真实像素宽度换行,数量列固定右对齐,保证长名称不会与数量错配。</p>
|
||||
*
|
||||
* <p>实现 {@link ReceiptRenderer} 版式契约,方法返回自身具体类型保留链式调用;
|
||||
* 指令打印由同包的 {@link EscPosReceiptCommand} 以相同调用序列产出 ESC/POS 字节。</p>
|
||||
*/
|
||||
public class ThermalReceiptImage {
|
||||
public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
|
||||
/** 80mm 打印头常见有效像素宽度。 */
|
||||
public static final int PAPER_WIDTH_PIXELS = 576;
|
||||
|
||||
Reference in new issue
Block a user