|
|
|
@ -8,38 +8,49 @@ import lombok.extern.slf4j.Slf4j; |
|
|
|
import java.math.BigDecimal; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
import com.google.zxing.*; |
|
|
|
import com.google.zxing.client.j2se.MatrixToImageWriter; |
|
|
|
import com.google.zxing.common.BitMatrix; |
|
|
|
|
|
|
|
import javax.imageio.ImageIO; |
|
|
|
import java.awt.image.BufferedImage; |
|
|
|
import java.io.ByteArrayInputStream; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
/** |
|
|
|
* ZPL代码生成器 |
|
|
|
* 统一处理不同打印方向的ZPL代码生成 |
|
|
|
*/ |
|
|
|
@Slf4j |
|
|
|
public class ZplGenerator { |
|
|
|
|
|
|
|
|
|
|
|
private String orientation; |
|
|
|
private Integer dpi; |
|
|
|
private CoordinateTransformer transformer; |
|
|
|
private ZplConfig config; |
|
|
|
private com.gaotao.modules.base.service.FontService fontService; |
|
|
|
|
|
|
|
|
|
|
|
public ZplGenerator(String orientation, Integer dpi, CoordinateTransformer.CanvasSize canvasSize) { |
|
|
|
this.orientation = orientation != null ? orientation : "portrait"; |
|
|
|
this.dpi = dpi != null ? dpi : 203; |
|
|
|
this.transformer = new CoordinateTransformer(this.orientation, canvasSize); |
|
|
|
this.config = getConfig(); |
|
|
|
} |
|
|
|
|
|
|
|
public ZplGenerator(String orientation, Integer dpi, CoordinateTransformer.CanvasSize canvasSize, |
|
|
|
|
|
|
|
public ZplGenerator(String orientation, Integer dpi, CoordinateTransformer.CanvasSize canvasSize, |
|
|
|
com.gaotao.modules.base.service.FontService fontService) { |
|
|
|
this(orientation, dpi, canvasSize); |
|
|
|
this.fontService = fontService; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取ZPL配置参数 |
|
|
|
*/ |
|
|
|
private ZplConfig getConfig() { |
|
|
|
ZplConfig config = new ZplConfig(); |
|
|
|
|
|
|
|
|
|
|
|
if ("landscape".equals(this.orientation)) { |
|
|
|
config.setFieldOrientation("^FWB"); // 旋转90度 |
|
|
|
config.setBarcodeOrientation("B"); // 旋转90度条码 |
|
|
|
@ -49,10 +60,10 @@ public class ZplGenerator { |
|
|
|
config.setBarcodeOrientation("N"); // 正常条码 |
|
|
|
config.setQrcodeOrientation("N"); // 正常二维码 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return config; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成完整的ZPL代码 |
|
|
|
*/ |
|
|
|
@ -60,11 +71,11 @@ public class ZplGenerator { |
|
|
|
if (elements == null || elements.isEmpty()) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
StringBuilder zpl = new StringBuilder(); |
|
|
|
zpl.append("^XA\n"); // ZPL开始标记 |
|
|
|
zpl.append(config.getFieldOrientation()).append("\n"); // 设置打印方向 |
|
|
|
|
|
|
|
|
|
|
|
// 处理每个元素 |
|
|
|
for (ReportLabelList element : elements) { |
|
|
|
String elementZpl = generateElementZPL(element); |
|
|
|
@ -72,12 +83,12 @@ public class ZplGenerator { |
|
|
|
zpl.append(elementZpl).append("\n"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
zpl.append("^XZ\n"); // ZPL结束标记 |
|
|
|
|
|
|
|
|
|
|
|
return zpl.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成单个元素的ZPL代码 |
|
|
|
*/ |
|
|
|
@ -85,12 +96,12 @@ public class ZplGenerator { |
|
|
|
if (element == null) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
CoordinateTransformer.ZplCoordinate coord = transformer.toZPL( |
|
|
|
element.getX() != null ? element.getX() : 0, |
|
|
|
element.getY() != null ? element.getY() : 0 |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
switch (element.getType()) { |
|
|
|
case "text": |
|
|
|
return generateTextZPL(element, coord.getX(), coord.getY()); |
|
|
|
@ -110,30 +121,30 @@ public class ZplGenerator { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成文本ZPL代码 |
|
|
|
*/ |
|
|
|
private String generateTextZPL(ReportLabelList element, int x, int y) { |
|
|
|
StringBuilder zpl = new StringBuilder(); |
|
|
|
|
|
|
|
|
|
|
|
// 设置字体 |
|
|
|
Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; |
|
|
|
String fontCommand = generateFontCommand(element, fontSize); |
|
|
|
zpl.append(fontCommand).append("\n"); |
|
|
|
|
|
|
|
|
|
|
|
// 如果有复选框 |
|
|
|
if (Boolean.TRUE.equals(element.getIsChecked())) { |
|
|
|
zpl.append("^FO").append(x - 60).append(",").append(y) |
|
|
|
.append("^GB45,45,2,B,0^FS ^FO").append(x - 50).append(",").append(y + 10) |
|
|
|
.append("^AJN,35,35^FD√^FS\n"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String data = element.getData() != null ? element.getData() : ""; |
|
|
|
|
|
|
|
|
|
|
|
// 处理文本对齐 |
|
|
|
String alignmentParam = getTextAlignmentParam(element.getTextAlign()); |
|
|
|
|
|
|
|
|
|
|
|
// 基础文本 |
|
|
|
if (Boolean.TRUE.equals(element.getNewline())) { |
|
|
|
// 多行文本 |
|
|
|
@ -148,7 +159,7 @@ public class ZplGenerator { |
|
|
|
zpl.append("^FO").append(x).append(",").append(y) |
|
|
|
.append("^FD").append(data).append("^FS"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 加粗效果(通过偏移重复打印实现) |
|
|
|
if (Boolean.TRUE.equals(element.getBold())) { |
|
|
|
if (Boolean.TRUE.equals(element.getNewline())) { |
|
|
|
@ -175,7 +186,7 @@ public class ZplGenerator { |
|
|
|
.append("^FD").append(data).append("^FS"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 下划线效果(通过线条实现) |
|
|
|
if (Boolean.TRUE.equals(element.getFontUnderline())) { |
|
|
|
int textWidth = estimateTextWidth(data, fontSize); |
|
|
|
@ -183,36 +194,36 @@ public class ZplGenerator { |
|
|
|
zpl.append("\n^FO").append(x).append(",").append(underlineY) |
|
|
|
.append("^GB").append(textWidth).append(",2,2,B^FS"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return zpl.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成字体命令 |
|
|
|
*/ |
|
|
|
private String generateFontCommand(ReportLabelList element, Integer fontSize) { |
|
|
|
StringBuilder fontCmd = new StringBuilder(); |
|
|
|
|
|
|
|
|
|
|
|
// 设置字符编码 |
|
|
|
fontCmd.append("^CI28"); |
|
|
|
|
|
|
|
|
|
|
|
// 根据字体族选择字体 |
|
|
|
String fontFamily = element.getFontFamily(); |
|
|
|
String fontFile = null; |
|
|
|
|
|
|
|
|
|
|
|
if (fontFamily != null && !"default".equals(fontFamily)) { |
|
|
|
// 优先使用FontService获取字体文件 |
|
|
|
if (fontService != null) { |
|
|
|
fontFile = fontService.getFontFilePath(fontFamily); |
|
|
|
log.debug("通过FontService获取字体文件: {} -> {}", fontFamily, fontFile); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果FontService没有找到,使用静态映射 |
|
|
|
if (fontFile == null) { |
|
|
|
fontFile = mapFontFamilyToFile(fontFamily); |
|
|
|
log.debug("通过静态映射获取字体文件: {} -> {}", fontFamily, fontFile); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (fontFile != null) { |
|
|
|
fontCmd.append(" ^CWJ,E:").append(fontFile); |
|
|
|
} else { |
|
|
|
@ -224,13 +235,13 @@ public class ZplGenerator { |
|
|
|
// 默认使用微软雅黑 |
|
|
|
fontCmd.append(" ^CWJ,E:MSYH.TTF"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置字体大小 |
|
|
|
fontCmd.append(" ^CFJ,").append(fontSize); |
|
|
|
|
|
|
|
|
|
|
|
return fontCmd.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 映射字体族到字体文件 |
|
|
|
* 注意:这是一个静态方法,在实际使用中应该通过FontService来获取 |
|
|
|
@ -285,7 +296,7 @@ public class ZplGenerator { |
|
|
|
return null; // 使用默认字体 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取文本对齐参数 |
|
|
|
*/ |
|
|
|
@ -293,7 +304,7 @@ public class ZplGenerator { |
|
|
|
if (textAlign == null) { |
|
|
|
return "L"; // 默认左对齐 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
switch (textAlign) { |
|
|
|
case "center": |
|
|
|
return "C"; |
|
|
|
@ -304,7 +315,7 @@ public class ZplGenerator { |
|
|
|
return "L"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 估算文本宽度(用于下划线) |
|
|
|
*/ |
|
|
|
@ -312,11 +323,11 @@ public class ZplGenerator { |
|
|
|
if (text == null || text.isEmpty()) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 简单估算:中文字符按字体大小计算,英文字符按字体大小的0.6倍计算 |
|
|
|
int chineseCount = 0; |
|
|
|
int englishCount = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (char c : text.toCharArray()) { |
|
|
|
if (c >= 0x4e00 && c <= 0x9fff) { |
|
|
|
chineseCount++; |
|
|
|
@ -324,10 +335,10 @@ public class ZplGenerator { |
|
|
|
englishCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return (int) (chineseCount * fontSize + englishCount * fontSize * 0.6); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成一维码ZPL代码 |
|
|
|
*/ |
|
|
|
@ -335,14 +346,14 @@ public class ZplGenerator { |
|
|
|
String orientation = config.getBarcodeOrientation(); |
|
|
|
String barcodeType = element.getBarcodeType() != null ? element.getBarcodeType() : "CODE128"; |
|
|
|
boolean showContent = element.getShowContent() != null ? element.getShowContent() : true; |
|
|
|
|
|
|
|
|
|
|
|
// 将毫米转换为ZPL单位 |
|
|
|
double widthMM = element.getWidth() != null ? element.getWidth().doubleValue() : 2.0; |
|
|
|
int width = Math.max(1, Math.min(10, (int) Math.round(widthMM * 1.5))); |
|
|
|
|
|
|
|
|
|
|
|
// 高度:毫米转换为点数 |
|
|
|
int height = Math.max(1, Math.round((element.getHeight() != null ? element.getHeight() : 15) * this.dpi / 25.4f)); |
|
|
|
|
|
|
|
|
|
|
|
// 根据条码类型选择ZPL指令 |
|
|
|
String zplCommand; |
|
|
|
switch (barcodeType) { |
|
|
|
@ -368,48 +379,99 @@ public class ZplGenerator { |
|
|
|
zplCommand = "^BC" + orientation; // CODE128 |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 构建完整的ZPL指令 |
|
|
|
String contentParam = showContent ? "Y" : "N"; |
|
|
|
String additionalParams = "B".equals(orientation) ? "," + contentParam + ",N,N" : "," + contentParam; |
|
|
|
String data = element.getData() != null ? element.getData() : ""; |
|
|
|
|
|
|
|
return String.format("^FO%d,%d^BY%d%s,%d%s^FD%s^FS", |
|
|
|
|
|
|
|
return String.format("^FO%d,%d^BY%d%s,%d%s^FD%s^FS", |
|
|
|
x, y, width, zplCommand, height, additionalParams, data); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成二维码ZPL代码 |
|
|
|
*/ |
|
|
|
private String generateQRCodeZPL(ReportLabelList element, int x, int y) { |
|
|
|
String orientation = config.getQrcodeOrientation(); |
|
|
|
String data = element.getData() != null ? element.getData() : ""; |
|
|
|
|
|
|
|
// 将毫米转换为ZPL尺寸单位 |
|
|
|
int sizeInDots = Math.max(1, Math.round((element.getHeight() != null ? element.getHeight() : 10) * this.dpi / 25.4f)); |
|
|
|
int size = Math.max(1, Math.min(10, Math.round(sizeInDots / 25f))); |
|
|
|
|
|
|
|
// 根据数据长度自动调整最小尺寸 |
|
|
|
if (data.length() > 300) { |
|
|
|
size = Math.max(size, 8); |
|
|
|
} else if (data.length() > 200) { |
|
|
|
size = Math.max(size, 6); |
|
|
|
} else if (data.length() > 100) { |
|
|
|
size = Math.max(size, 4); |
|
|
|
} else { |
|
|
|
size = Math.max(size, 2); |
|
|
|
try { |
|
|
|
return generateZplQRCode(data, sizeInDots, x, y); |
|
|
|
} catch (Exception e) { |
|
|
|
throw new RuntimeException(e); |
|
|
|
} |
|
|
|
|
|
|
|
// 根据数据类型选择模式 |
|
|
|
if (data.matches("^\\d+$")) { |
|
|
|
// 纯数字内容,使用数字模式 |
|
|
|
return String.format("^FO%d,%d^BQ%s,2,%d^FDMN,%s^FS", x, y, orientation, size, data); |
|
|
|
} else { |
|
|
|
// 混合内容,使用自动模式 |
|
|
|
return String.format("^FO%d,%d^BQ%s,2,%d^FDMA,%s^FS", x, y, orientation, size, data); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 生成二维码并转为 ZPL |
|
|
|
* |
|
|
|
* @param content 二维码内容(支持中文) |
|
|
|
* @param size 二维码宽高(像素 == dots) |
|
|
|
* @param x 打印起点 X 坐标(单位:dots) |
|
|
|
* @param y 打印起点 Y 坐标(单位:dots) |
|
|
|
* @return ZPL 字符串 |
|
|
|
*/ |
|
|
|
public String generateZplQRCode(String content, int size, int x, int y) throws Exception { |
|
|
|
byte[] qrBytes = generateQRCode(content, size, size); |
|
|
|
return convertToZpl(qrBytes, x, y); |
|
|
|
} |
|
|
|
|
|
|
|
// 1. 生成二维码(支持中文) |
|
|
|
private static byte[] generateQRCode(String content, int width, int height) throws Exception { |
|
|
|
Map<EncodeHintType, Object> hints = new HashMap<>(); |
|
|
|
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); |
|
|
|
hints.put(EncodeHintType.MARGIN, 1); |
|
|
|
BitMatrix bitMatrix = new MultiFormatWriter().encode( |
|
|
|
content, |
|
|
|
BarcodeFormat.QR_CODE, |
|
|
|
width, |
|
|
|
height, |
|
|
|
hints |
|
|
|
); |
|
|
|
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix); |
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
|
|
ImageIO.write(image, "png", baos); |
|
|
|
return baos.toByteArray(); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 转换 PNG 图片为 ZPL ^GF 指令 |
|
|
|
private static String convertToZpl(byte[] imageBytes, int x, int y) throws Exception { |
|
|
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes)); |
|
|
|
int widthBytes = (image.getWidth() + 7) / 8; // 每行字节数 |
|
|
|
int totalBytes = widthBytes * image.getHeight(); |
|
|
|
StringBuilder zpl = new StringBuilder(); |
|
|
|
zpl.append("^FO").append(x).append(",").append(y).append("\n"); |
|
|
|
zpl.append("^GFA,").append(totalBytes).append(",").append(totalBytes).append(",").append(widthBytes).append(","); |
|
|
|
StringBuilder imageData = new StringBuilder(); |
|
|
|
for (int yPos = 0; yPos < image.getHeight(); yPos++) { |
|
|
|
int bit = 0; |
|
|
|
int currentByte = 0; |
|
|
|
for (int xPos = 0; xPos < image.getWidth(); xPos++) { |
|
|
|
int rgb = image.getRGB(xPos, yPos); |
|
|
|
int gray = (rgb >> 16 & 0xff) * 299 + (rgb >> 8 & 0xff) * 587 + (rgb & 0xff) * 114; |
|
|
|
gray = gray / 1000; |
|
|
|
|
|
|
|
currentByte <<= 1; |
|
|
|
if (gray < 128) { // 黑色点 |
|
|
|
currentByte |= 1; |
|
|
|
} |
|
|
|
bit++; |
|
|
|
if (bit == 8) { |
|
|
|
imageData.append(String.format("%02X", currentByte)); |
|
|
|
bit = 0; |
|
|
|
currentByte = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
if (bit > 0) { |
|
|
|
currentByte <<= (8 - bit); |
|
|
|
imageData.append(String.format("%02X", currentByte)); |
|
|
|
} |
|
|
|
} |
|
|
|
zpl.append(imageData); |
|
|
|
return zpl.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成图片ZPL代码 |
|
|
|
*/ |
|
|
|
@ -418,10 +480,10 @@ public class ZplGenerator { |
|
|
|
if (data == null || data.isEmpty()) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return String.format("^FO%d,%d^GFA,%s", x, y, data); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成横线ZPL代码 |
|
|
|
*/ |
|
|
|
@ -431,52 +493,52 @@ public class ZplGenerator { |
|
|
|
BigDecimal width = element.getWidth() != null ? element.getWidth() : BigDecimal.valueOf(100); |
|
|
|
int elementX = element.getX() != null ? element.getX() : 0; |
|
|
|
int adjustedY = (int) Math.round(1200 - elementX) - width.intValue(); |
|
|
|
return String.format("^FO%d,%d^FWR^GB%d,%s,3,B^FS", |
|
|
|
x, adjustedY, |
|
|
|
element.getHeight() != null ? element.getHeight() : 3, |
|
|
|
return String.format("^FO%d,%d^FWR^GB%d,%s,3,B^FS", |
|
|
|
x, adjustedY, |
|
|
|
element.getHeight() != null ? element.getHeight() : 3, |
|
|
|
width.toString()); |
|
|
|
} else { |
|
|
|
return String.format("^FO%d,%d^GB%s,%d,3,B^FS", |
|
|
|
x, y, |
|
|
|
element.getWidth() != null ? element.getWidth().toString() : "100", |
|
|
|
return String.format("^FO%d,%d^GB%s,%d,3,B^FS", |
|
|
|
x, y, |
|
|
|
element.getWidth() != null ? element.getWidth().toString() : "100", |
|
|
|
element.getHeight() != null ? element.getHeight() : 3); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成竖线ZPL代码 |
|
|
|
*/ |
|
|
|
private String generateVerticalLineZPL(ReportLabelList element, int x, int y) { |
|
|
|
if ("landscape".equals(this.orientation)) { |
|
|
|
return String.format("^FO%d,%d^FWR^GB%d,%s,3,B^FS", |
|
|
|
x, y, |
|
|
|
element.getHeight() != null ? element.getHeight() : 100, |
|
|
|
return String.format("^FO%d,%d^FWR^GB%d,%s,3,B^FS", |
|
|
|
x, y, |
|
|
|
element.getHeight() != null ? element.getHeight() : 100, |
|
|
|
element.getWidth() != null ? element.getWidth().toString() : "3"); |
|
|
|
} else { |
|
|
|
return String.format("^FO%d,%d^GB1,%d,%s,B^FS", |
|
|
|
x, y, |
|
|
|
element.getHeight() != null ? element.getHeight() : 100, |
|
|
|
return String.format("^FO%d,%d^GB1,%d,%s,B^FS", |
|
|
|
x, y, |
|
|
|
element.getHeight() != null ? element.getHeight() : 100, |
|
|
|
element.getWidth() != null ? element.getWidth().toString() : "3"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成流水号ZPL代码 |
|
|
|
*/ |
|
|
|
private String generateSerialNumberZPL(ReportLabelList element, int x, int y) { |
|
|
|
StringBuilder zpl = new StringBuilder(); |
|
|
|
|
|
|
|
|
|
|
|
// 设置中文字体 |
|
|
|
Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; |
|
|
|
zpl.append("^CI28 ^CWJ,E:MSYH.TTF ^CFJ,").append(fontSize).append("\n"); |
|
|
|
|
|
|
|
|
|
|
|
// 基础文本 |
|
|
|
String data = element.getData() != null ? element.getData() : "流水号"; |
|
|
|
zpl.append("^FO").append(x).append(",").append(y).append("^FD").append(data).append("^FS"); |
|
|
|
|
|
|
|
|
|
|
|
return zpl.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* ZPL配置 |
|
|
|
*/ |
|
|
|
@ -486,4 +548,4 @@ public class ZplGenerator { |
|
|
|
private String barcodeOrientation; |
|
|
|
private String qrcodeOrientation; |
|
|
|
} |
|
|
|
} |
|
|
|
} |