前言
在有些场景下我们可能需要根据指定的模板来生成 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 = "国铁集团";// 生成PDFgeneratePdf(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();}// 关闭PdfStamperstamper.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;}}