使用itextpdf填充表单域并生成pdf

文章目录

  • 前言
  • 一、准备工作
    • 1.1 安装软件
    • 1.2 准备pdf
    • 1.3 设置表单域
  • 二、创建项目
  • 三、编写代码
    • 3.1 编写工具类
    • 3.2 测试
  • 四、测试结果

前言

最近手上有个任务,就是需要做一个pdf导出的功能。

可选择的技术点比较多,我这边综合考虑之后,使用的是 itext。
大致有两种实现思路:
1️⃣:使用软件【Adobe Acrobat DC】去做一个pdf模版,将表单域指定好,随后使用代码去填充参数,最终得到一个pdf或字节数组。

2️⃣:使用【Freemarker】渲染html页面,最终使用代码将该页面转换为pdf。

我这边当前的需求比较适合第一种方式。

一、准备工作

1.1 安装软件

首先是安装软件(失效的话麻烦评论区留言)
链接:https://pan.baidu.com/s/1O8JtVuK87VYbzx0DGQyJ1g
提取码:a0hy

1.2 准备pdf

新建一个word文档,插入一个表格,效果如下:
在这里插入图片描述

然后将word导出为pdf文件。
再使用我们刚刚安装好的软件打开。

1.3 设置表单域

在工具栏中找到“准备表单”功能,点击打开。
在这里插入图片描述

然后修改自己想要的文本域字段名、字体大小等配置:
在这里插入图片描述

也可以右键新增文本域(我这里的title就是新增的):
在这里插入图片描述
随后我们保存pdf即可。最终我们会得到这样一个pdf:
在这里插入图片描述

二、创建项目

创建一个普通的 springboot项目,引入依赖如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext7-core</artifactId>
            <version>7.2.5</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

resources 目录中创建 templates 文件夹,并将我们前边准备好的pdf放进去。效果如下:
在这里插入图片描述
我这里准备的文件是 personal_info.pdf

三、编写代码

3.1 编写工具类

package org.feng.pdf;

import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 填充pdf表单域
 *
 * @author fengjinsong
 * @date 2023-06-28 16:55:38
 * @version: 1.0
 */
@Slf4j
@Data
public class FillFormFieldsToPdfTemplateUtil {

    /**
     * 默认模板路径
     */
    private static final String DEFAULT_TEMPLATE_DIRECTORY;

    /**
     * 默认字体(pdf显示中文)
     */
    private static final PdfFont DEFAULT_FONT;

    /**
     * 模版路径
     */
    private String templateDirectory;

    /**
     * 字体
     */
    private PdfFont pdfFont;

    public FillFormFieldsToPdfTemplateUtil() {
    }

