package com.ruoyi.common.utils; import lombok.extern.slf4j.Slf4j; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.PNGTranscoder; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.interactive.form.PDPushButton; import org.apache.pdfbox.pdmodel.interactive.form.PDTextField; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.util.Base64; import java.util.Map; @Slf4j public class PdfTemplateUtil { /** * 填充 PDF 表单字段 * * @param templatePdf 模板 PDF 路径 * @param outputPdf 输出 PDF 路径 * @param params key=表单字段名, value=填充值 * @param fontPath 中文字体 ttf 路径 * @throws Exception */ public static void fillPdf(String templatePdf, String outputPdf, Map params, String fontPath) throws Exception { PDDocument document = PDDocument.load(new File(templatePdf)); PDAcroForm form = document.getDocumentCatalog().getAcroForm(); PDType0Font font = PDType0Font.load(document, new File(fontPath)); // 注册字体资源 PDResources resources = form.getDefaultResources(); if (resources == null) { resources = new PDResources(); form.setDefaultResources(resources); } COSName fontResName = resources.add(font); for (Map.Entry entry : params.entrySet()) { PDField field = form.getField(entry.getKey()); // 文字字段 if (field instanceof PDTextField) { PDTextField tf = (PDTextField) field; tf.setDefaultAppearance("/" + fontResName.getName() + " 10 Tf 0 0 0 rg"); tf.getCOSObject().setInt(COSName.Q, 1); // 水平居中 tf.setValue(entry.getValue()); } // 图片按钮字段 if (field instanceof PDPushButton) { PDPushButton button = (PDPushButton) field; String imgBase64 = entry.getValue(); if (imgBase64.contains(",")) { imgBase64 = imgBase64.split(",")[1]; // 去掉前缀 } byte[] imageBytes; if (entry.getValue().startsWith("data:image/svg+xml;base64,")) { // SVG 转 PNG byte[] svgBytes = Base64.getDecoder().decode(imgBase64); ByteArrayInputStream svgStream = new ByteArrayInputStream(svgBytes); PNGTranscoder transcoder = new PNGTranscoder(); // 输出到 ByteArray ByteArrayOutputStream pngOut = new ByteArrayOutputStream(); TranscoderInput input = new TranscoderInput(svgStream); TranscoderOutput output = new TranscoderOutput(pngOut); transcoder.transcode(input, output); pngOut.flush(); pngOut.close(); imageBytes = pngOut.toByteArray(); // BufferedImage 读取 BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageBytes)); // 旋转 90° int w = bufferedImage.getWidth(); int h = bufferedImage.getHeight(); BufferedImage rotated = new BufferedImage(h, w, bufferedImage.getType()); Graphics2D g2d = rotated.createGraphics(); g2d.translate(h / 2.0, w / 2.0); g2d.rotate(Math.toRadians(90)); g2d.translate(-w / 2.0, -h / 2.0); g2d.drawImage(bufferedImage, 0, 0, null); g2d.dispose(); // 转回字节数组 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(rotated, "png", baos); imageBytes = baos.toByteArray(); } else { // PNG/JPG 直接解码 imageBytes = Base64.getDecoder().decode(imgBase64); } // 创建 PDImageXObject 并绘制 PDImageXObject pdImage = PDImageXObject.createFromByteArray(document, imageBytes, "img"); PDAppearanceStream appearance = button.getWidgets().get(0).getNormalAppearanceStream(); PDPageContentStream cs = new PDPageContentStream(document, appearance); cs.drawImage(pdImage, 0, 0, appearance.getBBox().getWidth(), appearance.getBBox().getHeight()); cs.close(); } } form.refreshAppearances(); form.flatten(); document.save(outputPdf); document.close(); } public static void fillPdf(String templatePdf, OutputStream outputStream, Map params, String fontPath) throws Exception { PDDocument document = PDDocument.load(new File(templatePdf)); PDAcroForm form = document.getDocumentCatalog().getAcroForm(); PDType0Font font = PDType0Font.load(document, new File(fontPath)); // 注册字体资源 PDResources resources = form.getDefaultResources(); if (resources == null) { resources = new PDResources(); form.setDefaultResources(resources); } COSName fontResName = resources.add(font); for (Map.Entry entry : params.entrySet()) { PDField field = form.getField(entry.getKey()); // 文字字段 if (field instanceof PDTextField) { PDTextField tf = (PDTextField) field; tf.setDefaultAppearance("/" + fontResName.getName() + " 10 Tf 0 0 0 rg"); tf.getCOSObject().setInt(COSName.Q, 1); // 水平居中 tf.setValue(entry.getValue()); } // 图片按钮字段 if (field instanceof PDPushButton) { PDPushButton button = (PDPushButton) field; String imgBase64 = entry.getValue(); if (imgBase64.contains(",")) { imgBase64 = imgBase64.split(",")[1]; // 去掉前缀 } byte[] imageBytes; if (entry.getValue().startsWith("data:image/svg+xml;base64,")) { // SVG 转 PNG byte[] svgBytes = Base64.getDecoder().decode(imgBase64); ByteArrayInputStream svgStream = new ByteArrayInputStream(svgBytes); PNGTranscoder transcoder = new PNGTranscoder(); // 输出到 ByteArray ByteArrayOutputStream pngOut = new ByteArrayOutputStream(); TranscoderInput input = new TranscoderInput(svgStream); TranscoderOutput output = new TranscoderOutput(pngOut); transcoder.transcode(input, output); pngOut.flush(); pngOut.close(); imageBytes = pngOut.toByteArray(); // BufferedImage 读取 BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageBytes)); // 旋转 90° int w = bufferedImage.getWidth(); int h = bufferedImage.getHeight(); BufferedImage rotated = new BufferedImage(h, w, bufferedImage.getType()); Graphics2D g2d = rotated.createGraphics(); g2d.translate(h / 2.0, w / 2.0); g2d.rotate(Math.toRadians(90)); g2d.translate(-w / 2.0, -h / 2.0); g2d.drawImage(bufferedImage, 0, 0, null); g2d.dispose(); // 转回字节数组 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(rotated, "png", baos); imageBytes = baos.toByteArray(); } else { // PNG/JPG 直接解码 imageBytes = Base64.getDecoder().decode(imgBase64); } // 创建 PDImageXObject 并绘制 PDImageXObject pdImage = PDImageXObject.createFromByteArray(document, imageBytes, "img"); PDAppearanceStream appearance = button.getWidgets().get(0).getNormalAppearanceStream(); PDPageContentStream cs = new PDPageContentStream(document, appearance); cs.drawImage(pdImage, 0, 0, appearance.getBBox().getWidth(), appearance.getBBox().getHeight()); cs.close(); } } form.refreshAppearances(); form.flatten(); document.save(outputStream); document.close(); } public static void main(String[] args) throws Exception { String template = "D:/temp/zjsf.pdf"; String filled = "D:/temp/tes3.pdf"; String finalPdf = "D:/temp/commit_final.pdf"; // Java 8 写法:用 HashMap 初始化参数 Map map = new java.util.HashMap(); map.put("name", "张三"); map.put("idCard", "440***********1234"); map.put("date", "2025-11-27"); map.put("signImage", "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iNjY0LjM5MDYyNSIgaGVpZ2h0PSIzMjgiPjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9InJnYigwLDAsMCkiIGZpbGw9Im5vbmUiIGQ9Ik0gMTg1LjMgMjk2IEwgMTg1LjMgMjk2IEwgMTgzLjYgMjkwIEwgMTgwLjYgMjgyIEwgMTc3LjYgMjcyIEwgMTc0LjYgMjYwIEwgMTcxLjYgMjQxIEwgMTY3LjYgMjIzIEwgMTYzLjYgMjAyIEwgMTYwLjYgMTg0IEwgMTU4LjYgMTY2IEwgMTU1LjYgMTUwIEwgMTU0LjYgMTM3IEwgMTU0LjYgMTI5IEwgMTU0LjYgMTI0IEwgMTU0LjYgMTIyIEwgMTU1LjYgMTIxIEwgMTU3LjYgMTIxIEwgMTYwLjYgMTIyIEwgMTY5LjYgMTI5IEwgMTc3LjYgMTQwIEwgMTg5LjYgMTYxIEwgMTg5LjYgMTYxIi8+CjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9InJnYigwLDAsMCkiIGZpbGw9Im5vbmUiIGQ9Ik0gMjk2LjMgMzIzIEwgMjk2LjMgMzIzIEwgMjk2LjYgMzIyIEwgMjk2LjYgMzIwIEwgMjk2LjYgMzE2IEwgMjk1LjYgMzA5IEwgMjkzLjYgMzAzIEwgMjg4LjYgMjkzIEwgMjg0LjYgMjgyIEwgMjc5LjYgMjcwIEwgMjc2LjYgMjU4IEwgMjcyLjYgMjQ1IEwgMjY3LjYgMjM0IEwgMjY0LjYgMjI3IEwgMjYxLjYgMjIxIEwgMjU5LjYgMjE4IEwgMjU1LjYgMjE1IEwgMjUyLjYgMjE1IEwgMjQ4LjYgMjE1IEwgMjQxLjYgMjE1IEwgMjQxLjYgMjE1Ii8+CjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9InJnYigwLDAsMCkiIGZpbGw9Im5vbmUiIGQ9Ik0gMTcwLjMgMjQ1IEwgMTcwLjMgMjQ1IEwgMTc3LjYgMjQ3IEwgMTg1LjYgMjUxIEwgMTk1LjYgMjU1IEwgMjA5LjYgMjYwIEwgMjIxLjYgMjY2IEwgMjM3LjYgMjcwIEwgMjUxLjYgMjc1IEwgMjY0LjYgMjc4IEwgMjc1LjYgMjc4IEwgMjg0LjYgMjc4IEwgMjkxLjYgMjc4IEwgMjk2LjYgMjc2IEwgMzAwLjYgMjcyIEwgMzAyLjYgMjY5IEwgMzA0LjYgMjYzIEwgMzA1LjYgMjU2IEwgMzA1LjYgMjQ3IEwgMzA1LjYgMjM1IEwgMzAxLjYgMjE5IEwgMjk3LjYgMjA3IEwgMjkyLjYgMTkxIEwgMjg4LjYgMTc5IEwgMjgzLjYgMTcwIEwgMjgyLjYgMTY2IEwgMjgwLjYgMTY1IEwgMjgyLjYgMTY5IEwgMjg5LjYgMTc2IEwgMjk0LjYgMTg0IEwgMzAyLjYgMTk0IEwgMzA4LjYgMjAyIEwgMzE1LjYgMjExIEwgMzIyLjYgMjE5IEwgMzI3LjYgMjIzIEwgMzMzLjYgMjI3IEwgMzM4LjYgMjI5IEwgMzQxLjYgMjMxIEwgMzQzLjYgMjMxIEwgMzQ1LjYgMjMyIEwgMzQ2LjYgMjMyIEwgMzQ3LjYgMjMyIEwgMzQ4LjYgMjMxIEwgMzQ4LjYgMjMwIEwgMzQ4LjYgMjI5IEwgMzQ3LjYgMjI3IEwgMzQ2LjYgMjI2IEwgMzQzLjYgMjI1IEwgMzQxLjYgMjI1IEwgMzM4LjYgMjI2IEwgMzM1LjYgMjI4IEwgMzMyLjYgMjMwIEwgMzMwLjYgMjM1IEwgMzMwLjYgMjM1Ii8+CjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9InJnYigwLDAsMCkiIGZpbGw9Im5vbmUiIGQ9Ik0gMzE2LjMgMzExIEwgMzE2LjMgMzExIEwgMzE3LjYgMzEyIEwgMzE3LjYgMzEzIEwgMzE4LjYgMzEzIEwgMzE5LjYgMzEzIEwgMzIxLjYgMzEzIEwgMzIzLjYgMzExIEwgMzI4LjYgMzAzIEwgMzM0LjYgMjkzIEwgMzQwLjYgMjc1IEwgMzQ4LjYgMjUzIEwgMzU2LjYgMjI2IEwgMzYzLjYgMTkxIEwgMzcyLjYgMTU0IEwgMzc3LjYgMTE1IEwgMzc5LjYgNzkgTCAzODAuNiA0NiBMIDM4MC42IDE4IEwgMzgwLjYgLTQgTCAzODAuNiAtMjAgTCAzODAuNiAtMzMgTCAzODAuNiAtNDQgTCAzODAuNiAtNTMgTCAzNzkuNiAtNTkgTCAzNzkuNiAtNjIgTCAzNzkuNiAtNjMgTCAzNzguNiAtNjMgTCAzNzcuNiAtNjMgTCAzNzIuNiAtNTkgTCAzNjUuNiAtNTIgTCAzNTcuNiAtNDQgTCAzNTAuNiAtMzQgTCAzNTAuNiAtMzQiLz4KPHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZT0icmdiKDAsMCwwKSIgZmlsbD0ibm9uZSIgZD0iTSAyNDQuMyAxNDYgTCAyNDQuMyAxNDYgTCAyNDUuNiAxNDYgTCAyNDguNiAxNDQgTCAyNTIuNiAxNDEgTCAyNTguNiAxMzcgTCAyNjQuNiAxMzIgTCAyNzAuNiAxMjcgTCAyNzkuNiAxMTkgTCAyODYuNiAxMTIgTCAyOTUuNiAxMDMgTCAzMDMuNiA5NSBMIDMwOS42IDg3IEwgMzE1LjYgNzggTCAzMTguNiA3MSBMIDMyMS42IDYyIEwgMzIyLjYgNTYgTCAzMjIuNiA1MSBMIDMyMS42IDQ4IEwgMzE4LjYgNDYgTCAzMTIuNiA0MyBMIDMwNi42IDQxIEwgMjk2LjYgNDAgTCAyOTYuNiA0MCIvPgo8cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlPSJyZ2IoMCwwLDApIiBmaWxsPSJub25lIiBkPSJNIDI1Ni4zIDExMCBMIDI1Ni4zIDExMCBMIDI1OS42IDExNCBMIDI2NC42IDExOCBMIDI2OC42IDEyMSBMIDI3MS42IDEyMiBMIDI3My42IDEyNCBMIDI3NS42IDEyNSBMIDI3Ny42IDEyNSBMIDI3OC42IDEyNiBMIDI3OS42IDEyNiBMIDI3Ny42IDEyNiBMIDI3My42IDEyNiBMIDI3My42IDEyNiIvPjwvc3ZnPg=="); // 1. 填充文字字段 fillPdf(template, filled, map,"D:\\temp\\msyh.ttf"); } }