使用Itext/Itext7填充PDF表单域,并支持中文字体样式

前言

在日常开发中,会遇到需要使用给定数据填充PDF模板的需求,常用的方案是使用Itext进行表单域填充,但填充的字体往往达不到预期,特别是需要支持多种字体的情况。本文将从创建模板开始,基于Itext7,实现自动匹配字体填充文本。

一、将word导出为PDF

1. 编辑word文档

确定好文档内容和样式,预留需要填充文字的空白部分。

在这里插入图片描述

2. 将当前文档另存为pdf文件

在这里插入图片描述
在这里插入图片描述

二、制作模板

1. 使用 Adobe Acrobat DC 编辑导出的PDF,添加表单域

工具可在文末下载
打开文件后,选择 工具》准备表单》开始

在这里插入图片描述
在这里插入图片描述

可能会提示“未检测到新的表单域批注”,直接确定。

2. 添加文本域

使用文本域工具,在需要填充的位置添加文本域。
在这里插入图片描述

设置文本域名称,名称用于与后续填充数据的键值进行匹配。虽然允许同名文本域的存在,但为了后续能正常匹配字体,不建议存在同名文本域,最好是每个文本域都有唯一的域名称

在这里插入图片描述

双击文本域可以进入属性设置菜单,在外观页可以对字体样式进行设置,这里建议使用列表最底下的那部分中文字体。

在这里插入图片描述

设置完所有文本域后,点击保存,模板文件就制作完成了。实际操作中,可能需要根据填充结果反复对文本域长度、位置进行调整。
为方便演示字体效果,此处使用以下字体测试模板进行填充。

在这里插入图片描述

三、字体文件

字体文件可以按实际需要,在C:\Windows\Fonts中获取,笔者根据PDF常用中文字体(上文中提到的字体列表最底下的那部分),整理出与Itext解读别名的对应关系,并打包了相应字体,可在文末下载。

在这里插入图片描述

  • 字体映射关系

    字体名称字体文件Itext解读别名
    等线Deng.ttfDengXian
    等线LightDengl.ttfDengXian-Light
    等线粗体Dengb.ttfDengXian,Bold
    方正舒体FZSTK.TTFFZShuTi
    方正姚体FZYTK.TTFFZYaoTi
    仿宋simfang.ttfFangSong
    黑体simhei.ttfSimHei
    华文彩云STCAIYUN.TTFSTCaiyun
    华文仿宋STFANGSO.TTFSTFangsong
    华文琥珀STHUPO.TTFSTHupo
    华文楷体STKAITI.TTFSTKaiti
    华文隶书STLITI.TTFSTLiti
    华文宋体STSONG.TTFSTSong
    华文细黑STXIHEI.TTFSTXihei
    华文新魏STXINWEI.TTFSTXinwei
    华文行楷STXINGKA.TTFSTXingkai
    华文中宋STZHONGS.TTFSTZhongsong
    楷体simkai.ttfKaiTi
    隶书SIMLI.TTFLiSu
    宋体simsun.ttcSimSun
    微软雅黑msyh.ttcMicrosoftYaHei
    微软雅黑Lightmsyhl.ttcMicrosoftYaHeiLight
    微软雅黑粗体msyhbd.ttcMicrosoftYaHei,Bold
    新宋体simsun.ttcNSimSun
    幼圆SIMYOU.TTFYouYuan

四、代码实现

1. 在项目中依赖IText7(以maven为例)

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

2. FillPdfUtil.java

package org.nimang.pdfFill;



import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormAnnotation;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description 填充PDF表单
 * @Author JustHuman
 * @Time 2021/12/22 14:36
 */
public class FillPdfUtil {
    /**
     * 是否自动匹配字体,默认:否
     */
    private Boolean auto = Boolean.FALSE;

    /**
     * 扩展字体路径,默认: resources/fonts/extend
     */
    private String extendPath = DEFAULT_EXTEND_PATH;

    /**
     * 指定字体,默认:华文楷体(resources/fonts/STKAITI.TTF)
     */
    private PdfFont baseFont = defaultFont();


