diff --git a/src/main/java/com/gaotao/modules/base/service/Impl/BaseServiceImpl.java b/src/main/java/com/gaotao/modules/base/service/Impl/BaseServiceImpl.java index 5d73685e..177c8ea9 100644 --- a/src/main/java/com/gaotao/modules/base/service/Impl/BaseServiceImpl.java +++ b/src/main/java/com/gaotao/modules/base/service/Impl/BaseServiceImpl.java @@ -242,9 +242,9 @@ public class BaseServiceImpl implements BaseService { if (isBlank(inData.getCustomerId())) { throw new XJException("客户编码不能为空!"); } - if (isBlank(inData.getAddressNo())) { + /* if (isBlank(inData.getAddressNo())) { throw new XJException("地址编码不能为空!"); - } + }*/ if (isBlank(inData.getLabelNo())) { throw new XJException("标签编号不能为空!"); } diff --git a/src/main/java/com/gaotao/modules/base/service/Impl/FontServiceImpl.java b/src/main/java/com/gaotao/modules/base/service/Impl/FontServiceImpl.java index b3a0f5f9..be7c5804 100644 --- a/src/main/java/com/gaotao/modules/base/service/Impl/FontServiceImpl.java +++ b/src/main/java/com/gaotao/modules/base/service/Impl/FontServiceImpl.java @@ -33,6 +33,12 @@ public class FontServiceImpl implements FontService { // 英文字体映射 FONT_FILE_MAPPING.put("Arial", "ARIAL.TTF"); + FONT_FILE_MAPPING.put("Arial Unicode MS", "ARIAL UNICODE MS.TTF"); + FONT_FILE_MAPPING.put("Arial Unicode", "ARIAL UNICODE MS.TTF"); + FONT_FILE_MAPPING.put("ARIAL UNICODE MS.TTF", "ARIAL UNICODE MS.TTF"); + FONT_FILE_MAPPING.put("ARIALUNI.TTF", "ARIAL UNICODE MS.TTF"); + FONT_FILE_MAPPING.put("default", "ARIAL UNICODE MS.TTF"); + FONT_FILE_MAPPING.put("Default", "ARIAL UNICODE MS.TTF"); FONT_FILE_MAPPING.put("Times New Roman", "TIMES.TTF"); FONT_FILE_MAPPING.put("Courier New", "COUR.TTF"); FONT_FILE_MAPPING.put("Helvetica", "HELV.TTF"); @@ -235,10 +241,11 @@ public class FontServiceImpl implements FontService { // 基本字体配置 String[][] basicFontConfig = { - {"默认字体", "default", "system", "系统默认字体"}, + {"默认字体", "default", "system", "ARIAL UNICODE MS.TTF"}, {"微软雅黑", "Microsoft YaHei", "chinese", "现代中文字体"}, {"宋体", "SimSun", "chinese", "传统中文字体"}, {"黑体", "SimHei", "chinese", "粗体中文字体"}, + {"Arial Unicode MS", "Arial Unicode MS", "english", "Unicode全字库字体"}, {"Arial", "Arial", "english", "无衬线英文字体"}, {"Times New Roman", "Times New Roman", "english", "衬线英文字体"}, {"Courier New", "Courier New", "monospace", "等宽英文字体"} diff --git a/src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java b/src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java index 2f8e02ea..d5c97c5e 100644 --- a/src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java +++ b/src/main/java/com/gaotao/modules/base/utils/ZplGenerator.java @@ -1,6 +1,5 @@ package com.gaotao.modules.base.utils; -import com.gaotao.modules.base.entity.LabelSettingData; import com.gaotao.modules.base.entity.ReportLabelList; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -17,7 +16,9 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -26,6 +27,18 @@ import java.util.Map; */ @Slf4j public class ZplGenerator { + private static final String DEFAULT_FONT_FILE = "ARIAL UNICODE MS.TTF"; + /** + * 兼容既有标签:默认字体仍绑定J槽位。 + * 历史模板大量依赖 default -> ^CWJ,E:* 的行为,默认字体改为 ARIAL UNICODE MS 后仍保持 J 槽位, + * 避免中文预览/打印丢失。 + */ + private static final String DEFAULT_FONT_LETTER = "J"; + /** + * 打印机字体字母映射(固定可识别字母) + * 兜底策略:未命中映射时回退默认字体,避免中文/特殊字符直接丢失。 + */ + private static final Map FONT_LETTER_MAP = createFontLetterMap(); private String orientation; private Integer dpi; @@ -41,7 +54,7 @@ public class ZplGenerator { } public ZplGenerator(String orientation, Integer dpi, CoordinateTransformer.CanvasSize canvasSize, - com.gaotao.modules.base.service.FontService fontService) { + com.gaotao.modules.base.service.FontService fontService) { this(orientation, dpi, canvasSize); this.fontService = fontService; } @@ -99,8 +112,8 @@ public class ZplGenerator { } CoordinateTransformer.ZplCoordinate coord = transformer.toZPL( - element.getX() != null ? element.getX() : 0, - element.getY() != null ? element.getY() : 0 + element.getX() != null ? element.getX() : 0, + element.getY() != null ? element.getY() : 0 ); switch (element.getType()) { @@ -131,14 +144,15 @@ public class ZplGenerator { // 设置字体 Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; - String fontCommand = generateFontCommand(element, fontSize); - zpl.append(fontCommand).append("\n"); + FontCommand fontCommand = generateFontCommand(element, fontSize); + zpl.append(fontCommand.getSetupCommand()).append("\n"); + String fontSizeCommand = fontCommand.getFontSizeCommand(); // 如果有复选框 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"); + .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() : ""; @@ -152,13 +166,13 @@ public class ZplGenerator { int lineWidth = Math.max(1, Math.round((element.getLineWidth() != null ? element.getLineWidth() : 200) * this.dpi / 25.4f)); int lineRows = element.getLineRows() != null ? element.getLineRows() : 2; zpl.append("^FO").append(x).append(",").append(y) - .append("^FB").append(lineWidth).append(",").append(lineRows) - .append(",0,").append(alignmentParam).append("^CFJ,").append(fontSize) - .append("^FD").append(data).append("^FS"); + .append("^FB").append(lineWidth).append(",").append(lineRows) + .append(",0,").append(alignmentParam).append(fontSizeCommand) + .append("^FD").append(data).append("^FS"); } else { // 单行文本 zpl.append("^FO").append(x).append(",").append(y) - .append("^FD").append(data).append("^FS"); + .append("^FD").append(data).append("^FS"); } // 加粗效果(通过偏移重复打印实现) @@ -167,24 +181,24 @@ public class ZplGenerator { int lineWidth = Math.max(1, Math.round((element.getLineWidth() != null ? element.getLineWidth() : 200) * this.dpi / 25.4f)); int lineRows = element.getLineRows() != null ? element.getLineRows() : 2; zpl.append("\n^FO").append(x + 1).append(",").append(y) - .append("^FB").append(lineWidth).append(",").append(lineRows) - .append(",0,").append(alignmentParam).append("^CFJ,").append(fontSize) - .append("^FD").append(data).append("^FS"); + .append("^FB").append(lineWidth).append(",").append(lineRows) + .append(",0,").append(alignmentParam).append(fontSizeCommand) + .append("^FD").append(data).append("^FS"); zpl.append("\n^FO").append(x).append(",").append(y + 1) - .append("^FB").append(lineWidth).append(",").append(lineRows) - .append(",0,").append(alignmentParam).append("^CFJ,").append(fontSize) - .append("^FD").append(data).append("^FS"); + .append("^FB").append(lineWidth).append(",").append(lineRows) + .append(",0,").append(alignmentParam).append(fontSizeCommand) + .append("^FD").append(data).append("^FS"); zpl.append("\n^FO").append(x + 1).append(",").append(y + 1) - .append("^FB").append(lineWidth).append(",").append(lineRows) - .append(",0,").append(alignmentParam).append("^CFJ,").append(fontSize) - .append("^FD").append(data).append("^FS"); + .append("^FB").append(lineWidth).append(",").append(lineRows) + .append(",0,").append(alignmentParam).append(fontSizeCommand) + .append("^FD").append(data).append("^FS"); } else { zpl.append("\n^FO").append(x + 1).append(",").append(y) - .append("^FD").append(data).append("^FS"); + .append("^FD").append(data).append("^FS"); zpl.append("\n^FO").append(x).append(",").append(y + 1) - .append("^FD").append(data).append("^FS"); + .append("^FD").append(data).append("^FS"); zpl.append("\n^FO").append(x + 1).append(",").append(y + 1) - .append("^FD").append(data).append("^FS"); + .append("^FD").append(data).append("^FS"); } } @@ -193,7 +207,7 @@ public class ZplGenerator { int textWidth = estimateTextWidth(data, fontSize); int underlineY = y + fontSize + 2; zpl.append("\n^FO").append(x).append(",").append(underlineY) - .append("^GB").append(textWidth).append(",2,2,B^FS"); + .append("^GB").append(textWidth).append(",2,2,B^FS"); } return zpl.toString(); @@ -202,17 +216,150 @@ public class ZplGenerator { /** * 生成字体命令 */ - private String generateFontCommand(ReportLabelList element, Integer fontSize) { - StringBuilder fontCmd = new StringBuilder(); + private FontCommand generateFontCommand(ReportLabelList element, Integer fontSize) { + int resolvedFontSize = fontSize != null && fontSize > 0 ? fontSize : 30; + String fontFamily = element != null ? element.getFontFamily() : null; + String resolvedFontFile = DEFAULT_FONT_FILE; + String resolvedFontLetter = DEFAULT_FONT_LETTER; + boolean useDefaultFont = fontFamily == null + || fontFamily.trim().isEmpty() + || "default".equalsIgnoreCase(fontFamily.trim()); + boolean chineseFontFamily = isChineseFontFamily(fontFamily); + + if (!useDefaultFont) { + String resolvedByConfig = resolveFontFileByFamily(fontFamily); + if (resolvedByConfig != null) { + resolvedFontFile = resolvedByConfig; + if (chineseFontFamily) { + // 与前端预览保持一致:中文字体统一走J槽位,避免Labelary对B/C/D等槽位兼容差异导致中文丢失。 + resolvedFontLetter = DEFAULT_FONT_LETTER; + } else { + String mappedLetter = FONT_LETTER_MAP.get(resolvedFontFile); + if (mappedLetter != null) { + resolvedFontLetter = mappedLetter; + } else { + // 保守兜底:若字母映射缺失,强制使用默认槽位,避免^CF使用无效字母。 + log.warn("字体文件未维护字母映射,回退默认字体: {}", resolvedFontFile); + resolvedFontFile = DEFAULT_FONT_FILE; + resolvedFontLetter = DEFAULT_FONT_LETTER; + } + } + } else { + // 打印机字体可用性兜底:未识别字体统一回退默认字体,避免输出空白。 + log.warn("字体族未匹配到可用打印机字体,回退默认字体: {}", fontFamily); + resolvedFontFile = DEFAULT_FONT_FILE; + resolvedFontLetter = DEFAULT_FONT_LETTER; + } + } - // 设置字符编码 - fontCmd.append("^CI28"); + String setupCommand = "^CI28 ^CW" + resolvedFontLetter + ",E:" + resolvedFontFile + + " ^CF" + resolvedFontLetter + "," + resolvedFontSize; + String fontSizeCommand = "^CF" + resolvedFontLetter + "," + resolvedFontSize; - // 字体与字母映射,默认ARIAL为A + log.debug("字体命令解析: fontFamily={}, fontFile={}, fontLetter={}, useDefault={}, chineseFont={}", + fontFamily, resolvedFontFile, resolvedFontLetter, useDefaultFont, chineseFontFamily); + return new FontCommand(setupCommand, fontSizeCommand, resolvedFontFile, resolvedFontLetter, resolvedFontSize); + } + + /** + * 统一解析字体文件: + * 1) 优先走FontService; + * 2) 再走静态映射; + * 3) 修正历史扩展名差异(SIMSUN.TTC -> SIMSUN.TTF)。 + */ + private String resolveFontFileByFamily(String fontFamily) { + String trimmedFontFamily = fontFamily == null ? null : fontFamily.trim(); + if (trimmedFontFamily == null || trimmedFontFamily.isEmpty()) { + return null; + } + + String fontFile = null; + if (fontService != null) { + fontFile = fontService.getFontFilePath(trimmedFontFamily); + if (fontFile != null) { + log.debug("通过FontService获取字体文件: {} -> {}", trimmedFontFamily, fontFile); + } + } + if (fontFile == null) { + fontFile = mapFontFamilyToFile(trimmedFontFamily); + if (fontFile != null) { + log.debug("通过静态映射获取字体文件: {} -> {}", trimmedFontFamily, fontFile); + } + } + return normalizeFontFile(fontFile); + } + + /** + * 字体文件标准化,避免扩展名或大小写差异导致映射失效。 + */ + private String normalizeFontFile(String fontFile) { + if (fontFile == null) { + return null; + } + String normalized = fontFile.trim().toUpperCase(Locale.ROOT); + if (normalized.isEmpty()) { + return null; + } + // 修正宋体历史配置扩展名:统一按项目使用的TTF处理。 + if ("SIMSUN.TTC".equals(normalized)) { + return "SIMSUN.TTF"; + } + // 兼容历史配置中旧字体文件名:统一映射到打印机当前实际字体文件。 + if ("ARIALUNI.TTF".equals(normalized)) { + return "ARIAL UNICODE MS.TTF"; + } + return normalized; + } + + /** + * 中文字体识别(与前端字体处理策略保持一致) + */ + private boolean isChineseFontFamily(String fontFamily) { + if (fontFamily == null) { + return false; + } + String normalized = fontFamily.trim(); + if (normalized.isEmpty()) { + return false; + } + switch (normalized) { + case "Microsoft YaHei": + case "微软雅黑": + case "SimSun": + case "宋体": + case "SimHei": + case "黑体": + case "KaiTi": + case "楷体": + case "FangSong": + case "仿宋": + case "Microsoft JhengHei": + case "微軟正黑體": + case "PMingLiU": + case "DFKai-SB": + case "標楷體": + case "华文宋体": + case "华文仿宋": + case "新細明體": + case "新细明体": + case "华文楷体": + case "新宋体": + return true; + default: + return false; + } + } + + /** + * 创建字体字母映射表 + */ + private static Map createFontLetterMap() { Map fontLetterMap = new HashMap<>(); + fontLetterMap.put("ARIAL UNICODE MS.TTF", "J"); + fontLetterMap.put("ARIALUNI.TTF", "J"); fontLetterMap.put("ARIAL.TTF", "A"); fontLetterMap.put("MSYH.TTF", "B"); - fontLetterMap.put("SIMSUN.TTC", "C"); + fontLetterMap.put("SIMSUN.TTF", "C"); fontLetterMap.put("SIMHEI.TTF", "D"); fontLetterMap.put("KAITI.TTF", "E"); fontLetterMap.put("SIMFANG.TTF", "F"); @@ -229,42 +376,7 @@ public class ZplGenerator { fontLetterMap.put("PALA.TTF", "Q"); fontLetterMap.put("GARA.TTF", "R"); fontLetterMap.put("BOOKOS.TTF", "S"); - - String fontFamily = element.getFontFamily(); - String fontFile = null; - String fontLetter = "J"; // 默认A - - 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) { - fontLetter = fontLetterMap.getOrDefault(fontFile, "A"); - fontCmd.append(" ^CW").append(fontLetter).append(",E:").append(fontFile); - } else { - fontCmd.append(" ^CWA,E:ARIAL.TTF"); - log.warn("未找到字体文件映射,使用默认字体: {}", fontFamily); - } - } else { - fontCmd.append(" ^CWJ,E:ARIAL.TTF"); - } - - // 设置字体大小,中文字体用J,其它用对应字母 - if (fontFile != null && (fontFile.equals("MSYH.TTF") || fontFile.equals("SIMSUN.TTC") || fontFile.equals("SIMHEI.TTF") - || fontFile.equals("KAITI.TTF") || fontFile.equals("SIMFANG.TTF"))) { - fontCmd.append(" ^CFJ,").append(fontSize); - } else { - fontCmd.append(" ^CF").append(fontLetter).append(",").append(fontSize); - } - - return fontCmd.toString(); + return Collections.unmodifiableMap(fontLetterMap); } /** @@ -291,6 +403,9 @@ public class ZplGenerator { return "SIMFANG.TTF"; case "Arial": return "ARIAL.TTF"; + case "Arial Unicode MS": + case "Arial Unicode": + return "ARIAL UNICODE MS.TTF"; case "Times New Roman": return "TIMES.TTF"; case "Courier New": @@ -415,7 +530,7 @@ public class ZplGenerator { String data = element.getData() != null ? element.getData() : ""; return String.format("^FO%d,%d^BY%d%s,%d%s^FD%s^FS", - x, y, width.setScale(3, RoundingMode.HALF_UP).intValue(), zplCommand, height, additionalParams, data); + x, y, width.setScale(3, RoundingMode.HALF_UP).intValue(), zplCommand, height, additionalParams, data); } /** @@ -443,9 +558,9 @@ public class ZplGenerator { // 计算文本位置:二维码下方,居中对齐 int textY = y + sizeInDots + 10; // 二维码下方留10个点的间距 int fontSize = Math.max(18, Math.min(25, sizeInDots / 4)); // 根据二维码大小调整字体大小 + FontCommand fontCommand = generateFontCommand(element, fontSize); - // 设置中文字体支持 - zpl.append("\n^CI28 ^CWJ,E:ARIAL.TTF ^CFJ,").append(fontSize); + zpl.append("\n").append(fontCommand.getSetupCommand()); // 计算文本宽度以实现居中对齐 int estimatedTextWidth = estimateTextWidth(data, fontSize); @@ -457,13 +572,13 @@ public class ZplGenerator { int lineWidth = Math.max(sizeInDots, estimatedTextWidth); int lineRows = Math.min(3, (data.length() + 19) / 20); // 最多3行 zpl.append("\n^FO").append(x).append(",").append(textY) - .append("^FB").append(lineWidth).append(",").append(lineRows) - .append(",0,C^CFJ,").append(fontSize) - .append("^FD").append(data).append("^FS"); + .append("^FB").append(lineWidth).append(",").append(lineRows) + .append(",0,C").append(fontCommand.getFontSizeCommand()) + .append("^FD").append(data).append("^FS"); } else { // 单行文本 zpl.append("\n^FO").append(textX).append(",").append(textY) - .append("^FD").append(data).append("^FS"); + .append("^FD").append(data).append("^FS"); } } @@ -573,14 +688,14 @@ public class ZplGenerator { 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, - width.toString()); + 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", - element.getHeight() != null ? element.getHeight() : 3); + x, y, + element.getWidth() != null ? element.getWidth().toString() : "100", + element.getHeight() != null ? element.getHeight() : 3); } } @@ -590,14 +705,14 @@ public class ZplGenerator { 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, - element.getWidth() != null ? element.getWidth().toString() : "3"); + 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, - element.getWidth() != null ? element.getWidth().toString() : "3"); + x, y, + element.getHeight() != null ? element.getHeight() : 100, + element.getWidth() != null ? element.getWidth().toString() : "3"); } } @@ -612,9 +727,9 @@ public class ZplGenerator { StringBuilder zpl = new StringBuilder(); - // 设置中文字体 Integer fontSize = element.getFontSize() != null ? element.getFontSize() : 30; - zpl.append("^CI28 ^CWJ,E:ARIAL.TTF ^CFJ,").append(fontSize).append("\n"); + FontCommand fontCommand = generateFontCommand(element, fontSize); + zpl.append(fontCommand.getSetupCommand()).append("\n"); // 基础文本 String data = element.getData() != null ? element.getData() : "流水号"; @@ -632,4 +747,13 @@ public class ZplGenerator { private String barcodeOrientation; private String qrcodeOrientation; } + + @Data + private static class FontCommand { + private final String setupCommand; + private final String fontSizeCommand; + private final String fontFile; + private final String fontLetter; + private final Integer fontSize; + } }