wangxichen hai 1 semana
pai
achega
d138bf71b4

BIN=BIN
config/liuzhuanka_template.pdf


BIN=BIN
lib/commons-logging-1.2.jar


BIN=BIN
lib/core-3.5.1.jar


BIN=BIN
lib/fontbox-2.0.30.jar


BIN=BIN
lib/pdfbox-2.0.30.jar


+ 15 - 0
sources.txt

@@ -0,0 +1,15 @@
+src/com/mes/print/config/PrinterConfig.java
+src/com/mes/print/impl/HpPrinter.java
+src/com/mes/print/impl/NonePrinter.java
+src/com/mes/print/impl/PrinterImpl.java
+src/com/mes/print/impl/TscPrinter.java
+src/com/mes/print/model/FlowCardData.java
+src/com/mes/print/model/LabelData.java
+src/com/mes/print/Printer.java
+src/com/mes/print/PrinterException.java
+src/com/mes/print/PrinterFactory.java
+src/com/mes/print/template/FlowCardRenderer.java
+src/com/mes/print/template/LabelTspl.java
+src/com/mes/print/TestMain.java
+src/com/mes/print/ui/PrinterSettingDialog.java
+src/com/mes/print/util/PrinterUtil.java

+ 7 - 2
src/com/mes/print/config/PrinterConfig.java

@@ -41,11 +41,16 @@ public class PrinterConfig {
 		}
 		// 其次工作目录
 		File f = new File(CONFIG_FILE);
+		System.out.println("[PrinterConfig] 当前工作目录: " + new File(".").getAbsolutePath());
+		System.out.println("[PrinterConfig] 查找配置文件: " + f.getAbsolutePath() + " (exists=" + f.exists() + ")");
 		if (f.exists()) return new FileInputStream(f);
 		// 最后 classpath
 		InputStream in = PrinterConfig.class.getClassLoader().getResourceAsStream("print.properties");
