前言
在有些场景下我们可能需要根据指定的模板来生成 PDF,比如说合同、收据、发票等等。因为 PDF 是不可编辑的,所以用代码直接对 PDF 文件进行修改是很不方便的,这里我是通过 itext
和 Adobe Acrobat
来实现的,以下就是具体实现方法。
一、准备模板
Adobe Acrobat
是由 Adobe
公司开发的一款 PDF
(Portable Document Format,便携式文档格式)编辑软件。借助它,你可以以 PDF 格式制作和保存文档 ,以便于浏览和打印,或使用更高级的功能。
说白一点就是 Adobe Acrobat 可以让你的 PDF 文件编程可编辑文件,PDF 文件可编辑的话,使用代码去修改就会方便很多。
adobe 中文官网:https://www.adobe.com/cn/
Adobe Acrobat 中文官网:https://www.adobe.com/cn/acrobat.html
如果你之前没有使用过这个软件,可以在上面我提供的官网里面去下载
下载完,打开该软件大概是这个样子的
2、创建模板
- 使用Adobe编辑打开
- 点击准备表单,会自动帮你创建好表单,要是没有这个按钮,就去 更多工具 里添加
- 此按钮可以用户自定义添加表单
3、引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<!--字体集-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox-tools</artifactId>
<version>2.0.27</version>
</dependency>
</dependencies>
4、编写代码
package com.hnys.zhct.common.core.utils;
import com.hnys.zhct.common.core.domain.ProblemSolutionVO;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 生成pdf的工具类
*
* @author Simon
*/
@Slf4j
public class GeneratedPdfUtils {
public static void main(String[] args) {
try {
// 示例数据
Map<String, Object> data = new HashMap<>();
data.put("name", "lily");
data.put("address", "麓谷");
data.put("city", "changsha");
data.put("state", "CN");
data.put("zip", "123");
// 例如:黑体字体
String fontPath = "D:\\testImg\\myf\\simhei.ttf";
// PDF文件路径
String templatePath = "D:\\testImg\\template\\template.pdf", pdfPath = "D:\\output.pdf";
// 水印内容
String watermarkText = "国铁集团";
// 生成PDF
generatePdf(templatePath, pdfPath, data, fontPath, watermarkText);
log.info("PDF生成成功!");
// 输出目录
String outputDir = "D:\\output_images";
// 分辨率(DPI)通常300 DPI适用于高质量的图片,值越高质量越好,文件大小越大
int dpi = 300;
// 转换为PNG图片
String imagePath = convertPdfToSingleLongPng(pdfPath, outputDir, dpi);
// 打印生成的图片路径
System.out.println("生成的图片: " + imagePath);
} catch (IOException | DocumentException e) {
log.error("PDF生成失败!");
e.printStackTrace();
}
}
/**
* 通过PDF模板生成PDF文件
*
* @param templatePath PDF模板路径
* @param outputPath 生成的PDF文件路径
* @param data 要填充的数据,键为表单字段名,值为要填充的内容
* @param fontPath 字体
* @param watermarkText 水印文字
* @throws IOException 文件流异常
* @throws DocumentException 文件读取异常
*/
@SuppressWarnings("unchecked")
public static void generatePdf(String templatePath, String outputPath, Map<String,
Object> data, String fontPath, String watermarkText)
throws IOException, DocumentException {
// 读取PDF模板
PdfReader reader = new PdfReader(templatePath);
// 创建PdfStamper对象
PdfStamper stamper = new PdfStamper(reader, Files.newOutputStream(Paths.get(outputPath)));
// 获取表单字段
AcroFields form = stamper.getAcroFields();
// 加载字体
BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
// 设置表单字段的字体
for (String fieldName : form.getFields().keySet()) {
form.setFieldProperty(fieldName, "textfont", baseFont, null);
}
// 填充固定表单字段
for (Map.Entry<String, Object> entry : data.entrySet()) {
String fieldName = entry.getKey();
Object value = entry.getValue();
if (!(value instanceof List)) {
form.setField(fieldName, value.toString());
}
}
// 处理动态的List数据
List<ProblemSolutionVO> problemSolutions = (List<ProblemSolutionVO>) data.get("problemList");
if (CollectionUtils.isNotEmpty(problemSolutions)) {
PdfContentByte content = stamper.getOverContent(1);
float startY = 700; // 起始纵坐标
float margin = 20; // 间距
for (ProblemSolutionVO ps : problemSolutions) {
// 添加问题描述段落;如果存在其他string类型的字段,copy以下这段代码即可,需要多次执行main方法来调整坐标位置
content.beginText();
content.setFontAndSize(baseFont, 12);
content.setTextMatrix(50, startY);
content.showText(ps.getProblemDescription());
content.endText();
startY -= 20;
// 添加图片
Image image = Image.getInstance(ps.getImagePath());
float width = 300;
float height = width * image.getHeight() / image.getWidth();
image.scaleToFit(width, height);
image.setAbsolutePosition(50, startY - height);
content.addImage(image);
startY -= height + margin;
}
}
// 设置表单为不可编辑
stamper.setFormFlattening(true);
// 添加水印
int totalPages = reader.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
PdfContentByte watermarkContent = stamper.getOverContent(i);
watermarkContent.saveState();
watermarkContent.setFontAndSize(baseFont, 50);
watermarkContent.setColorFill(BaseColor.LIGHT_GRAY);
watermarkContent.setTextMatrix(300, 300);
watermarkContent.showTextAligned(Element.ALIGN_CENTER, watermarkText, 300, 300, 45);
watermarkContent.restoreState();
}
// 关闭PdfStamper
stamper.close();
reader.close();
}
/**
* 将PDF文件转换为单个长PNG图片
*
* @param pdfPath PDF文件路径
* @param outputDir 输出图片的目录
* @param dpi 图片分辨率(每英寸点数)
* @return 生成的长图文件路径
* @throws IOException 文件流异常
*/
public static String convertPdfToSingleLongPng(String pdfPath, String outputDir, int dpi) throws IOException {
// 加载PDF文件
PDDocument document = PDDocument.load(new File(pdfPath));
PDFRenderer renderer = new PDFRenderer(document);
// 创建输出目录(如果不存在)
File outputDirFile = new File(outputDir);
if (!outputDirFile.exists()) {
outputDirFile.mkdirs();
}
// 获取PDF页数
int pageCount = document.getNumberOfPages();
// 逐页渲染为图片并计算总高度
int totalHeight = 0;
int width = 0;
// 多页pdf即多张图片
BufferedImage[] images = new BufferedImage[pageCount];
for (int i = 0; i < pageCount; i++) {
BufferedImage image = renderer.renderImageWithDPI(i, dpi);
images[i] = image;
totalHeight += image.getHeight();
width = Math.max(width, image.getWidth());
}
// 创建一个新的长图
BufferedImage longImage = new BufferedImage(width, totalHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = longImage.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, totalHeight);
// 将所有图片合并到长图中
int y = 0;
for (BufferedImage image : images) {
g2d.drawImage(image, 0, y, null);
y += image.getHeight();
}
g2d.dispose();
// 保存长图
String longImagePath = outputDir + "/long_image.png";
ImageIO.write(longImage, "png", new File(longImagePath));
// 关闭PDF文档
document.close();
return longImagePath;
}
}