    // 正则表达式,排除非法字符
    private static final String ILLEGAL_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
    private static final String DEFAULT_FONT_PATH = "/fonts/STKAITI.TTF";
    private static final String DEFAULT_EXTEND_PATH = "/fonts/extend/";
    private static final Map<String, String> FONTS_MAP = fontsMap();

    /**
     * 不自动匹配字体,使用默认字体
     */
    public FillPdfUtil() {
    }

    /**
     * 使用指定字体
     * @param baseFont 指定字体
     */
    public FillPdfUtil(PdfFont baseFont) {
        if(baseFont != null){
            this.baseFont = baseFont;
        }
    }

    /**
     * 指定是否自动匹配字体,使用默认字体扩展包路径
     * @param auto 是否自动匹配字体
     */
    public FillPdfUtil(Boolean auto) {
        this.auto = auto;
    }

    /**
     * 使用指定字体,使用自定义字体扩展包路径
     * @param baseFont 指定字体
     * @param extendPath 字体扩展包路径
     */
    public FillPdfUtil(PdfFont baseFont, String extendPath) {
        if(baseFont != null){
            this.baseFont = baseFont;
        }
        if (extendPath != null && !extendPath.isBlank()) {
            this.extendPath = extendPath;
        }
    }

    /**
     * 指定是否自动匹配字体,使用自定义字体扩展包路径
     * @param auto 是否自动匹配字体
     * @param extendPath 字体扩展包路径
     */
    public FillPdfUtil(Boolean auto, String extendPath) {
        this.auto = auto;
        if (extendPath != null && !extendPath.isBlank()) {
            this.extendPath = extendPath;
        }
    }

    public Boolean getAuto() {
        return auto;
    }

    public void setAuto(Boolean auto) {
        this.auto = auto;
    }

    public String getExtendPath() {
        return extendPath;
    }

    public void setExtendPath(String extendPath) {
        this.extendPath = extendPath;
    }

    public PdfFont getBaseFont() {
        return baseFont;
    }

    public void setBaseFont(PdfFont baseFont) {
        this.baseFont = baseFont;
    }


    /**
     * 填充PDF表单
     * @param inputFilePath 输入文件路径
     * @param outputFilePath 输出文件路径
     * @param data 填充数据
     */
    public void fill(String inputFilePath, String outputFilePath, Map<String, String> data) {
        OutputStream os = null;
        try {
            os = Files.newOutputStream(Paths.get(outputFilePath));
        } catch (IOException e) {
            throw new RuntimeException("获取输出流失败:" + e.getMessage());
        }
        fill(inputFilePath, os, data);
    }

    /**
     * 填充PDF表单
     * @param inputFilePath 输入文件路径
     * @param os 输出流
     * @param data 填充数据
     */
    public void fill(String inputFilePath, OutputStream os, Map<String, String> data){
        PdfDocument pdfDoc = null;
        try {
            // 打开源PDF文件
            PdfReader reader = new PdfReader(inputFilePath);
            PdfWriter writer = new PdfWriter(os);
            pdfDoc = new PdfDocument(reader, writer);
            // 获取PDF表单
            PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
            // 如未指定字体,则使用默认字体
            PdfFont font = this.baseFont;
            for (String key : data.keySet()) {
                PdfFormField field = form.getField(key);
                if(field != null){
                    field.setValue(data.get(key));
                    if(this.auto){
                        font = getFont(field.getFont());
                    }
                    field.setFont(font);
                }
            }
            // 设置表单不可编辑
            form.flattenFields();
        } catch (Exception e) {
            throw new RuntimeException("填充PDF表单失败:" + e.getMessage(),e);
        } finally {
            if(pdfDoc != null){
                pdfDoc.close();
            }
        }
    }

    /**
     * 默认字体
     * @return PdfFont
     * @throws IOException
     */
    private PdfFont defaultFont() {
        // 默认华文楷体
        PdfFont baseFont;
        try {
            baseFont = PdfFontFactory.createFont(DEFAULT_FONT_PATH, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED);
        } catch (Exception e) {
            throw new RuntimeException("无法获取默认字体:" + e.getMessage(),e);
        }
        return baseFont;
    }