-		if (in != null) return in;
-		throw new PrinterException("找不到配置文件: " + CONFIG_FILE);
+		if (in != null) {
+			System.out.println("[PrinterConfig] 从 classpath 加载配置");
+			return in;
+		}
+		throw new PrinterException("找不到配置文件: " + CONFIG_FILE + ",工作目录=" + new File(".").getAbsolutePath());
 	}
 
 	public static String get(String key) {

+ 202 - 19
src/com/mes/print/template/FlowCardRenderer.java

@@ -1,54 +1,237 @@
 package com.mes.print.template;
 
+import java.awt.Color;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.GraphicsEnvironment;
 import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
 import java.awt.print.PageFormat;
 import java.awt.print.Printable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
 
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.ImageType;
+import org.apache.pdfbox.rendering.PDFRenderer;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 import com.mes.print.model.FlowCardData;
 
 /**
- * 流转卡渲染器(Java2D,A4)
- * 这是最小可用版本,后续按真实模板调整字段位置/表格样式
+ * 流转卡渲染器(支持 PDF 模板 + 二维码
+ * 复用 OP40 的 LaserCardPrintUtil 渲染逻辑
  */
 public class FlowCardRenderer {
 
+	// PDF 模板尺寸(A5 纵向 148x210mm)
+	private static final float PDF_PAGE_W_PT = 419.5f;
+	private static final float PDF_PAGE_H_PT = 595.25f;
+
+	// 三个动态元素在 PDF 坐标系里的位置(pt,左上原点)
+	private static final float QR_X = 194.5f;
+	private static final float QR_Y = 65.4f;
+	private static final float QR_W = 35.7f;
+	private static final float QR_H = 34.6f;
+
+	// 文字 baseline(Graphics2D.drawString 的 y 参数)
+	private static final float STEEL_TEXT_X = 291.5f;
+	private static final float STEEL_TEXT_Y = 85.5f;
+	private static final float SN_TEXT_X = 291.5f;
+	private static final float SN_TEXT_Y = 96.4f;
+
+	private static final int TEXT_FONT_SIZE = 8;
+
+	// 中文字体候选,按顺序找到系统里第一个可用的
+	private static final String[] FONT_CANDIDATES = {"SimSun", "宋体", "Microsoft YaHei", "微软雅黑", Font.SANS_SERIF};
+
+	// 打印用的底图 DPI;越高越清晰但内存/耗时越大,300 是常规打印标准
+	private static final float PRINT_TEMPLATE_DPI = 300f;
+
+	// 模板 PDF 缓存
+	private static volatile PDDocument templateDoc;
+	private static volatile PDFRenderer templateRenderer;
+	private static volatile BufferedImage templateImage;
+	private static final Object LOCK = new Object();
+
 	public static Printable render(final FlowCardData data) {
+		try {
+			BufferedImage bgImage = getTemplateImage();
+			BufferedImage qr = generateQrCode(data.getSn(), 600);
+
+			return new Printable() {
+				@Override
+				public int print(Graphics g, PageFormat pf, int idx) {
+					if (idx > 0) return NO_SUCH_PAGE;
+					Graphics2D g2 = (Graphics2D) g;
+
+					g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+					g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+					g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+
+					// 把 PDF 页面尺寸(pt)等比缩放到打印区域,居中
+					double sx = pf.getImageableWidth() / PDF_PAGE_W_PT;
+					double sy = pf.getImageableHeight() / PDF_PAGE_H_PT;
+					double scale = Math.min(sx, sy);
+					double tx = pf.getImageableX() + (pf.getImageableWidth() - PDF_PAGE_W_PT * scale) / 2.0;
+					double ty = pf.getImageableY() + (pf.getImageableHeight() - PDF_PAGE_H_PT * scale) / 2.0;
+
+					g2.translate(tx, ty);
+					g2.scale(scale, scale);
+
+					// 底图 BufferedImage 是 A5 @ 300dpi ≈ 1747x2480 像素,drawImage 到 pt 坐标 419.5x595.25
+					g2.drawImage(bgImage, 0, 0, (int) Math.ceil(PDF_PAGE_W_PT), (int) Math.ceil(PDF_PAGE_H_PT), null);
+
+					drawDynamic(g2, qr, data);
+					return PAGE_EXISTS;
+				}
+			};
+		} catch (Exception e) {
+			e.printStackTrace();
+			// 降级:如果 PDF 模板加载失败,用简单文本打印
+			return renderSimple(data);
+		}
+	}
+
+	/** 降级方案:纯文本流转卡 */
+	private static Printable renderSimple(final FlowCardData data) {
 		return new Printable() {
 			@Override
 			public int print(Graphics g, PageFormat pf, int idx) {
 				if (idx > 0) return NO_SUCH_PAGE;
 				Graphics2D g2 = (Graphics2D) g;
-				g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
-						RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+				g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 				g2.translate(pf.getImageableX(), pf.getImageableY());
-				draw(g2, data);
+				drawSimple(g2, data);
 				return PAGE_EXISTS;
 			}
 		};
 	}
 
-	private static void draw(Graphics2D g, FlowCardData d) {
-		// 标题
+	private static void drawSimple(Graphics2D g, FlowCardData d) {
 		g.setFont(new Font("宋体", Font.BOLD, 22));
 		g.drawString("生产流转卡", 220, 40);
-
-		// 分隔线
 		g.drawLine(30, 60, 550, 60);
-
-		// 内容
 		g.setFont(new Font("宋体", Font.PLAIN, 14));
-		int y = 100;
-		int step = 32;
+		int y = 100, step = 32;
 		g.drawString("产品编号: " + safe(d.getProduct()), 50, y); y += step;
-		g.drawString("序列号 : " + safe(d.getSn()),      50, y); y += step;
-		g.drawString("工序号 : " + safe(d.getOpNo()),    50, y); y += step;
-		g.drawString("批次  : " + safe(d.getBatch()),   50, y); y += step;
-		g.drawString("日期  : " + safe(d.getDate()),    50, y); y += step;
-		g.drawString("操作员 : " + safe(d.getOperator()),50, y); y += step;
-		g.drawString("备注  : " + safe(d.getRemark()),  50, y);
+		g.drawString("序列号 : " + safe(d.getSn()), 50, y); y += step;
+		g.drawString("钢印码 : " + safe(d.getSteelSn()), 50, y); y += step;
+		g.drawString("工序号 : " + safe(d.getOpNo()), 50, y); y += step;
+		g.drawString("批次  : " + safe(d.getBatch()), 50, y); y += step;
+		g.drawString("日期  : " + safe(d.getDate()), 50, y); y += step;
+		g.drawString("操作员 : " + safe(d.getOperator()), 50, y); y += step;
+		g.drawString("备注  : " + safe(d.getRemark()), 50, y);
+	}
+
+	private static void drawDynamic(Graphics2D g2, BufferedImage qr, FlowCardData d) {
+		// 白底盖一下原模板上的二维码占位图
+		g2.setColor(Color.WHITE);
+		g2.fill(new java.awt.geom.Rectangle2D.Float(QR_X, QR_Y, QR_W, QR_H));
+		if (qr != null) {
+			g2.drawImage(qr, (int) QR_X, (int) QR_Y, (int) QR_W, (int) QR_H, null);
+		}
+
+		g2.setColor(Color.BLACK);
+		g2.setFont(pickChineseFont(TEXT_FONT_SIZE));
+		String steelSn = d.getSteelSn();
+		String customerSn = d.getSn();
+		if (steelSn != null) {
+			g2.drawString(steelSn, STEEL_TEXT_X, STEEL_TEXT_Y);
+		}
+		if (customerSn != null) {
+			g2.drawString(customerSn, SN_TEXT_X, SN_TEXT_Y);
+		}
+	}
+
+	private static BufferedImage getTemplateImage() throws IOException {
+		if (templateImage != null) return templateImage;
+		synchronized (LOCK) {
+			if (templateImage == null) {
+				PDFRenderer r = getRenderer();
+				templateImage = r.renderImageWithDPI(0, PRINT_TEMPLATE_DPI, ImageType.RGB);
+				System.out.println("[流转卡] 底图已预渲染 " + templateImage.getWidth() + "x" + templateImage.getHeight()
+						+ " @" + PRINT_TEMPLATE_DPI + "dpi");
+			}
+		}
+		return templateImage;
+	}
+
+	private static PDFRenderer getRenderer() throws IOException {
+		if (templateRenderer != null) return templateRenderer;
+		synchronized (LOCK) {
+			if (templateRenderer == null) {
+				String path = "config/liuzhuanka_template.pdf";
+				File pdfFile = extractToTemp(path);
+				PDDocument doc = PDDocument.load(pdfFile);
+				templateDoc = doc;
+				templateRenderer = new PDFRenderer(doc);
+				System.out.println("[流转卡] 已加载模板:" + pdfFile.getAbsolutePath());
+			}
+		}
+		return templateRenderer;
+	}
+
+	private static File extractToTemp(String cpPath) throws IOException {
+		File direct = new File(cpPath);
+		if (direct.isFile()) {
+			return direct;
+		}
+		try (InputStream is = ClassLoader.getSystemResourceAsStream(cpPath)) {
+			if (is == null) {
+				throw new IOException("找不到流转卡模板:" + cpPath);
+			}
+			Path tmp = Files.createTempFile("liuzhuanka_template_", ".pdf");
+			tmp.toFile().deleteOnExit();
+			Files.copy(is, tmp, StandardCopyOption.REPLACE_EXISTING);
+			return tmp.toFile();
+		}
+	}
+
+	private static Font pickChineseFont(int size) {
+		String[] available = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+		for (String name : FONT_CANDIDATES) {
+			for (String a : available) {
+				if (a.equalsIgnoreCase(name)) {
+					return new Font(name, Font.BOLD, size);
+				}
+			}
+		}
+		return new Font(Font.SANS_SERIF, Font.BOLD, size);
+	}
+
+	private static BufferedImage generateQrCode(String content, int pixelSize) {
+		if (content == null || content.isEmpty()) return null;
+		try {
+			Map<EncodeHintType, Object> hints = new HashMap<>();
+			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+			hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+			hints.put(EncodeHintType.MARGIN, 0);
+
+			BitMatrix bm = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, pixelSize, pixelSize, hints);
+			BufferedImage img = new BufferedImage(pixelSize, pixelSize, BufferedImage.TYPE_INT_RGB);
+			for (int x = 0; x < pixelSize; x++) {
+				for (int y = 0; y < pixelSize; y++) {
+					img.setRGB(x, y, bm.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
+				}
+			}
+			return img;
+		} catch (WriterException e) {
+			e.printStackTrace();
+			return null;
+		}
 	}
 
 	private static String safe(String s) {