    static {
        try {
            DEFAULT_TEMPLATE_DIRECTORY = ResourceUtils.getURL("classpath:").getPath() + "/templates/";
            DEFAULT_FONT = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 依据pdf模板,填充表单域,生成pdf文件
     *
     * @param templateFileName 模版文件名,不能为空
     * @param formFieldsParams 表单域需要的参数,不能为空
     * @param destPdfPath      目标位置
     * @throws IOException 涉及到文件操作
     */
    public void generatePdfFile(String templateFileName, String destPdfPath, Map<String, String> formFieldsParams) throws IOException {
        // 构造pdf阅读器,指定输入流
        PdfReader pdfReader = new PdfReader(new FileInputStream(getTemplatePath(templateFileName)));
        // 构造pdf写出器,指定输出流
        OutputStream fos = new FileOutputStream(destPdfPath);

        // 构造pdfDocument实例
        PdfDocument pdf = new PdfDocument(pdfReader, new PdfWriter(fos));

        // 设置为a4纸张大小
        pdf.setDefaultPageSize(PageSize.A4);

        // 替换参数(如果有参数的话)
        PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
        fillFormFields(form, formFieldsParams);

        // 锁定表单,不让修改
        form.flattenFields();
        pdf.close();
    }

    /**
     * 根据pdf模版和表单域参数获取pdf对应的字节数组
     *
     * @param templateFileName 模版文件名,不能为空
     * @param formFieldsParams 表单域需要的参数,不能为空
     * @return 填充了表单域参数之后的pdf字节数组
     * @throws IOException 涉及到文件操作
     */
    public byte[] generatePdfByteArray(String templateFileName, Map<String, String> formFieldsParams) throws IOException, RuntimeException {

        // 构造pdf阅读器,指定输入流
        PdfReader pdfReader = new PdfReader(new FileInputStream(getTemplatePath(templateFileName)));
        // 构造pdf写出器,指定输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfWriter pdfWriter = new PdfWriter(outputStream);

        // 构造pdfDocument实例
        PdfDocument pdf = new PdfDocument(pdfReader, pdfWriter);
        // 设置为a4纸张大小
        pdf.setDefaultPageSize(PageSize.A4);

        // 替换参数(如果有参数的话)
        PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
        fillFormFields(form, formFieldsParams);

        // 锁定表单,不让修改
        form.flattenFields();
        // 关闭资源
        pdf.close();
        // 返回最终结果
        return outputStream.toByteArray();
    }

    /**
     * 获取模版文件路径
     *
     * @param templateFileName 模版文件名,在指定的模版目录下
     * @return 模版文件路径
     */
    private String getTemplatePath(String templateFileName) {
        // 拼接完整模版路径
        return Objects.nonNull(templateDirectory) ?
                templateDirectory + templateFileName : DEFAULT_TEMPLATE_DIRECTORY + templateFileName;
    }

    /**
     * 填充表单域
     *
     * @param form             表单
     * @param formFieldsParams 表单需要的动态参数
     */
    private void fillFormFields(PdfAcroForm form, Map<String, String> formFieldsParams) {
        // 获取所有的表单域
        Map<String, PdfFormField> fields = form.getFormFields();
        // 获取当前字体
        PdfFont currentFont = pdfFont == null ? DEFAULT_FONT : pdfFont;
        formFieldsParams.forEach((key, value) -> {
            // 获取某个表单域
            Optional<PdfFormField> formFieldOptional = Optional.ofNullable(fields.get(key));
            formFieldOptional.ifPresent(formField -> {
                // 设置字体、替换值
                formField.setFont(currentFont).setValue(value);
            });
        });
    }
}

3.2 测试

准备一个 Controller 类,如下:

package org.feng.controller;

import lombok.extern.slf4j.Slf4j;
import org.feng.pdf.FillFormFieldsToPdfTemplateUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * itextpdf7控制器
 *
 * @author fengjinsong
 * @date 2023-06-29 09:00:04
 * @version: 1.0
 */
@Controller
@Slf4j
public class ItextPdf7Controller {

    /**
     * 下载文件
     *
     * @param response 响应对象;用于设置响应参数
     */
    @GetMapping("/download")
    public ResponseEntity<byte[]> download(HttpServletResponse response) throws IOException {

        HttpHeaders headers = new HttpHeaders();
        // application/octet-stream二进制流数据(文本下载)。
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

        // 下载显示的文件名,解决中文名称乱码问题
        String downloadFileName = System.currentTimeMillis() + ".pdf";
        // 弹出保存框即资源选择器
        response.setHeader("Content-Disposition", "attachment;filename=" + downloadFileName);

        // 组装参数
        Map<String, String> data = new HashMap<>();
        data.put("title", "个人简介");
        data.put("name", "我的某个朋友");
        data.put("age", "30");
        data.put("likes", "女");
        data.put("birthday", "2022-12-22");
        // 使用数据填充pdf模版并转换为字节数组
        FillFormFieldsToPdfTemplateUtil pdfTemplateUtil = new FillFormFieldsToPdfTemplateUtil();
        byte[] bytes = pdfTemplateUtil.generatePdfByteArray("personal_info.pdf", data);

        // 返回结果
        return new ResponseEntity<>(bytes, headers, HttpStatus.CREATED);
    }


    @ResponseBody
    @GetMapping("/generate")
    public String generatePdfFile() throws IOException {

        // 指定输出的目录
        String path = ResourceUtils.getURL("classpath:").getPath() + "/templates/";

        // 组装参数
        Map<String, String> data = new HashMap<>();
        data.put("title", "个人简介");
        data.put("name", "我的某个朋友");
        data.put("age", "30");
        data.put("birthday", "2022-12-22");
        data.put("likes", "女");

        // 在项目中生成pdf
        FillFormFieldsToPdfTemplateUtil pdfTemplateUtil = new FillFormFieldsToPdfTemplateUtil();
        pdfTemplateUtil.generatePdfFile("personal_info.pdf", path + "dsadsad.pdf", data);

        // 返回结果
        return "success";
    }
}

四、测试结果

在项目中生成pdf文件,发送请求 GET http://localhost:8080/generate
下载生成的pdf文件,发送请求 GET http://localhost:8080/download

生成的pdf效果如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/33822.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第五课—大学英语四六级备考—听力专项

Key words 1.implement vt.实施 "Implement" 在中文中的意思是「实施」或「执行」。以下是一些示例用法和搭配&#xff1a; 中文意思&#xff1a;实施、执行 形近字&#xff1a;implicate&#xff08;牵连&#xff09; 1. 用英文造句&#xff1a;The government …

微信小程序浏览docx,pdf等文件在线预览使用wx.openDocument

wx.downloadFile({ url: fileUrl,//pdf链接success(res) {wx.openDocument({ //打开文档filePath: res.tempFilePath,fileType: "pdf",//文档类型showMenu: true,success: function (res) {wx.showToast({title: 打开文档成功,})},fail: function (res) {wx.showToas…

vue封装svg组件来修改svg图片颜色

文章目录 1、引入依赖2、根目录的vue.config.js配置3、在组件文件夹(compontents)中创建svgIcon.vue4、在src目录下创建icons文件5、处理svg格式的图片6、在main.js文件中引入icons文件中的index.js文件7、使用8、效果图1、项目成功运行后的样子2、直接在html上添加样式&#x…

LangChain:LLM应用程序开发(中)——文档问答、评估、Agents(代理)

文章目录 四、文档问答4.1 快速入门4.2 逐步实现4.3 其它方法 五、评估5.1 创建QA app5.2 生成测试数据点5.2.1 Hard-coded examples5.2.2 LLM-Generated examples 5.3 link chain debug手动评估5.4 LLM assisted evaluation5.5 LangChain Evaluation platform 六、Agents&…

论文笔记 CPU Accounting for Multicore Processors

Abstract 确定了对CPU utilization的不准确测量是如何影响OS的几个关键方面的这篇文章提出来了一个比CPU utilization更准确的性能评估指标 In this paper, we identify how an inaccurate measurement of the CPU utilization affects several key aspects of the system suc…

Python 基本数据类型(三)

文章目录 每日一句正能量数值运算数值类型实例String&#xff08;字符串&#xff09; 每日一句正能量 人的相处&#xff0c;靠的是真心&#xff0c;不是套路。合得来的人&#xff0c;坦诚相待&#xff0c;合不来的人&#xff0c;客气寒暄&#xff1b;谁也别给谁冷脸看&#xff…

期末复习【计算机网络】

期末复习【计算机网络】 前言推荐期末复习如何快速阅读电子书重点第1章 概述1.6 计算机网络的性能1.6.2 计算机网络的性能指标√ 1.7 计算机网络体系结构1.7.3 具有五层协议的体系结构√ 第2章 物理层2.3 物理层下面的传输媒体 *2.4 信道复用技术2.4.1 频分复用、时分复用和统计…

LeetCode 剑指 Offer 13. 机器人的运动范围(深度遍历)

LeetCode 剑指 Offer 13. 机器人的运动范围 原题思路代码运行截图收获 原题 LeetCode 剑指 Offer 13. 机器人的运动范围 思路 通过深度遍历来找出所有可达的格子通过0、1、2来区分未遍历、可到达、不可到达三种状态 代码 class Solution { public:int visited[109][109];i…

【计算机网络】数据链路层--点对点协议PPP

1.概念 2.构成 3.封装成帧 - 帧格式 4.透明传输 4.1字节填充法&#xff08;面向字节的异步链路&#xff09; 4.2.比特填充法&#xff08;面向比特的同步链路&#xff09; 5.差错检测 6.工作状态 7.小结

Webpack和Vite简单使用

目录 WebPack 介绍 基础使用 初始化使用 webpack.config.js文件 webpack开发服务器 vite 介绍 使用 使用vite创建vue框架项目 WebPack 介绍 当我们习惯了在node中编写代码的方式后&#xff0c;在回到前端编写html、css、js这些东西会感觉到各种的不便。比如: 不能放心…

【Linux】硬链接 和 软链接

为了方便用户访问文件&#xff0c;Linux提供了一种称为连接&#xff08;link&#xff09;的机制&#xff0c;可以将一个文件或目录与另一个文件或目录建立关联&#xff0c;从而实现多个路径指向同一个文件或目录的效果。 一、概述二、硬链接和软链接详解2.1 硬链接2.11 硬链接的…

C++ 模板

模板是泛型编程的基础&#xff0c;泛型编程即以一种独立于任何特定类型的方式编写代码。 模板是创建泛型类或函数的蓝图或公式。库容器&#xff0c;比如迭代器和算法&#xff0c;都是泛型编程的例子&#xff0c;它们都使用了模板的概念。 每个容器都有一个单一的定义&#xf…

微信小程序的跨页面传参以及data-方法的相关细节

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;微信小程序的跨页面传参以及data-方法的相关细节 目录 前言wx.navigateTo()方法微信小程序传参的几种方式通过data-属性传参关于data-方法配合点击事件传参的细节 前言 其实在学习新东西的过程中&a…

记录一次对STM32G4串口硬件FIFO的调试

记录一次对STM32G4串口硬件FIFO的调试 前言&#xff1a;通常我们使用串口接收多字节数据会使用中断和DMA两种方式。使用中断方式&#xff0c;每接收到一个字节就会触发一次中断&#xff0c;我们可以在中断函数里将接收到的这一字节保存在内存中然后等待其他程序处理&#xff0c…

ruoyi-vue版本(三十)Spring Security 安全框架中token的生成与解析

目录 1 使用2 写工具类3 使用工具类 1 使用 1 项目里面添加依赖 <!-- Token生成与解析--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency>2 写工具类 package com.ruoyi.framework.we…

ethtool 原理介绍和解决网卡丢包排查思路

前言 之前记录过处理因为 LVS 网卡流量负载过高导致软中断发生丢包的问题&#xff0c;RPS 和 RFS 网卡多队列性能调优实践[1]&#xff0c;对一般人来说压力不大的情况下其实碰见的概率并不高。这次想分享的话题是比较常见服务器网卡丢包现象排查思路&#xff0c;如果你是想了解…

【数据库四】MySQL备份与恢复

MySQL备份与恢复 1.数据库备份的分类1.1 数据备份的重要性1.2 数据库备份的分类1.3 常见的备份方法 2.MySQL完全备份与恢复2.1 MySQL完全备份2.2 数据库完全备份分类2.3 MySQL物理冷备份及恢复2.4 数据迁移DST2.5 mysqldump进行逻辑备份2.5.1 mysqldump备份数据库2.5.2 mysqldu…

Nvidia官方视频编解码性能

NVIDIA VIDEO CODEC SDK | NVIDIA Developer 1080P解码性能&#xff1a; 720P解码性能&#xff1a; 详细的参见官方的链接地址&#xff0c;对于GPU的解码fps能力&#xff0c;可以作为评估参照&#xff01;

视频与AI,与进程交互(二) pytorch 极简训练自己的数据集并识别

目标学习任务 检测出已经分割出的图像的分类 2 使用pytorch pytorch 非常简单就可以做到训练和加载 2.1 准备数据 如上图所示&#xff0c;用来训练的文件放在了train中&#xff0c;验证的文件放在val中&#xff0c;train.txt 和 val.txt 分别放文件名称和分类类别&#xff…