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