文章目录
前言
在日常开发中,会遇到需要使用给定数据填充PDF模板的需求,常用的方案是使用Itext
进行表单域填充,但填充的字体往往达不到预期,特别是需要支持多种字体的情况。本文将从创建模板开始,基于Itext7
,实现自动匹配字体填充文本。
一、将word导出为PDF
1. 编辑word文档
确定好文档内容和样式,预留需要填充文字的空白部分。
2. 将当前文档另存为pdf文件
二、制作模板
1. 使用 Adobe Acrobat DC 编辑导出的PDF,添加表单域
工具可在文末下载
打开文件后,选择 工具》准备表单》开始
可能会提示“未检测到新的表单域批注”,直接确定。
2. 添加文本域
使用文本域工具,在需要填充的位置添加文本域。
设置文本域名称,名称用于与后续填充数据的键值进行匹配。虽然允许同名文本域的存在,但为了后续能正常匹配字体,不建议存在同名文本域,最好是每个文本域都有唯一的域名称。
双击文本域可以进入属性设置菜单,在外观页可以对字体样式进行设置,这里建议使用列表最底下的那部分中文字体。
设置完所有文本域后,点击保存,模板文件就制作完成了。实际操作中,可能需要根据填充结果反复对文本域长度、位置进行调整。
为方便演示字体效果,此处使用以下字体测试模板进行填充。
三、字体文件
字体文件可以按实际需要,在C:\Windows\Fonts
中获取,笔者根据PDF常用中文字体(上文中提到的字体列表最底下的那部分),整理出与Itext解读别名的对应关系,并打包了相应字体,可在文末下载。
-
字体映射关系
字体名称 字体文件 Itext解读别名 等线 Deng.ttf DengXian 等线Light Dengl.ttf DengXian-Light 等线粗体 Dengb.ttf DengXian,Bold 方正舒体 FZSTK.TTF FZShuTi 方正姚体 FZYTK.TTF FZYaoTi 仿宋 simfang.ttf FangSong 黑体 simhei.ttf SimHei 华文彩云 STCAIYUN.TTF STCaiyun 华文仿宋 STFANGSO.TTF STFangsong 华文琥珀 STHUPO.TTF STHupo 华文楷体 STKAITI.TTF STKaiti 华文隶书 STLITI.TTF STLiti 华文宋体 STSONG.TTF STSong 华文细黑 STXIHEI.TTF STXihei 华文新魏 STXINWEI.TTF STXinwei 华文行楷 STXINGKA.TTF STXingkai 华文中宋 STZHONGS.TTF STZhongsong 楷体 simkai.ttf KaiTi 隶书 SIMLI.TTF LiSu 宋体 simsun.ttc SimSun 微软雅黑 msyh.ttc MicrosoftYaHei 微软雅黑Light msyhl.ttc MicrosoftYaHeiLight 微软雅黑粗体 msyhbd.ttc MicrosoftYaHei,Bold 新宋体 simsun.ttc NSimSun 幼圆 SIMYOU.TTF YouYuan
四、代码实现
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