| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- 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;
- /**
- * 流转卡渲染器(支持 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) {
- throw new RuntimeException("流转卡渲染失败: " + e.getMessage(), e);
- }
- }
- 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;
- }
- }
- }
|