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

news2024/11/25 2:45:46

文章目录

  • 前言
  • 一、准备工作
    • 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效果如下:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

品牌推广的新路径:邀请歌手出席活动的独特策略“

在当今的市场竞争中&#xff0c;品牌推广和市场营销已经成为企业取得成功的重要因素之一。而邀请知名歌手出席活动则是一种备受瞩目的策略&#xff0c;可以为品牌带来巨大的优势和机遇。无论是与赵丽颖、迪丽热巴、张子枫、关晓彤、周冬雨还是孙俪等知名歌手合作&#xff0c;都…

WPF中Binding的数据转换—ValueConverters

WPF中Binding的数据转换—ValueConverters 在WPF中使用Binding经常会遇到需要转换的情况&#xff0c;如bool转为visibility&#xff0c;通常情况需要自己写一个类继承自IValueConverter接口&#xff0c;使用详情请参见Binding对数据的转换和校验&#xff0c;这种方法虽然不难&…

C 模拟包装机

一种自动包装机的结构如图 1 所示。首先机器中有 N 条轨道&#xff0c;放置了一些物品。轨道下面有一个筐。当某条轨道的按钮被按下时&#xff0c;活塞向左推动&#xff0c;将轨道尽头的一件物品推落筐中。当 0 号按钮被按下时&#xff0c;机械手将抓取筐顶部的一件物品&#x…

中间件漏洞解析

服务器解析漏洞算是历史比较悠久了&#xff0c;但如今依然广泛存在。在此记录汇总一些常见服务器&#xff08;WEB server&#xff09;的解析漏洞&#xff0c;比如IIS6.0、IIS7.5、apache、nginx等 2|0 二、IIS5.x-6.x解析漏洞&#xff08;针对asa/asp/cer&#xff09; 2|11、打…

学习笔记|盘点一些Linux 常用的命令

目录 1、apt-get Debian/Ubuntu系统包管理器 2、uname 获取 操作系统信息 3、date 查看/设置 系统时间 4、yum CentOS系统包管理器 5、mkdir 新建 文件夹 6、free 查看内存使用信息 7、wget 下载工具 8、cd 进入 文件夹 8、cp 复制或重命名 文件/文件夹 9、VI、VIM …

机器学习之支持向量机(SVM)

1 支持向量机介绍 支持向量机&#xff08;support vector machine&#xff0c;SVM&#xff09;是有监督学习中最有影响力的机器学习算法之一&#xff0c;该算法的诞生可追溯至上世纪 60 年代&#xff0c; 前苏联学者 Vapnik 在解决模式识别问题时提出这种算法模型&#xff0c;…

synchronized监视器锁

1、synchronized&监视器锁 1.1 synchronized 介绍 在 Java 中&#xff0c;synchronized 是一种关键字&#xff0c;用于实现线程的同步和互斥控制。它可以修饰方法或代码块&#xff0c;用于保护共享资源的访问&#xff0c;避免多个线程同时修改数据而引发的并发问题。 具…

chatgpt赋能python:Python重写父类__init__方法的必要性与实现方法

Python重写父类__init__方法的必要性与实现方法 在Python中&#xff0c;一个类可以继承自另一个类&#xff0c;从而获得另一个类的属性和方法。当我们继承一个父类时&#xff0c;通常我们需要重写其中的一些方法&#xff0c;以满足我们自己的需求。在这篇文章中&#xff0c;我…

玩机搞机-----带你了解高通刷机平台中的一些选项释义 玩转平台

很多刷机工具玩家都使用过&#xff0c;但对于一些新手来说。有些选项所表达的意义不太了解&#xff0c;选择与否严重会导致机型固件刷完个别功能出现故障&#xff0c;今天的这个博文对有些刷机平台中的选项做个简单的说明。 一 小米刷机平台 MiFlash.截止目前最新的版本是2022…

最新|2024年QS世界大学排名前100榜单发布

