Browse Source

越南文文本改为位图输出(^GFA),规避打印机对扩展拉丁字形支持不全的问题。

master
han\hanst 1 month ago
parent
commit
452ac28c37
  1. 221
      src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java

221
src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java

@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.google.zxing.*; import com.google.zxing.*;
@ -20,6 +21,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
/** /**
* ZPL代码生成器 * ZPL代码生成器
@ -28,6 +30,19 @@ import java.util.Map;
@Slf4j @Slf4j
public class ZplGenerator { public class ZplGenerator {
private static final String DEFAULT_FONT_FILE = "ARIAL UNICODE MS.TTF"; private static final String DEFAULT_FONT_FILE = "ARIAL UNICODE MS.TTF";
/**
* 越南文常用字符覆盖
* - 基础越南文字母Ă/ă Â/â Ê/ê Ô/ô Ơ/ơ Ư/ư Đ/đ
* - 扩展重音区U+1EA0~U+1EF9
*/
private static final Pattern VIETNAMESE_CHAR_PATTERN = Pattern.compile(
"[\\u0102\\u0103\\u00C2\\u00E2\\u00CA\\u00EA\\u00D4\\u00F4\\u01A0\\u01A1\\u01AF\\u01B0\\u0110\\u0111\\u1EA0-\\u1EF9]"
);
/**
* 位图文本Y轴微调dot
* 越南文位图与普通字体渲染相比存在轻微下移默认上移1dot做视觉对齐
*/
private static final int VIETNAMESE_BITMAP_Y_OFFSET = -5;
/** /**
* 兼容既有标签默认字体仍绑定J槽位 * 兼容既有标签默认字体仍绑定J槽位
* 历史模板大量依赖 default -> ^CWJ,E:* 的行为默认字体改为 ARIAL UNICODE MS 后仍保持 J 槽位 * 历史模板大量依赖 default -> ^CWJ,E:* 的行为默认字体改为 ARIAL UNICODE MS 后仍保持 J 槽位
@ -142,11 +157,9 @@ public class ZplGenerator {
private String generateTextZPL(ReportLabelList element, int x, int y) { private String generateTextZPL(ReportLabelList element, int x, int y) {
StringBuilder zpl = new StringBuilder(); StringBuilder zpl = new StringBuilder();
// 设置字体
Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30;
FontCommand fontCommand = generateFontCommand(element, fontSize);
zpl.append(fontCommand.getSetupCommand()).append("\n");
String fontSizeCommand = fontCommand.getFontSizeCommand();
String data = element.getData() != null ? element.getData() : "";
String alignmentParam = getTextAlignmentParam(element.getTextAlign());
// 如果有复选框 // 如果有复选框
if (Boolean.TRUE.equals(element.getIsChecked())) { if (Boolean.TRUE.equals(element.getIsChecked())) {
@ -155,10 +168,20 @@ public class ZplGenerator {
.append("^AJN,35,35^FD√^FS\n"); .append("^AJN,35,35^FD√^FS\n");
} }
String data = element.getData() != null ? element.getData() : "";
// 业务原因越南文在部分机型的TTF字形渲染存在缺字改走位图可规避固件字形缺失
if (containsVietnameseChars(data)) {
log.debug("检测到越南文文本,切换位图打印: {}", data);
String bitmapText = generateVietnameseTextBitmapZPL(element, x, y, fontSize, data, alignmentParam);
if (!bitmapText.isEmpty()) {
zpl.append(bitmapText);
}
return zpl.toString();
}
// 处理文本对齐
String alignmentParam = getTextAlignmentParam(element.getTextAlign());
// 非越南文维持原来的字体指令路径避免影响已有中文/英文模板
FontCommand fontCommand = generateFontCommand(element, fontSize);
zpl.append(fontCommand.getSetupCommand()).append("\n");
String fontSizeCommand = fontCommand.getFontSizeCommand();
// 基础文本 // 基础文本
if (Boolean.TRUE.equals(element.getNewline())) { if (Boolean.TRUE.equals(element.getNewline())) {
@ -252,8 +275,9 @@ public class ZplGenerator {
} }
} }
String setupCommand = "^CI28 ^CW" + resolvedFontLetter + ",E:" + resolvedFontFile
+ " ^CF" + resolvedFontLetter + "," + resolvedFontSize;
// 命令间不加空格避免字体文件名包含空格时把分隔空格误算进^CW参数
String setupCommand = "^CI28^CW" + resolvedFontLetter + ",E:" + resolvedFontFile
+ "^CF" + resolvedFontLetter + "," + resolvedFontSize;
String fontSizeCommand = "^CF" + resolvedFontLetter + "," + resolvedFontSize; String fontSizeCommand = "^CF" + resolvedFontLetter + "," + resolvedFontSize;
log.debug("字体命令解析: fontFamily={}, fontFile={}, fontLetter={}, useDefault={}, chineseFont={}", log.debug("字体命令解析: fontFamily={}, fontFile={}, fontLetter={}, useDefault={}, chineseFont={}",
@ -261,6 +285,177 @@ public class ZplGenerator {
return new FontCommand(setupCommand, fontSizeCommand, resolvedFontFile, resolvedFontLetter, resolvedFontSize); return new FontCommand(setupCommand, fontSizeCommand, resolvedFontFile, resolvedFontLetter, resolvedFontSize);
} }
private boolean containsVietnameseChars(String text) {
return text != null && !text.isEmpty() && VIETNAMESE_CHAR_PATTERN.matcher(text).find();
}
/**
* 越南文文本改为位图输出^GFA规避打印机对扩展拉丁字形支持不全的问题
*/
private String generateVietnameseTextBitmapZPL(ReportLabelList element, int x, int y, Integer fontSize,
String data, String alignmentParam) {
if (data == null || data.isEmpty()) {
return "";
}
int resolvedFontSize = fontSize != null && fontSize > 0 ? fontSize : 30;
String bitmapFontFamily = resolveBitmapFontFamily(element != null ? element.getFontFamily() : null);
int fontStyle = Boolean.TRUE.equals(element.getBold()) ? java.awt.Font.BOLD : java.awt.Font.PLAIN;
java.awt.Font renderFont = new java.awt.Font(bitmapFontFamily, fontStyle, resolvedFontSize);
BufferedImage measureCanvas = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
java.awt.Graphics2D measureGraphics = measureCanvas.createGraphics();
measureGraphics.setFont(renderFont);
java.awt.FontMetrics metrics = measureGraphics.getFontMetrics();
List<String> lines;
if (Boolean.TRUE.equals(element.getNewline())) {
int lineWidth = Math.max(1, Math.round((element.getLineWidth() != null ? element.getLineWidth() : 200) * this.dpi / 25.4f));
int lineRows = Math.max(1, element.getLineRows() != null ? element.getLineRows() : 2);
lines = wrapTextForBitmap(data, metrics, lineWidth, lineRows);
} else {
lines = Collections.singletonList(data);
}
int lineHeight = Math.max(1, metrics.getHeight());
int maxLineWidth = 0;
for (String line : lines) {
maxLineWidth = Math.max(maxLineWidth, metrics.stringWidth(line));
}
measureGraphics.dispose();
// 位图文本与普通^FD文本对齐策略
// - 上边距设为0避免^FO(y)后整体下移
// - 左右/下边距保留防止抗锯齿和下延笔画被裁切
int horizontalPadding = 3;
int topPadding = 0;
int bottomPadding = 2;
int configWidth = Math.max(1, Math.round((element.getLineWidth() != null ? element.getLineWidth() : 200) * this.dpi / 25.4f));
int imageWidth = Boolean.TRUE.equals(element.getNewline())
? Math.max(configWidth, maxLineWidth + horizontalPadding * 2)
: Math.max(1, maxLineWidth + horizontalPadding * 2);
int imageHeight = Math.max(1, lines.size() * lineHeight + topPadding + bottomPadding);
BufferedImage textImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_BYTE_BINARY);
java.awt.Graphics2D drawGraphics = textImage.createGraphics();
drawGraphics.setColor(java.awt.Color.WHITE);
drawGraphics.fillRect(0, 0, imageWidth, imageHeight);
drawGraphics.setColor(java.awt.Color.BLACK);
drawGraphics.setFont(renderFont);
drawGraphics.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
java.awt.FontMetrics drawMetrics = drawGraphics.getFontMetrics();
int baselineY = topPadding + drawMetrics.getAscent();
for (int i = 0; i < lines.size(); i++) {
String lineText = lines.get(i);
int linePixelWidth = drawMetrics.stringWidth(lineText);
int drawX = horizontalPadding;
if ("C".equals(alignmentParam)) {
drawX = Math.max(0, (imageWidth - linePixelWidth) / 2);
} else if ("R".equals(alignmentParam)) {
drawX = Math.max(0, imageWidth - linePixelWidth - horizontalPadding);
}
int drawY = baselineY + i * lineHeight;
drawGraphics.drawString(lineText, drawX, drawY);
if (Boolean.TRUE.equals(element.getFontUnderline())) {
int underlineY = drawY + 1;
drawGraphics.drawLine(drawX, underlineY, drawX + linePixelWidth, underlineY);
}
}
drawGraphics.dispose();
return convertBufferedImageToGfa(textImage, x, y + VIETNAMESE_BITMAP_Y_OFFSET);
}
private List<String> wrapTextForBitmap(String text, java.awt.FontMetrics fontMetrics, int maxWidth, int maxRows) {
List<String> lines = new ArrayList<>();
if (text == null || text.isEmpty()) {
lines.add("");
return lines;
}
StringBuilder currentLine = new StringBuilder();
for (char c : text.toCharArray()) {
if (c == '\n') {
lines.add(currentLine.toString());
if (lines.size() >= maxRows) {
return lines;
}
currentLine.setLength(0);
continue;
}
String candidate = currentLine.toString() + c;
if (fontMetrics.stringWidth(candidate) <= maxWidth || currentLine.length() == 0) {
currentLine.append(c);
} else {
lines.add(currentLine.toString());
if (lines.size() >= maxRows) {
return lines;
}
currentLine.setLength(0);
currentLine.append(c);
}
}
if (lines.size() < maxRows && currentLine.length() > 0) {
lines.add(currentLine.toString());
}
if (lines.isEmpty()) {
lines.add("");
}
return lines;
}
private String resolveBitmapFontFamily(String fontFamily) {
if (fontFamily == null || fontFamily.trim().isEmpty() || "default".equalsIgnoreCase(fontFamily.trim())) {
return "Arial Unicode MS";
}
String normalized = fontFamily.trim();
if (normalized.toUpperCase(Locale.ROOT).endsWith(".TTF")) {
normalized = normalized.substring(0, normalized.length() - 4);
}
return normalized;
}
private String convertBufferedImageToGfa(BufferedImage image, int x, int y) {
int width = image.getWidth();
int height = image.getHeight();
int widthBytes = (width + 7) / 8;
int totalBytes = widthBytes * height;
StringBuilder imageData = new StringBuilder(totalBytes * 2);
for (int yPos = 0; yPos < height; yPos++) {
int bit = 0;
int currentByte = 0;
for (int xPos = 0; xPos < width; 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));
}
}
return "^FO" + x + "," + y
+ "^GFA," + totalBytes + "," + totalBytes + "," + widthBytes + "," + imageData
+ "^FS";
}
/** /**
* 统一解析字体文件 * 统一解析字体文件
* 1) 优先走FontService * 1) 优先走FontService
@ -728,11 +923,17 @@ public class ZplGenerator {
StringBuilder zpl = new StringBuilder(); StringBuilder zpl = new StringBuilder();
Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30;
String data = element.getData() != null ? element.getData() : "流水号";
String alignmentParam = getTextAlignmentParam(element.getTextAlign());
if (containsVietnameseChars(data)) {
log.debug("检测到越南文流水号文本,切换位图打印: {}", data);
return generateVietnameseTextBitmapZPL(element, x, y, fontSize, data, alignmentParam);
}
FontCommand fontCommand = generateFontCommand(element, fontSize); FontCommand fontCommand = generateFontCommand(element, fontSize);
zpl.append(fontCommand.getSetupCommand()).append("\n"); zpl.append(fontCommand.getSetupCommand()).append("\n");
// 基础文本 // 基础文本
String data = element.getData() != null ? element.getData() : "流水号";
zpl.append("^FO").append(x).append(",").append(y).append("^FD").append(data).append("^FS"); zpl.append("^FO").append(x).append(",").append(y).append("^FD").append(data).append("^FS");
return zpl.toString(); return zpl.toString();

Loading…
Cancel
Save