使用itextpdf动态生成PDF

发布于:2025-02-12 ⋅ 阅读:(10) ⋅ 点赞:(0)

前言

在有些场景下我们可能需要根据指定的模板来生成 PDF,比如说合同、收据、发票等等。因为 PDF 是不可编辑的,所以用代码直接对 PDF 文件进行修改是很不方便的,这里我是通过 itextAdobe 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;
    }

}