6月28日世界高等教育研究机构Quacquarelli Symonds&#xff08;QS&#xff09;率先公布了2024年世界大学排名&#xff0c;本次QS排名因指标和权重的重大调整&#xff0c;导致排名发生较大变化。知识人网小编将新的评分标准及前100的大学榜单整理如下&#xff0c;供读者参考。 前…

Unity渲染工程收集

NPR 非真实渲染 UnityURP-AnimeStyleCelShader SSR 屏幕空间反射 UnitySSReflectionURP

消息传输不丢失:掌握消息中间件的持久化机制

当涉及到消息的持久化和重放时&#xff0c;我们可以使用Spring Boot与RabbitMQ来实现这个场景。RabbitMQ支持消息的持久化&#xff0c;以确保在发送和接收过程中消息不会丢失。同时&#xff0c;我们可以使用消息的重放机制&#xff0c;以便在需要时重新发送消息。 首先&#xf…

leetcode:387. 字符串中的第一个唯一字符(python3解法)

难度&#xff1a;简单 给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回 -1 。 示例 1&#xff1a; 输入: s "leetcode" 输出: 0示例 2: 输入: s "loveleetcode" 输出: 2示例 3: 输…

1253. 重构 2 行二进制矩阵(力扣)

1253. 重构 2 行二进制矩阵&#xff08;力扣&#xff09; 题目第一种方式分析测试代码运行结果 第二种方式测试代码运行结果 题目 给你一个 2 行 n 列的二进制数组&#xff1a; 矩阵是一个二进制矩阵&#xff0c;这意味着矩阵中的每个元素不是 0 就是 1。 第 0 行的元素之和为…

系统架构设计师-软件工程(1)

一、软件过程模型 &#xff08;1&#xff09;瀑布模型、&#xff08;2&#xff09;V模型【瀑布变种】、&#xff08;3&#xff09;原型模型、 &#xff08;4&#xff09;螺旋模型【原型瀑布】、&#xff08;5&#xff09;构件组装模型/基于构件的开发方法、 &#xff08;…

学习Kotlin~类

类 类的field 类定义的每一个属性&#xff0c;kotlin都会产生一个filed,一个setter(),一个getter()field用来存储属性数据&#xff0c;不能直接定义&#xff0c;kotlin会封装&#xff0c;保护它里面数据&#xff0c;只暴露给getter和setter使用只有可变属性才有setter方法需要…

UNITY3D弹幕游戏,万人同屏解决方案_类萌宠宠之战

先上效果 &#xff08;类萌宠宠之战&#xff09;弹幕游戏&#xff0c;万人同屏解决方案演示 UNITY默认的人物动画显示方案是 SkinnedMeshRenderer 该动画的计算是由CPU计算&#xff08;计算骨骼位置所影响的顶点位置&#xff09; 所以是CPU计算&#xff0c;物体大于2000个时…

Python3,掌握这几种并行处理,轻轻松松提升for循环速度。

并行处理几种方法 1、引言2、并行处理2.1 定义2.2 并行处理优缺点2.3 并行处理的常用库2.4 代码示例2.4.1 multiprocessing2.4.2 concurrent.futures2.4.3 joblib2.4.4 threading 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;你给我讲一讲并行处理呗。 小鱼&#xff1…

Android:datePicker对话框的使用

一、前言&#xff1a;这篇文章是关于DatePickerDialog&#xff0c;点击按钮出现一个日期选择器对话框&#xff0c;通过点击确认把选则的日期显示到文本控件上。 二、上代码 页面布局xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout…

正则表达式回溯引发的生产惨案

文章目录 背景问题原因分析如何解决&#xff1f;chatgpt 3.5GP4的表现未完待续 背景 业务上的一个字段在解析时为了避免脏数据导致后续ETL的异常&#xff0c;决定从源头将该字段严格按照设计的规则去匹配。该字段的上传是设备端传上来的文件中的一个字段。 正向&#xff1f;反…