    /**
     * 获取字体
     * @param font 字体信息
     * @return PdfFont
     */
    private PdfFont getFont(PdfFont font) {
        String fontName = font.getFontProgram().getFontNames().getFontName();
        String fontKey = FONTS_MAP.get(fontName);
        if(fontKey==null){
            font = defaultFont();
        }else {
            try {
                if(fontKey.toLowerCase().endsWith("ttc")){
                    font = PdfFontFactory.createFont(this.extendPath + fontKey + ",0", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED);
                }else {
                    font = PdfFontFactory.createFont(this.extendPath + fontKey, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED);
                }
            } catch (IOException e) {
                font = defaultFont();
                e.printStackTrace();
            }
        }
        return font;
    }

    /**
     * 文件名合法性检测
     * @param fileName 文件名
     * @return boolean
     */
    public static boolean isValidFileName(String fileName) {
        if(fileName == null || fileName.trim().length() == 0){
            return false;
        }
        // 编译正则表达式
        Pattern pattern = Pattern.compile(ILLEGAL_CHARACTERS_REGEX);
        // 创建匹配器
        Matcher matcher = pattern.matcher(fileName);
        // 如果在文件名中找到非法字符,则返回false
        return !matcher.find();
    }

    /**
     * 获取文件名
     * @param inputFileName 输入文件路径
     * @param fileName 文件名
     * @return String
     */
    public static String getFileName(String inputFileName, String fileName) {
        if(fileName == null || fileName.trim().length() == 0){
            if(inputFileName.contains("/")){
                fileName = inputFileName.substring(inputFileName.lastIndexOf("/") + 1);
            }else if(inputFileName.contains("\\")){
                fileName = inputFileName.substring(inputFileName.lastIndexOf("\\") + 1);
            }
        }
        if(!isValidFileName(fileName)){
            fileName = "result.pdf";
        }
        if(!fileName.endsWith(".pdf")){
            fileName += ".pdf";
        }
        return fileName;
    }

    /**
     * 字体映射关系
     * @return
     */
    private static Map<String, String> fontsMap(){
        Map<String, String> map = new HashMap<>();
        // 字体映射关系,可扩展
        // itext字体标注/字体文件名
        map.put("DengXian", "Deng.ttf");//等线
        map.put("DengXian-Light", "Dengl.ttf");//等线Light
        map.put("DengXian,Bold", "Dengb.ttf");//等线粗体
        map.put("FZShuTi", "FZSTK.TTF");//方正舒体
        map.put("FZYaoTi", "FZYTK.TTF");//方正姚体
        map.put("FangSong", "simfang.ttf");//仿宋
        map.put("SimHei", "simhei.ttf");//黑体
        map.put("STCaiyun", "STCAIYUN.TTF");//华文彩云
        map.put("STFangsong", "STFANGSO.TTF");//华文仿宋
        map.put("STHupo", "STHUPO.TTF");//华文琥珀
        map.put("STKaiti", "STKAITI.TTF");//华文楷体
        map.put("STLiti", "STLITI.TTF");//华文隶书
        map.put("STSong", "STSONG.TTF");//华文宋体
        map.put("STXihei", "STXIHEI.TTF");//华文细黑
        map.put("STXinwei", "STXINWEI.TTF");//华文新魏
        map.put("STXingkai", "STXINGKA.TTF");//华文行楷
        map.put("STZhongsong", "STZHONGS.TTF");//华文中宋
        map.put("KaiTi", "simkai.ttf");//楷体
        map.put("LiSu", "SIMLI.TTF");//隶书
        map.put("SimSun", "simsun.ttc");//宋体
        map.put("MicrosoftYaHei", "msyh.ttc");//微软雅黑
        map.put("MicrosoftYaHeiLight", "msyhl.ttc");//微软雅黑Light
        map.put("MicrosoftYaHei,Bold", "msyhbd.ttc");//微软雅黑粗体
        map.put("NSimSun", "simsun.ttc");//新宋体
        map.put("YouYuan", "SIMYOU.TTF");//幼圆
        return map;
    }
}

3. Main.java

