11
This commit is contained in:
1 parent
5c47704e09
commit
ccab8a9de6
2 files changed
+134
-51
No files matched your search
+114
-29
@@ -15,8 +15,8 @@ import java.util.List;
|
||||
* <p>排版约定:80mm 纸按 48 个 ANK 字符列计算(中文和制表符占 2 列);中文通过 ESC/POS
|
||||
* 汉字模式(FS & / FS .)以 GBK 编码输出,每行打印后立即关闭汉字模式避免状态泄漏。
|
||||
* 图片版的矩形边框用 GBK 字库制表符(─ │ ┌ ┐ └ ┘)拼装,相邻字符字模相连成实线;
|
||||
* 黑底白字标签用 ESC { 反色指令输出。输出内容不含初始化以外的走纸与切刀命令,
|
||||
* 末尾走纸和切刀由终端按本机配置统一包装。</p>
|
||||
* 黑底白字标签用 GS B 反色指令输出。区块间距按图片版像素值(1px≈1 点)用压缩行距的
|
||||
* 空行实现。输出不含切刀和大段走纸命令,末尾走纸和切刀由终端按本机配置统一包装。</p>
|
||||
*/
|
||||
public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
|
||||
@@ -70,27 +70,26 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染制表符描边矩形框包围的居中加粗提示,还原图片模式的横幅边框。
|
||||
* 渲染倍宽粗横线上下包围的居中倍宽加粗提示,还原图片模式 44px 大字横幅。
|
||||
*
|
||||
* <p>图片版为四边描边框加特大字;指令模式放大到倍宽后文字已占满纸宽,无法再容纳
|
||||
* 左右竖线,以两条倍宽粗横线近似横幅上下边,横幅后按图片版留 14 点空隙。</p>
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand banner(String text) {
|
||||
List<String> lines = wrapByWidth(text == null ? "" : text, BOX_INNER_WIDTH);
|
||||
printBoxTop();
|
||||
for (String line : lines) {
|
||||
int pad = BOX_INNER_WIDTH - displayWidth(line);
|
||||
int left = pad / 2;
|
||||
boxLine(" ".repeat(left) + line + " ".repeat(pad - left), true);
|
||||
}
|
||||
printBoxBottom();
|
||||
printLine(BOX_HORIZONTAL.repeat(CHARS_PER_LINE / 4), ALIGN_LEFT, true, 2);
|
||||
printCenteredScaled(text, true, 2);
|
||||
printLine(BOX_HORIZONTAL.repeat(CHARS_PER_LINE / 4), ALIGN_LEFT, true, 2);
|
||||
feedGap(14);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染居中三倍加粗短码;超宽时降为双倍再单倍。
|
||||
* 渲染居中加粗放大短码;图片版为 48px 特大字,对应指令双倍宽度,超宽时逐级降为单倍。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand verificationCode(String shortCode) {
|
||||
printCenteredScaled(shortCode == null || shortCode.isBlank() ? "--" : shortCode, true, 3);
|
||||
printCenteredScaled(shortCode == null || shortCode.isBlank() ? "--" : shortCode, true, 2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -182,25 +181,32 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片:制表符矩形框包围,内部为反色类型标签、加粗名称、两栏价格和组合内容。
|
||||
* 渲染兑换券商品卡片:制表符矩形框包围,内部为类型标签、倍宽加粗名称、两栏价格和组合内容。
|
||||
*
|
||||
* <p>与图片版卡片一一对应:框线对应描边矩形、反色标签对应黑底白字 tag、粗体名称对应大号商品名,
|
||||
* 组合内容区保留内部短线与缩进组成项;卡片之间输出空行还原图片的卡片间距。</p>
|
||||
* <p>与图片版卡片逐项对应:框线对应描边矩形;组合商品标签为 GS B 反色黑底(对应图片
|
||||
* fillRect 黑底白字),单品标签为制表符描边小框(对应图片 drawRect 描边框);卡片内部
|
||||
* 压缩行距让左右竖线行间衔接不断线,各区块间距按图片版像素值(1px≈1 点)走纸。</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 priceText = "单价 ¥" + (unitPrice == null || unitPrice.isBlank() ? "--" : unitPrice)
|
||||
+ " " + (quantity == null || quantity.isBlank() ? "x0" : quantity);
|
||||
String subtotalText = "小计 ¥" + (subtotal == null || subtotal.isBlank() ? "--" : subtotal);
|
||||
|
||||
// 行距压到字高附近(ESC 3 19 ≈ 27 点),竖线行间只留极小缝隙,视觉上连续。
|
||||
setLineSpacing(19);
|
||||
printBoxTop();
|
||||
boxLineFragments(List.of(new Segment(typeLabel, true)), false);
|
||||
for (String nameLine : wrapByWidth(safeName, BOX_INNER_WIDTH)) {
|
||||
boxLine(nameLine, true);
|
||||
if (combo) {
|
||||
boxLineFragments(List.of(new Segment(" 组合商品 ", true)), false);
|
||||
} else {
|
||||
printSingleTagFrame(" 单品 ");
|
||||
}
|
||||
// 商品名称倍宽加粗还原图片版 40px 特大字;竖线保持单倍细线,避免边框粗细交替。
|
||||
int scaledInner = CHARS_PER_LINE / 2 - 4;
|
||||
for (String nameLine : wrapByWidth(safeName, scaledInner)) {
|
||||
printScaledNameInBox(nameLine, scaledInner - displayWidth(nameLine));
|
||||
}
|
||||
boxLineTwoColumns(priceText, subtotalText, false);
|
||||
|
||||
@@ -216,12 +222,50 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
}
|
||||
}
|
||||
printBoxBottom();
|
||||
printLine("", ALIGN_LEFT, false, 1);
|
||||
resetLineSpacing();
|
||||
// 卡片间距按图片版 14px 间隙走一个压缩行距空行。
|
||||
feedGap(14);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染上下制表符实线包围的商品合计;双倍宽度放不下时自动降为单倍。
|
||||
* 输出商品卡片内的倍宽加粗名称行:左右竖线保持单倍细线,仅名称文字片段放大加粗。
|
||||
*
|
||||
* <p>GS ! 与 ESC E 是行内即时指令,竖线输出后再开、名称结束即关,卡片左右边框
|
||||
* 不随名称放大变粗,与上下框线粗细一致。</p>
|
||||
*
|
||||
* @param nameLine 已按倍宽容量折行的名称文本
|
||||
* @param padColumns 名称后的倍宽空格填充列数,保证右竖线与上下框线对齐
|
||||
*/
|
||||
private void printScaledNameInBox(String nameLine, int padColumns) {
|
||||
writeRaw(0x1B, 0x61, ALIGN_LEFT);
|
||||
writeRaw(0x1C, 0x26);
|
||||
writeText(BOX_VERTICAL);
|
||||
writeRaw(0x1D, 0x21, 0x11);
|
||||
writeRaw(0x1B, 0x45, 1);
|
||||
writeText(nameLine);
|
||||
writeRaw(0x1B, 0x45, 0);
|
||||
writeText(" ".repeat(Math.max(0, padColumns)));
|
||||
writeRaw(0x1D, 0x21, 0x00);
|
||||
writeText(BOX_VERTICAL);
|
||||
writeRaw('\n');
|
||||
writeRaw(0x1C, 0x2E);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染单品类型标签的制表符描边小框(三行),还原图片版单品 tag 的描边矩形黑字。
|
||||
*
|
||||
* @param tag 含前后空格的标签文本,空格保证框内留白
|
||||
*/
|
||||
private void printSingleTagFrame(String tag) {
|
||||
int frameWidth = displayWidth(tag) / 2;
|
||||
boxLine(BOX_TOP_LEFT + BOX_HORIZONTAL.repeat(frameWidth) + BOX_TOP_RIGHT, false);
|
||||
boxLine(BOX_VERTICAL + tag + BOX_VERTICAL, false);
|
||||
boxLine(BOX_BOTTOM_LEFT + BOX_HORIZONTAL.repeat(frameWidth) + BOX_BOTTOM_RIGHT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染上下制表符实线包围的商品合计;双倍宽度放不下时自动降为单倍,合计后按图片版留空隙。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherTotal(String totalAmount) {
|
||||
@@ -235,11 +279,12 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
printTwoColumns(label, amount, true);
|
||||
}
|
||||
printLine(BOX_HORIZONTAL.repeat(CHARS_PER_LINE / 2), ALIGN_LEFT, false, 1);
|
||||
feedGap(14);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染整行制表符实线分隔。
|
||||
* 渲染整行制表符实线分隔;行距自带的行间空隙即视觉间隔,不再额外走纸。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand divider() {
|
||||
@@ -422,23 +467,24 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
writeRaw(0x1B, 0x45, 1);
|
||||
}
|
||||
if (scale > 1) {
|
||||
// GS ! n:高 4 位为高度倍数、低 4 位为宽度倍数,宽度决定每行容量。
|
||||
writeRaw(0x1D, 0x21, (scale << 4) | scale);
|
||||
// GS ! n:高 4 位为高度倍数、低 4 位为宽度倍数;半字节值 0x01 表示 2 倍,0x00 表示正常大小。
|
||||
writeRaw(0x1D, 0x21, ((scale - 1) << 4) | (scale - 1));
|
||||
}
|
||||
writeRaw(0x1C, 0x26);
|
||||
for (Segment segment : segments) {
|
||||
if (segment.reverse()) {
|
||||
writeRaw(0x1B, 0x7B, 1);
|
||||
// GS B n 为黑白反转指令;ESC { 是字符倒置指令,不能用于反色。
|
||||
writeRaw(0x1D, 0x42, 1);
|
||||
}
|
||||
writeText(segment.text());
|
||||
if (segment.reverse()) {
|
||||
writeRaw(0x1B, 0x7B, 0);
|
||||
writeRaw(0x1D, 0x42, 0);
|
||||
}
|
||||
}
|
||||
writeRaw('\n');
|
||||
writeRaw(0x1C, 0x2E);
|
||||
if (scale > 1) {
|
||||
writeRaw(0x1D, 0x21, 0x01);
|
||||
writeRaw(0x1D, 0x21, 0x00);
|
||||
}
|
||||
if (bold) {
|
||||
writeRaw(0x1B, 0x45, 0);
|
||||
@@ -461,6 +507,45 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出与图片版像素间距对应的垂直空隙(图片 576px 内容宽与打印 576 点一一对应)。
|
||||
*
|
||||
* <p>不使用 ESC d 点级走纸:实测部分国产打印机对其单位实现不标准,会放大数十倍导致
|
||||
* 出纸失控。此处改用所有机型都可靠支持的行距指令:临时把行距压到目标点数、走一个
|
||||
* 空行、立即恢复默认行距,单次空隙即目标像素值,不存在被放大的空间。</p>
|
||||
*
|
||||
* @param pixels 图片版像素间距,0 至 60
|
||||
*/
|
||||
private void feedGap(int pixels) {
|
||||
if (pixels <= 0) {
|
||||
return;
|
||||
}
|
||||
// 点数换算为 ESC 3 的 1/144 英寸单位(203dpi 下 1 单位约 1.41 点),最小 2 保证肉眼可见。
|
||||
int units = Math.max(2, Math.round(pixels * 144f / 203f));
|
||||
setLineSpacing(Math.min(255, units));
|
||||
writeRaw('\n');
|
||||
resetLineSpacing();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置行间距(ESC 3 n,单位 1/144 英寸,203dpi 下 n≈27 为字高加小缝)。
|
||||
*
|
||||
* <p>默认行距(ESC 2 = 1/6 英寸 ≈ 34 点)会在相邻行间留下约 10 点空隙,导致卡片
|
||||
* 左右竖线视觉断线;卡片内部压缩行距后竖线行间衔接。</p>
|
||||
*
|
||||
* @param units 行间距单位值
|
||||
*/
|
||||
private void setLineSpacing(int units) {
|
||||
writeRaw(0x1B, 0x33, units);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复默认行距(ESC 2 = 1/6 英寸)。
|
||||
*/
|
||||
private void resetLineSpacing() {
|
||||
writeRaw(0x1B, 0x32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文本的显示列数:中文字符按 2 列、其余按 1 列,与打印机汉字模式宽度一致。
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user