itext没有提供直接替换PDF文本的接口,我们可以通过在原有的文本区域覆盖一个遮挡层,再在上面加上文本来实现。
所需jar包:
1.先在PDF需要替换的位置覆盖一个白色遮挡层(颜色可根据PDF文字背景色自行定义)
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* <p>PDF增加遮盖层<p>
* @version 1.0
* @author li_hao
* @date 2018年3月14日
*/
public class HighLightByAddingContent {
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfContentByte canvas = stamper.getUnderContent(1); //不可遮挡文字
// PdfContentByte canvas = stamper.getOverContent(1); //可以遮挡文字
canvas.saveState();
canvas.setColorFill(BaseColor.YELLOW); //黄色遮挡层
// canvas.setColorFill(BaseColor.WHITE); //白色遮挡层
canvas.rectangle(116, 726, 66, 16);
canvas.fill();
canvas.restoreState();
stamper.close();
reader.close();
}
/**
* 测试
*/
public static void main(String[] args) throws IOException, DocumentException {
String SRC = "D:/testpdf/test.pdf";
String DEST = "D:/testpdf/test2.pdf";
File file = new File(DEST);
file.getParentFile().mkdirs();
new HighLightByAddingContent().manipulatePdf(SRC, DEST);
}
}
测试结果(不遮挡文字+黄色背景):
测试结果(遮挡文字+白色背景):
2. PDF增加遮盖层+文字
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* <p>PDF增加遮盖层+文字<p>
* @version 1.0
* @author li_hao
* @date 2018年3月13日
*/
public class HighLightByAddingContent {
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
//添加一个遮挡处,可以把原内容遮住,后面在上面写入内容
// PdfContentByte canvas = stamper.getUnderContent(1); //不可以遮挡文字
PdfContentByte canvas = stamper.getOverContent(1); //可以遮挡文字
float height=780;
System.out.println(canvas.getHorizontalScaling());
float x,y;
x= 116;
y = height -49.09F;
canvas.saveState();
canvas.setColorFill(BaseColor.YELLOW); //遮挡层颜色:黄色
// canvas.setColorFill(BaseColor.WHITE); //遮挡层颜色:白色
canvas.rectangle(x, y-5, 43, 15);
canvas.fill();
canvas.restoreState();
//开始写入文本
canvas.beginText();
//BaseFont bf = BaseFont.createFont(URLDecoder.decode(CutAndPaste.class.getResource("/AdobeSongStd-Light.otf").getFile()), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
Font font = new Font(bf,10,Font.BOLD);
//设置字体和大小
canvas.setFontAndSize(font.getBaseFont(), 10);
//设置字体的输出位置
canvas.setTextMatrix(x, y);
//要输出的text
canvas.showText("我是替换" );
canvas.endText();
stamper.close();
reader.close();
System.out.println("complete");
}
/**
* 测试
*/
public static void main(String[] args) throws IOException, DocumentException {
String SRC = "D:/testpdf/test.pdf";
String DEST = "D:/testpdf/test_a.pdf";
File file = new File(DEST);
file.getParentFile().mkdirs();
new HighLightByAddingContent().manipulatePdf(SRC, DEST);
}
}
测试结果:
下面是自动查找旧文字替换新文字完整代码:
1. 需要替换的区域
/**
* <p>需要替换的区域 <p>
* @version 1.0
* @author li_hao
* @date 2018年3月14日
*/
public class ReplaceRegion {
private String aliasName;
private Float x;
private Float y;
private Float w;
private Float h;
public ReplaceRegion(String aliasName){
this.aliasName = aliasName;
}
/**
* 替换区域的别名
* @return
*/
public String getAliasName() {
return aliasName;
}
public void setAliasName(String aliasName) {
this.aliasName = aliasName;
}
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 getW() {
return w;
}
public void setW(Float w) {
this.w = w;
}
public Float getH() {
return h;
}
public void setH(Float h) {
this.h = h;
}
}
2. 解析PDF中文本的x,y位置
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
/**
* <p>解析PDF中文本的x,y位置<p>
* @version 1.0
* @author li_hao
* @date 2018年3月14日
*/
public class PdfPositionParse {
private PdfReader reader;
private List<String> findText = new ArrayList<String>(); //需要查找的文本
private PdfReaderContentParser parser;
public PdfPositionParse(String fileName) throws IOException{
FileInputStream in = null;
try{
in =new FileInputStream(fileName);
byte[] bytes = new byte[in.available()];
in.read(bytes);
init(bytes);
}finally{
in.close();
}
}
public PdfPositionParse(byte[] bytes) throws IOException{
init(bytes);
}
private boolean needClose = true;
/**
* 传递进来的reader不会在PdfPositionParse结束时关闭
* @param reader
*/
public PdfPositionParse(PdfReader reader){
this.reader = reader;
parser = new PdfReaderContentParser(reader);
needClose = false;
}
public void addFindText(String text){
this.findText.add(text);
}
private void init(byte[] bytes) throws IOException {
reader = new PdfReader(bytes);
parser = new PdfReaderContentParser(reader);
}
/**
* 解析文本
* @return
* @throws IOException
*/
public Map<String, ReplaceRegion> parse() throws IOException{
try{
if(this.findText.size() == 0){
throw new NullPointerException("没有需要查找的文本");
}
PositionRenderListener listener = new PositionRenderListener(this.findText);
parser.processContent(1, listener);
return listener.getResult();
}finally{
if(reader != null && needClose){
reader.close();
}
}
}
}
3. PDF渲染监听,当找到渲染的文本时,得到文本的坐标x,y,w,h
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.itextpdf.awt.geom.Rectangle2D.Float;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
/**
* <p>pdf渲染监听,当找到渲染的文本时,得到文本的坐标x,y,w,h<p>
* @version 1.0
* @author li_hao
* @date 2018年3月14日
*/
public class PositionRenderListener implements RenderListener{
private List<String> findText;
private float defaultH; ///出现无法取到值的情况,默认为12
private float fixHeight; //可能出现无法完全覆盖的情况,提供修正的参数,默认为2
public PositionRenderListener(List<String> findText, float defaultH,float fixHeight) {
this.findText = findText;
this.defaultH = defaultH;
this.fixHeight = fixHeight;
}
public PositionRenderListener(List<String> findText) {
this.findText = findText;
this.defaultH = 12;
this.fixHeight = 2;
}
@Override
public void beginTextBlock() {
}
@Override
public void endTextBlock() {
}
@Override
public void renderImage(ImageRenderInfo imageInfo) {
}
private Map<String, ReplaceRegion> result = new HashMap<String, ReplaceRegion>();
@Override
public void renderText(TextRenderInfo textInfo) {
String text = textInfo.getText();
for (String keyWord : findText) {
if (null != text && text.equals(keyWord)){
Float bound = textInfo.getBaseline().getBoundingRectange();
ReplaceRegion region = new ReplaceRegion(keyWord);
region.setH(bound.height == 0 ? defaultH : bound.height);
region.setW(bound.width);
region.setX(bound.x);
region.setY(bound.y-this.fixHeight);
result.put(keyWord, region);
}
}
}
public Map<String, ReplaceRegion> getResult() {
for (String key : findText) { //补充没有找到的数据
if(this.result.get(key) == null){
this.result.put(key, null);
}
}
return this.result;
}
}
4. 替换PDF文件某个区域内的文本
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
/**
* <p>替换PDF文件某个区域内的文本 <p>
* @version 1.0
* @author li_hao
* @date 2018年3月14日
*/
public class PdfReplacer {
private static final Logger logger = LoggerFactory.getLogger(PdfReplacer.class);
private int fontSize;
private Map<String, ReplaceRegion> replaceRegionMap = new HashMap<String, ReplaceRegion>();
private Map<String, Object> replaceTextMap =new HashMap<String, Object>();
private ByteArrayOutputStream output;
private PdfReader reader;
private PdfStamper stamper;
private PdfContentByte canvas;
private Font font;
public PdfReplacer(byte[] pdfBytes) throws DocumentException, IOException{
init(pdfBytes);
}
public PdfReplacer(String fileName) throws IOException, DocumentException{
FileInputStream in = null;
try{
in =new FileInputStream(fileName);
byte[] pdfBytes = new byte[in.available()];
in.read(pdfBytes);
init(pdfBytes);
}finally{
in.close();
}
}
private void init(byte[] pdfBytes) throws DocumentException, IOException{
logger.info("初始化开始");
reader = new PdfReader(pdfBytes);
output = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, output);
canvas = stamper.getOverContent(1);
setFont(10);
logger.info("初始化成功");
}
private void close() throws DocumentException, IOException{
if(reader != null){
reader.close();
}
if(output != null){
output.close();
}
output=null;
replaceRegionMap=null;
replaceTextMap=null;
}
public void replaceText(float x, float y, float w,float h, String text){
ReplaceRegion region = new ReplaceRegion(text); //用文本作为别名
region.setH(h);
region.setW(w);
region.setX(x);
region.setY(y);
addReplaceRegion(region);
this.replaceText(text, text);
}
public void replaceText(String name, String text){
this.replaceTextMap.put(name, text);
}
/**
* 替换文本
* @throws DocumentException
* @throws IOException
*/
private void process() throws DocumentException, IOException{
try{
parseReplaceText();
canvas.saveState();
Set<Entry<String, ReplaceRegion>> entrys = replaceRegionMap.entrySet();
for (Entry<String, ReplaceRegion> entry : entrys) {
ReplaceRegion value = entry.getValue();
canvas.setColorFill(BaseColor.WHITE);
canvas.rectangle(value.getX(),value.getY(),value.getW(),value.getH());
}
canvas.fill();
canvas.restoreState();
//开始写入文本
canvas.beginText();
for (Entry<String, ReplaceRegion> entry : entrys) {
ReplaceRegion value = entry.getValue();
//设置字体
canvas.setFontAndSize(font.getBaseFont(), getFontSize());
canvas.setTextMatrix(value.getX(),value.getY()+2/*修正背景与文本的相对位置*/);
canvas.showText((String) replaceTextMap.get(value.getAliasName()));
}
canvas.endText();
}finally{
if(stamper != null){
stamper.close();
}
}
}
/**
* 未指定具体的替换位置时,系统自动查找位置
*/
private void parseReplaceText() {
PdfPositionParse parse = new PdfPositionParse(reader);
Set<Entry<String, Object>> entrys = this.replaceTextMap.entrySet();
for (Entry<String, Object> entry : entrys) {
if(this.replaceRegionMap.get(entry.getKey()) == null){
parse.addFindText(entry.getKey());
}
}
try {
Map<String, ReplaceRegion> parseResult = parse.parse();
Set<Entry<String, ReplaceRegion>> parseEntrys = parseResult.entrySet();
for (Entry<String, ReplaceRegion> entry : parseEntrys) {
if(entry.getValue() != null){
this.replaceRegionMap.put(entry.getKey(), entry.getValue());
}
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
/**
* 生成新的PDF文件
* @param fileName
* @throws DocumentException
* @throws IOException
*/
public void toPdf(String fileName) throws DocumentException, IOException{
FileOutputStream fileOutputStream = null;
try{
process();
fileOutputStream = new FileOutputStream(fileName);
fileOutputStream.write(output.toByteArray());
fileOutputStream.flush();
}catch(IOException e){
logger.error(e.getMessage(), e);
throw e;
}finally{
if(fileOutputStream != null){
fileOutputStream.close();
}
close();
}
logger.info("文件生成成功");
}
/**
* 将生成的PDF文件转换成二进制数组
* @return
* @throws DocumentException
* @throws IOException
*/
public byte[] toBytes() throws DocumentException, IOException{
try{
process();
logger.info("二进制数据生成成功");
return output.toByteArray();
}finally{
close();
}
}
/**
* 添加替换区域
* @param replaceRegion
*/
public void addReplaceRegion(ReplaceRegion replaceRegion){
this.replaceRegionMap.put(replaceRegion.getAliasName(), replaceRegion);
}
/**
* 通过别名得到替换区域
* @param aliasName
* @return
*/
public ReplaceRegion getReplaceRegion(String aliasName){
return this.replaceRegionMap.get(aliasName);
}
public int getFontSize() {
return fontSize;
}
/**
* 设置字体大小
* @param fontSize
* @throws DocumentException
* @throws IOException
*/
public void setFont(int fontSize) throws DocumentException, IOException{
if(fontSize != this.fontSize){
this.fontSize = fontSize;
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); //使用iTextAsian.jar中的字体
// BaseFont bf = BaseFont.createFont("C:/WINDOWS/Fonts/STSONG.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED); //使用Windows系统字体(TrueType)
// BaseFont bf = BaseFont.createFont("/STSONG.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED); //使用资源字体(ClassPath)
font = new Font(bf,this.fontSize,Font.BOLD);
}
}
public void setFont(Font font){
if(font == null){
throw new NullPointerException("font is null");
}
this.font = font;
}
/**
* 测试
*/
public static void main(String[] args) throws IOException, DocumentException {
PdfReplacer textReplacer = new PdfReplacer("D:/testpdf/test.pdf");
textReplacer.replaceText("我是测试名称", "李大锤李大锤李大锤");
textReplacer.replaceText("我是测试内容", "张三丰张三丰张三丰");
textReplacer.toPdf("D:/testpdf/test3.pdf");
}
}
测试结果:
以上就是java使用itext替换PDF中的文本的代码,在替换文本时可以自动查找文本的内容,当然也可以自定义坐标区域替换。
注:如果不好确定替换区域的坐标位置,而PDF对应区域又是空白的话,可以在生成PDF的时候,在需要替换的区域先写入“特定文字内容”,预设的文字颜色设置和背景色相同,这样PDF看起来似乎是空的,但是却可以用查找文本的方式替换,替换后的就能显示出替换后的内容了,这样有时候可以方便很多。
附:替换后的文本在PC端、Android端、iOS端都是可以打开和使用,在HTML5开发时使用PDF.js插件时发现替换后的字体不显示的情况,这是由于PDF.js插件不识别iTextAsign.jar中的字体,把:BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED)做下修改,可以使用Windows中的字体:BaseFont bf = BaseFont.createFont("C:/WINDOWS/Fonts/STSONG.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED),字体和路径路径可以自行设置,linux中先安装中文字体库,再使用相应字体的路径即可。替换字体后,网页中使用PDF.js插件也没问题了。
源码:https://github.com/piaoliudelaoyaoguai/pdfReplaceWordsDemo.git