package org.nimang.pdfFill;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // 模板地址(根据实际位置修改)
        String tempPdfPath = "template/fontsTest.pdf";
        // 填充后PDF文件的输出地址(根据实际位置修改)
        String pdfOutPath = "D:\\test\\字体测试样本.pdf";
        // 字体扩展包地址(根据实际位置修改)
        String extendPath = "D:\\fonts\\";
        // 参数准备
        Map<String, String> data = getData();
        // 生成
        // 基于字体扩展包,自动匹配已有字体
        FillPdfUtil fillPdfUtil = new FillPdfUtil(true, extendPath);
        fillPdfUtil.fill(tempPdfPath, pdfOutPath, data);
    }

    public static Map<String, String> getData(){
        String info = "心之所向,身之所往——for my heart";
        Map<String, String> map = new HashMap<>();
        map.put("DengXian", info);//等线
        map.put("DengXian-Light", info);//等线Light
        map.put("DengXian,Bold", info);//等线粗体
        map.put("FZShuTi", info);//方正舒体
        map.put("FZYaoTi", info);//方正姚体
        map.put("FangSong", info);//仿宋
        map.put("SimHei", info);//黑体
        map.put("STCaiyun", info);//华文彩云
        map.put("STFangsong", info);//华文仿宋
        map.put("STHupo", info);//华文琥珀
        map.put("STKaiti", info);//华文楷体
        map.put("STLiti", info);//华文隶书
        map.put("STSong", info);//华文宋体
        map.put("STXihei", info);//华文细黑
        map.put("STXinwei", info);//华文新魏
        map.put("STXingkai", info);//华文行楷
        map.put("STZhongsong", info);//华文中宋
        map.put("KaiTi", info);//楷体
        map.put("LiSu", info);//隶书
        map.put("SimSun", info);//宋体
        map.put("MicrosoftYaHei", info);//微软雅黑
        map.put("MicrosoftYaHeiLight", info);//微软雅黑Light
        map.put("MicrosoftYaHei,Bold", info);//微软雅黑粗体
        map.put("NSimSun", info);//新宋体
        map.put("YouYuan", info);//幼圆
        return map;
    }
}

4. 填充效果

在这里插入图片描述

五、测试项目源码

>> Gitee - PdfFillUtil <<
测试项目源码,包含支持下载PDF功能的Util,通过选择标签查看不同版本Itext实现。

在这里插入图片描述

六、相关下载

Adobe Acrobat DC 2020
提取码:9654

中文字体包
提取码:9654

七、参考文章

使用IText生成PDF文件,并记录一些遇到的问题

生成pdf设置中文字体出错 \simsun.ttc’ with ‘Identity-H’…

java使用itext填充pdf模板,超简单教学,有手就行

Itext7填充PDF表单中文无法显示,‘凉’不显示问题

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
使用iTextPDF生成pdf填充自定义数据的步骤如下: 1. 首先,获取需要生成pdf的初始模板,包含格式,但不包含数据。可以使用工具如Adobe Acrobat来编辑pdf模板,在对应区生成文本,为每个设置一个唯一的名称。 2. 在Java代码中,使用iTextPDF库来对pdf进行操作。首先,需要获取PdfReader对象,将模板文件加载为PdfReader对象。可以使用以下代码: ```java PdfReader reader = new PdfReader(templatePath); // templatePath是模板pdf文件的路径 ``` 3. 接下来,使用PdfStamper对象来填充数据并生成最终的pdf文件。可以使用以下代码: ```java PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(outputPath)); // outputPath是生成的pdf文件的路径 AcroFields fields = stamper.getAcroFields(); // 使用fields对象来填充文本的数据 fields.setField("fieldName1", "fieldValue1"); fields.setField("fieldName2", "fieldValue2"); // 继续填充其他文本的数据 // 最后,调用stamper的close方法来保存并关闭pdf文件 stamper.close(); ``` 4. 通过上述步骤,你可以使用iTextPDF将自定义数据填充pdf模板中,并生成最终的pdf文件。 请注意,以上代码只展示了主要的方法代码,并不是整个流程的逻辑代码。具体的实现可能会根据具体的需求和模板结构有所不同。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [java使用itextpdf生成pdf填充自定义数据](https://blog.csdn.net/qq_34244426/article/details/104833805)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值