背景
最近研究电子合同的签约。遇到了Pdf模板的文字替换的问题。因为是标准合同,所以只需要替换其中的文本就可以。
思路
首先找到相关的关键字,在关键字上先放一个蒙层。再用新的内容盖住旧的文字。
引入PDFBox
implementation 'org.apache.pdfbox:pdfbox:3.0.1'
第二步
需要找到我们要替换的文字的位置。这里需要扩展 PDFTextStripper
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SearchPositionPDFTextStripper extends PDFTextStripper {
private final List<String> keyWordList;
private final List<SearchResult> list = new ArrayList<>();
public SearchPositionPDFTextStripper(List<String> keyWordList) throws IOException {
this.keyWordList = keyWordList;
}
@Override
protected void writeString(String text, List<TextPosition> positions) {
for (String keyWord : keyWordList) {
int index = text.indexOf(keyWord);
if(index>=0) {
SearchResult entity = new SearchResult();
entity.setKeyWord(keyWord);
TextPosition startPosition = positions.get(index);
TextPosition endPosition = positions.get(index+keyWord.length()-1);
Matrix matrix = startPosition.getTextMatrix();
float x = matrix.getTranslateX();
float y = matrix.getTranslateY();
// 获取结束字符坐标
Matrix endMatrix = endPosition.getTextMatrix();
float x2 = endMatrix.getTranslateX();
// 获取字体大小
float fontSizeInPt = startPosition.getFontSizeInPt();
entity.setFont(startPosition.getFont());
entity.setX(x);
entity.setFontSize(startPosition.getFontSize());
entity.setY(y - fontSizeInPt / 5);
float width = x2 - x + startPosition.getFontSizeInPt();
entity.setWidth(width);
entity.setHeight(fontSizeInPt);
this.getKeyWordEntityList().add(entity);
}
}
}
public List<SearchResult> getSearchResult() {
return list;
}
}
这个类用于结果的保存
import org.apache.pdfbox.pdmodel.font.PDFont;
public class SearchResult {
private String keyWord;
private float x; //记录查找到的文本所在的x坐标点
private float y; //记录查找到的文本所在的y坐标点
private float width; //记录查找到的文本的宽
private float height; //记录查找到的文本的高
private PDFont font;
private float fontSize;
public float getFontSize() {
return fontSize;
}
public void setFontSize(float fontSize) {
this.fontSize = fontSize;
}
public PDFont getFont() {
return font;
}
public void setFont(PDFont font) {
this.font = font;
}
public String getKeyWord() {
return keyWord;
}
public void setKeyWord(String keyWord) {
this.keyWord = keyWord;
}
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 getWidth() {
return width;
}
public void setWidth(float width) {
this.width = width;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
}
有了这两个类,我们就可以在PDF文档里查找文字了。我们用下边这个方法来查找。
File file = new File("1.pdf");
try(PDDocument document = Loader.loadPDF(file)) {
SearchPositionPDFTextStripper stripper = new SearchPositionPDFTextStripper();
stripper.setSortByPosition(true);
stripper.getText(document);
List<SearchResult> list = stripper.getSearchResult();
//在这里做替换
//1. 先把字体加进来
ClassPathResource resource = new ClassPathResource("SimSun.ttf");
PDFont font = PDType0Font.load(document, resource.getInputStream(),true);
PDPage page = document.getPage(0);
//2. 新建一个内容流
PDPageContentStream stream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
for (SearchResult item : list) {
//3. 这里是做了一个填图型的遮盖。如果只是把文字抹去,这里看上去是空白的,还可以把原来的文字内容拷出去。所以一定要重新写过替换的内容
stream.setNonStrokingColor(Color.WHITE);
stream.addRect(item.getX(), page.getArtBox().getHeight() - item.getY() - item.getHeight(), item.getWidth(), item.getHeight());
stream.fill();
//4. 这里就开始做文字替换了
stream.setNonStrokingColor(Color.BLACK);
String replace = "";
for(int i=0;i<item.getKeyWord.length;i++) {
replace = replace + "*"
}
writeText(stream,replace,font,item.getX(), page.getArtBox().getHeight() - item.getY() - item.getHeight(), item.getFontSize());
}
//5. 都替换完成了。这里把结果保存成另外一个PDF就完成了
stream.close();
document.save("3.pdf");
} catch (IOException e) {
throw new RuntimeException(e);
}
writeText方法的代码
private void writeText(PDPageContentStream contents, String text, PDFont font, float x, float y,float fontSize) throws IOException {
contents.moveTo(x, y);
contents.beginText();
contents.setFont(font, 12);
contents.newLineAtOffset(x, y);
contents.showText(text);
contents.endText();
}
待解决的问题
- 当引用原有字体时,会产生下边的错误
java.lang.IllegalArgumentException: No glyph for U+0020 ( ) in font MLJNAR+simsun
- 这个方法,只能做同样数量的字符替换,另外当字符串宽度不一致的时候也可能会出现问题。