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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> map = new java.util.HashMap<String, String>();
|
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");
|
|
}
|
}
|