场景:后台有“合同”.pdf 需要填充 客户名称、身份证件号 等信息。
解决办法:
方式1:
先对pdf进行特殊处理,设置“表单”,再通过程序获取pdf的表单,进行遍历表单进行填充。
具体步骤:
步骤一:确认合同内容并转换成PDF
客户根据自身业务要求确认合同内容,在合同中需要填入参数的地方空出足够的空间。客户将word版本的合同文本转换成PDF格式。
步骤二:安装Adobe Acrobat软件
客户在本地安装Adobe Acrobat 9 Pro软件。
步骤三:标出合同的文本填充域
(1)使用Adobe Acrobat 9 Pro软件打开PDF格式的合同模板。打开“表单”中的“添加或编辑域”。
图1:表单位置
(2)打开后,页面跳出对话框,提示是否要让Acrobat为您检测表单域,选择“否”。注意,不能使用Acrobat自动检测的表单域。
图2:是否要让Acrobat为您检测提示页
(3)PDF变成如下格式。点击“添加新域”中的“文本框”,将鼠标移至PDF需要填充内容的地方,会出现填充文本域方框。拖动方框周边可将方框调整成合适大小。
图3:添加新域
图4:填充文本区域框
(4)双击文本域方框,页面跳出文本域属性。在“一般”属性中的“名称”和“工具提示”输入文本域的序号,如1、2、3……。“名称”和“工具提示”的序号需一致。确认同一个合同模板里,各文本域的序号唯一。文本域的序号应与创建合同时传输过来的报文相关内容一致。注意,为了减少报文长度,请文本域序号尽量为1、2、3……。
图5:文本域属性1
(5)在“外观”属性中,可设置字体大小和文本颜色。一般而言,字体大小为“自动”,文本颜色为“黑色”。注意,字体必须选择“Adobe宋体Std L”,如下图,否则创建合同时可能会产生错误。其他属于无需设置。
图6:文本域属性2
(6)在一般属性中,勾选表单域的只读格式。
图7:文本域属性3
步骤四:标出合同的签名域
(1)点击“添加新域”中的“数字签名”,将鼠标移至PDF需要数字签名的地方,会出现签名域方框。拖动方框周边可将方框调整成合适大小。建议若签署的是个人,可将签名域设置成长方形区域,若签署的是企业(显示企业电子印章样式),可将签名域设置成正方形区域,这样才能签出较标准的圆章。
图8:签名区域框
(2)双击签名域方框,页面跳出签名域属性。在“一般”属性中的“名称”和“工具提示”输入签名域的序号,如Signature1、Signature2、Signature3……。“名称”和“工具提示”的序号需一致。确认同一个合同模板里的各签名序号不唯一。对接平台的签名域序号为“Signature_plat”,签名域的序号应与创建合同时传输过来的报文相关内容一致。
图9:签名域属性1
(3)在“外观”属性中,可设置文本颜色和字体。一般而言,签名的文本颜色为“红色”。注意,字体必须选择“Adobe宋体Std L”,如下图,否则创建合同时可能会产生错误。其他属于无需设置。设置完成后,关闭PDF合同,确认保存修改。
图10:签名域属性2
- 注意事项
(1)不能使用Acrobat自动检测表单域。
(2)所有的字体必须选择“Adobe宋体Std L”。
(3)为了减少报文长度,请文本域序号尽量为1、2、3……。
(4)对接平台的签名域序号为“Signature_plat”。
(5)文本域和签名域的序号应与创建合同时传输过来的报文相关内容一致。
(6)将模板发送给安心签管理员时,邮件正文务必中列出所有的文本域编号,用英文格式的“;”分离,如1;2;3;4……。否则使用时可能报错。
(7)合同模板大小不得超过20M。
(8)若合同模板中的标准内容发生变更,需重新制作模板,即通过word将内容更改后再转换成PDF进行制作。为提升效率,对于新合同模板中,保留了原模板文本域和签名域的地方,可直接复制原模板的文本域和签名域,粘贴使用。
至此,pdf特殊处理完成了,现在我们通过程度对其进行填充。
coontroller层:
@RequestMapping("/downLoadPdf")
public void downpdf(HttpServletRequest request,HttpServletResponse response @PathParam("params") String params){
//设置ContentType有好几种,设置不对是不会下载的
response.setContentType("application/force-download");
//设置下载文件的名称叫 myFile.pdf
response.addHeader("Content-Disposition","attachment;fileName=myFile.pdf");
//web端下载都是通过 ServletOutputStram 流进行下载的,其他我们不用管
ServletOutputStream outputStream = resonse.getOutputStram(); //我们只要吧文件写入这个流,web就会下载我们写入的文件了
//读取resource文件下的pdf文件夹的myFile.pdf文件
PdfReader reader = new PdfReader("pdf/myFile.pdf");
//将读取到的内容准备写入到 ServletOutputStream流中去 操作stamper 进行填充。
PdfStamper stamper = new PdfStamper(reader,responser.getOutputStram())
// 使用中文字体
BaseFont bf = BaseFont.createFont(fontName, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
ArrayList<BaseFont> fontList = new ArrayList<BaseFont>();
fontList.add(bf);
/* 取出报表模板中的所有字段 */
AcroFields fields = ps.getAcroFields();
fields.setSubstitutionFonts(fontList);
String data[] ={"zhangsan","2020.07.05"}
//给字段赋值
Iterator<String> it -from.getFields().keySey().iterator();
int i=0;
while(it.hasNext){
String name = it.next.toString();
String value= data[i++]
form.setField(name,value); //如果pdf特殊处理的时候没有设置 信息,这里会取 1,2,3,4.。。。
}
/* 必须要调用这个,否则文档不会生成的 */
ps.setFormFlattening(true);
ps.close();
}
方式2: 不经过特殊处理的pdf,全文搜索再替换。原理:找到关键字所在的位置,记录下信息,然后在该区域画白色的布遮住,再在此区域画上文字
参考博客:JAVA替换PDF文字 - 北漂追梦人 下面改进了一丢丢:
引入jar包
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
/**
* 用来保存关键字信息
*/
public class MatchItem {
//页数
private Integer pageNum;
//x坐标
private Float x;
//y坐标
private Float y;
//页宽
private Float pageWidth;
//页高
private Float pageHeight;
//匹配字符
private String content;
//字体宽
private float fontWidth;
//字体高
private float fontHeight = 12;
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Float getX() {
return x;
}
public void setX(Float x) {
this.x = x;
}
public Float getY() {
return y;
}
public void setY(Float y) {
this.y = y;
}
public Float getPageWidth() {
return pageWidth;
}
public void setPageWidth(Float pageWidth) {
this.pageWidth = pageWidth;
}
public Float getPageHeight() {
return pageHeight;
}
public void setPageHeight(Float pageHeight) {
this.pageHeight = pageHeight;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public float getFontWidth() {
return fontWidth;
}
public void setFontWidth(float fontWidth) {
this.fontWidth = fontWidth;
}
public float getFontHeight() {
return fontHeight;
}
public void setFontHeight(float fontHeight) {
this.fontHeight = fontHeight;
}
@Override
public String toString() {
return "MatchItem{" +
"pageNum=" + pageNum +
", x=" + x +
", y=" + y +
", pageWidth=" + pageWidth +
", pageHeight=" + pageHeight +
", content='" + content + '\'' +
'}';
}
}
import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import java.util.ArrayList;
import java.util.List;
/**
* 用来匹配pdf的关键词 监听类
*/
public class KeyWordPositionListener implements RenderListener {
//存放匹配上的字符信息
private List<MatchItem> matches = new ArrayList<MatchItem>();
//存放所有的字符信息
private List<MatchItem> allItems = new ArrayList<MatchItem>();
private Rectangle curPageSize;
/**
* 匹配的关键字
*/
private String keyword;
/**
* 匹配的当前页
*/
private Integer pageNumber;
@Override
public void beginTextBlock() {
//do nothing
}
@Override
public void renderText(TextRenderInfo renderInfo) {
//获取字符
String content = renderInfo.getText();
Rectangle2D.Float textRectangle = renderInfo.getDescentLine().getBoundingRectange();
MatchItem item = new MatchItem();
item.setContent(content);
item.setPageNum(pageNumber);
item.setFontHeight(textRectangle.height == 0 ? 12:textRectangle.height);//默认12
item.setFontWidth(textRectangle.width);
item.setPageHeight(curPageSize.getHeight());
item.setPageWidth(curPageSize.getWidth());
item.setX((float)textRectangle.getX());
item.setY((float)textRectangle.getY());
//若keyword是单个字符,匹配上的情况
if(content.equalsIgnoreCase(keyword)) {
matches.add(item);
}
//保存所有的项
allItems.add(item);
}
@Override
public void endTextBlock() {
//do nothing
}
@Override
public void renderImage(ImageRenderInfo renderInfo) {
//do nothing
}
/**
* 设置需要匹配的当前页
* @param pageNumber
*/
public void setPageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
}
/**
* 设置需要匹配的关键字,忽略大小写
* @param keyword
*/
public void setKeyword(String keyword) {
this.keyword = keyword;
}
/**
* 返回匹配的结果列表
* @return
*/
public List<MatchItem> getMatches() {
return matches;
}
void setCurPageSize(Rectangle rect) {
this.curPageSize = rect;
}
public List<MatchItem> getAllItems() {
return allItems;
}
public void setAllItems(List<MatchItem> allItems) {
this.allItems = allItems;
}
}
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Font;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* pdf替换文字工具类
*
* 思路:
* 1.逐页搜索关键字,逐页匹配
* 2.先读取一页的所有字符信息,存放到allItems中
* 3.把一页的字符拼接成字符串,然后匹配关键字,匹配上,记录匹配的第一个字符的MatchItem信息;匹配不是,继续下一页匹配
* 4.根据匹配字符串的长度和字符的宽高信息画遮罩层,然后替换文字生成新的pdf文件
*
* 不足之处:
* 1.目前只支持单字符串匹配
* 2.替换之后的文字无法和原pdf中替换掉的文字信息一致(主要有:字体大小、样式等)
* 3.某些情况下(主要是替换字体的大小)替换之后显示不是太整齐
* 4.字体大小、样式无法把控
* 5.无法匹配目标文字在两页中显示的情况(例如:目标文字:替换工具,第一页页尾有替换两字,第二页页首有工具二字)
*
*/
public class PdfUtils {
public static void main(String[] args) throws Exception{
PdfService pdfService = new PdfService();
PdfReader reader = new PdfReader("pdf/myFile.pdf");
//PdfStamper stamper = new PdfStamper(reader,resopnse.getOutputStream); 下载用的
PdfStamper stamper = new PdfStamper(reader,new FileOutputStream("result.pdf"));//如果不存在会自动生成这个文件的
pdfReplace(reader,"pdf/myFile.pdf","要替换的字符串","代替的新字符串",stamper);
pdfReplace(reader,"pdf/myFile.pdf","要替换的字符串1","代替的新字符串1",stamper);
stamper.close();
reader.close();
}
/**
* 根据关键字和pdf路径,全文搜索关键字
* @param filePath pdf目标路径
* @param keyword 关键字
* @return
* @throws Exception
*/
public static List<MatchItem> matchAll(PdfReader reader, String keyword) throws Exception {
List<MatchItem> items = new ArrayList<MatchItem>();
//获取pdf页数
int pageSize = reader.getNumberOfPages();
//逐页匹配关键字
for(int page = 1;page <= pageSize;page++){
items.addAll(matchPage(reader,page,keyword));
}
return items;
}
/**
* 根据关键字、文档路径、pdf页数寻找特定的文件内容
* @param reader
* @param pageNumber 页数
* @param keyword 关键字
* @return
* @throws Exception
*/
public static List<MatchItem> matchPage(PdfReader reader, Integer pageNumber,String keyword) throws Exception {
PdfReaderContentParser parse = new PdfReaderContentParser(reader);
Rectangle rectangle = reader.getPageSize(pageNumber);
//匹配监听
KeyWordPositionListener renderListener = new KeyWordPositionListener();
renderListener.setKeyword(keyword);
renderListener.setPageNumber(pageNumber);
renderListener.setCurPageSize(rectangle);
parse.processContent(pageNumber, renderListener);
return findKeywordItems(renderListener,keyword);
}
/**
* 找到匹配的关键词块
* @param renderListener
* @param keyword
* @return
*/
public static List<MatchItem> findKeywordItems(KeyWordPositionListener renderListener,String keyword){
//先判断本页中是否存在关键词
List<MatchItem> allItems = renderListener.getAllItems();//所有块LIST
StringBuffer sbtemp = new StringBuffer("");
for(MatchItem item : allItems){//将一页中所有的块内容连接起来组成一个字符串。
sbtemp.append(item.getContent());
}
List<MatchItem> matches = renderListener.getMatches();
//一页组成的字符串没有关键词,直接return
//第一种情况:关键词与块内容完全匹配的项,直接返回
if(sbtemp.toString().indexOf(keyword) == -1 || matches.size() > 0){
return matches;
}
//第二种情况:多个块内容拼成一个关键词,则一个一个来匹配,组装成一个关键词
sbtemp = new StringBuffer("");
List<MatchItem> tempItems = new ArrayList();
for(MatchItem item : allItems){
if(keyword.indexOf(item.getContent()) != -1 ){
tempItems.add(item);
sbtemp.append(item.getContent());
if(keyword.indexOf(sbtemp.toString()) == -1){//如果暂存的字符串和关键词 不再匹配时
sbtemp = new StringBuffer(item.getContent());
tempItems.clear();
tempItems.add(item);
}
if(sbtemp.toString().equalsIgnoreCase(keyword)){//暂存的字符串正好匹配到关键词时
matches.add(tempItems.get(0));//得到匹配的项
sbtemp = new StringBuffer("");//清空暂存的字符串
tempItems.clear();//清空暂存的LIST
continue;//继续查找
}
}else{//如果找不到则清空
sbtemp = new StringBuffer("");
tempItems.clear();
}
}
return matches;
}
/**
* 替换目标文字,生成新的pdf文件
* @param src 目标pdf路径
* @param dest 新pdf的路径
* @throws Exception
*/
public static void manipulatePdf(List<MatchItem> matchItems,String keyWord,String keyWordNew,PdfStamper stamper) throws Exception{
PdfContentByte canvas = null;
Map<Integer,List<MatchItem>> mapItem = new HashMap<Integer,List<MatchItem>>();
List<MatchItem> itemList = null;
for(MatchItem item : matchItems){
Integer pageNum = item.getPageNum();
if(mapItem.containsKey(pageNum)){
itemList = mapItem.get(pageNum);
itemList.add(item);
mapItem.put(pageNum,itemList);
}else{
itemList = new ArrayList<MatchItem>();
itemList.add(item);
mapItem.put(pageNum,itemList);
}
}
//遍历每一页去修改
for(Integer page : mapItem.keySet()){
List<MatchItem> items = mapItem.get(page);
//遍历每一页中的匹配项
for(MatchItem item : items){
canvas = stamper.getOverContent(page);
float x = item.getX();
float y = item.getY();
float fontWidth = item.getFontWidth();
float fontHeight = item.getFontHeight();
canvas.saveState();
canvas.setColorFill(BaseColor.WHITE);
canvas.rectangle(x, y,fontWidth*keyWord.length(),fontWidth+2);
canvas.fill();
canvas.restoreState();
//开始写入文本
canvas.beginText();
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
Font font = new Font(bf,fontWidth,Font.BOLD);
//设置字体和大小
canvas.setFontAndSize(font.getBaseFont(), fontWidth);
//设置字体的输出位置
canvas.setTextMatrix(x, y+fontWidth/10+0.5f);
//要输出的text
canvas.showText(keyWordNew);
canvas.endText();
}
}
System.out.println("complete");
}
/**
* 替换pdf中指定文字
* @param src 目标pdf路径
* @param dest 新pdf的路径
* @param keyWord 替换的文字
* @param keyWordNew 替换后的文字
* @throws Exception
*/
public static void pdfReplace(PdfReader reader String orgin,String keyWord,PdfStamper stamper) throws Exception{
manipulatePdf(matchAll(reader,orgin),orgin,keyWord,stamper);
}
}
扩展:
查找pdf内容,并定位该关键字所在当前页的位置:
引入依赖:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.1.12</version>
<type>pom</type>
</dependency>
实现代码
package com.example.demo.pdf;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.parser.PdfDocumentContentParser;
import com.itextpdf.kernel.pdf.canvas.parser.listener.IPdfTextLocation;
import com.itextpdf.kernel.pdf.canvas.parser.listener.RegexBasedLocationExtractionStrategy;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import java.util.Collection;
public class ImageInPDF {
public static void main(String args[]) {
String input = "C:\\Users\\wjw\\Desktop\\test1.pdf";
//通过指定pdf文件名,指定关键字,和指定的pdf文件的待处理页数做参数
getKeyWordsLocation(input, "王建伟", 1);
}
/**
* 得到关键字位置
* @param input 源文件
* @param KEY_WORD 关键字
* @param pageNum 文档页数
*/
public static void getKeyWordsLocation(String input, String KEY_WORD, int pageNum) {
RegexBasedLocationExtractionStrategy strategy = new RegexBasedLocationExtractionStrategy(KEY_WORD);
try {
//得到需要插入的图片
ImageData imageData = ImageDataFactory.create("C:\\Users\\wjw\\Desktop\\主页.png");
//核心思路为对PdfDocument对象采用某种Strategy,这里使用RegexBasedLocationExtractionStrategy
PdfReader pr = new PdfReader(input);
//生成新的PDF文件
PdfDocument pd = new PdfDocument(pr, new PdfWriter("C:\\Users\\wjw\\Desktop\\test.pdf"));
//int pageNum = pd.getNumberOfPages();获取新pdf的总页数
Document document = new Document(pd);
PdfDocumentContentParser pdcp = new PdfDocumentContentParser(pd);
//文本内容具体解析借助使用PdfDocumentContentParser类(实质使用PdfCanvasProcessor进行处理), 对待处理页面装配合适策略
RegexBasedLocationExtractionStrategy regexStrategy = pdcp.processContent(pageNum, strategy);
//获取处理结果
Collection<IPdfTextLocation> resultantLocations = strategy.getResultantLocations();
//自定义结果处理
if (!resultantLocations.isEmpty()) {
for (IPdfTextLocation item : resultantLocations) {
Rectangle boundRectangle = item.getRectangle();
System.out.println(item.getText());
System.out.println("关键字“" + KEY_WORD + "” 的坐标为 x: " + boundRectangle.getX() + " ,y: " + boundRectangle.getY());
Image image = new Image(imageData).scaleAbsolute(70, 40).setFixedPosition(pageNum, boundRectangle.getRight() + 5f, boundRectangle.getBottom());
document.add(image);
}
document.close();
} else {
System.out.println("结果为空");
}
pr.close();
pd.close();
} catch (Exception e) {
System.err.println("读取文件失败!");
e.printStackTrace();
}
}
}