使用itext直接替换PDF中的文本

直接说问题,itext没有直接提供替换PDF中文本的接口(查看资料得到的结论是PDF不支持这种操作),不过存在解决思路:在需要替换的文本上覆盖新的文本。按照这个思路我们需要解决以下几个问题:

  • itext怎样增加白色底的覆盖层
  • 找到覆盖层的位置(左顶点的位置)和高度与宽带
这样做的目的是什么了?也告诉下大家,比如:现在要你将业务数据导出成PDF存档,且PDF的模板有现成的。对我们写程序的来说,变化的只是部分数据,假如我们可以直接替换里面的数据,是不是可以节省我们的开发时间。

1、itext怎样增加覆盖层?

itext在自己的Demo中提供了很多案例代码,从中我们可以看到高亮的案例
查看itext代码
[java] view plain copy
 
  1. /* 
  2.  * This example was written in answer to the question: 
  3.  * http://stackoverflow.com/questions/33952183 
  4.  */  
  5. package sandbox.stamper;  
  6.    
  7. import com.itextpdf.text.BaseColor;  
  8. import com.itextpdf.text.DocumentException;  
  9. import com.itextpdf.text.pdf.PdfContentByte;  
  10. import com.itextpdf.text.pdf.PdfReader;  
  11. import com.itextpdf.text.pdf.PdfStamper;  
  12. import java.io.File;  
  13. import java.io.FileOutputStream;  
  14. import java.io.IOException;  
  15.    
  16. /** 
  17.  * 
  18.  * @author Bruno Lowagie (iText Software) 
  19.  */  
  20. public class HighLightByAddingContent {  
  21.    
  22.     public static final String SRC = "resources/pdfs/hello.pdf";  
  23.     public static final String DEST = "results/stamper/hello_highlighted.pdf";  
  24.     public static void main(String[] args) throws IOException, DocumentException {  
  25.         File file = new File(DEST);  
  26.         file.getParentFile().mkdirs();  
  27.         new HighLightByAddingContent().manipulatePdf(SRC, DEST);  
  28.     }  
  29.    
  30.     public void manipulatePdf(String src, String dest) throws IOException, DocumentException {  
  31.         PdfReader reader = new PdfReader(src);  
  32.         PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));  
  33.         PdfContentByte canvas = stamper.getUnderContent(1);  
  34.         canvas.saveState();  
  35.         canvas.setColorFill(BaseColor.YELLOW);  
  36.         canvas.rectangle(36, 786, 66, 16);  
  37.         canvas.fill();  
  38.         canvas.restoreState();  
  39.         stamper.close();  
  40.         reader.close();  
  41.     }  
  42. }  
 这里可以在任意位置产生一个层,符合我们的“遮盖层”的要求,不过,通过测试发现此段代码存在一个问题点,它无法遮挡住文字,只是添加了一个背景层。为了达到我们的要求,我们只需要修改一处地方:
[java] view plain copy
 
  1. PdfContentByte canvas = stamper.getUnderContent(1);  //变成 PdfContentByte canvas = stamper.getOverContent(1);  
到目前为止,我们的遮盖层已添加,后面我们还需要的就是在新的遮盖层上写上自己的文字,代码如下:
[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : HighLightByAddingContent.java 
  4.  * CLASS : HighLightByAddingContent 
  5.  * 
  6.  * 
  7.  * FUNCTION : TODO 
  8.  * 
  9.  * 
  10.  *====================================================================== 
  11.  * CHANGE HISTORY LOG 
  12.  *---------------------------------------------------------------------- 
  13.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  14.  *---------------------------------------------------------------------- 
  15.  *       
  16.  * DESCRIPTION: 
  17.  * </pre> 
  18.  ***********************************************************************/  
  19.   
  20. package com.cx.itext;  
  21.   
  22. import java.io.File;  
  23. import java.io.FileOutputStream;  
  24. import java.io.IOException;  
  25. import java.net.URLDecoder;  
  26.   
  27. import com.itextpdf.text.BaseColor;  
  28. import com.itextpdf.text.DocumentException;  
  29. import com.itextpdf.text.Font;  
  30. import com.itextpdf.text.pdf.BaseFont;  
  31. import com.itextpdf.text.pdf.PdfContentByte;  
  32. import com.itextpdf.text.pdf.PdfReader;  
  33. import com.itextpdf.text.pdf.PdfStamper;  
  34.   
  35. public class HighLightByAddingContent {  
  36.   
  37.    @SuppressWarnings("deprecation")  
  38.    public static final String SRC = URLDecoder.decode(HighLightByAddingContent.class.getResource("ticket.pdf").getFile());  
  39.    public static final String DEST = "I://ticket.pdf";  
  40.    public static void main(String[] args) throws IOException, DocumentException {  
  41.        File file = new File(DEST);  
  42.        file.getParentFile().mkdirs();  
  43.        new HighLightByAddingContent().manipulatePdf(SRC, DEST);  
  44.    }  
  45.   
  46.    public void manipulatePdf(String src, String dest) throws IOException, DocumentException {  
  47.        PdfReader reader = new PdfReader(src);  
  48.        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));  
  49.        PdfContentByte canvas = stamper.getOverContent(1);  
  50.        float height=595;  
  51.        System.out.println(canvas.getHorizontalScaling());  
  52.        float x,y;  
  53.        x= 216;  
  54.        y = height -49.09F;  
  55.        canvas.saveState();  
  56.        canvas.setColorFill(BaseColor.WHITE);  
  57.        canvas.rectangle(x, y-5, 43, 15);  
  58.          
  59.        canvas.fill();  
  60.        canvas.restoreState();  
  61.      //开始写入文本   
  62.        canvas.beginText();   
  63.        //BaseFont bf = BaseFont.createFont(URLDecoder.decode(CutAndPaste.class.getResource("/AdobeSongStd-Light.otf").getFile()), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);  
  64.        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);  
  65.        Font font = new Font(bf,10,Font.BOLD);   
  66.        //设置字体和大小   
  67.        canvas.setFontAndSize(font.getBaseFont(), 10);    
  68.        //设置字体的输出位置   
  69.        canvas.setTextMatrix(x, y);    
  70.        //要输出的text   
  71.        canvas.showText("多退少补" );     
  72.          
  73.        //设置字体的输出位置   
  74.        canvas.setFontAndSize(font.getBaseFont(), 20);    
  75.        canvas.setTextMatrix(x, y-90);    
  76.        //要输出的text   
  77.        canvas.showText("多退少补" );     
  78.          
  79.        canvas.endText();  
  80.        stamper.close();  
  81.        reader.close();  
  82.        System.out.println("complete");  
  83.    }  
  84. }  

2、找到覆盖层的位置(左顶点的位置)和高度与宽带

我的第一个想法是通过工具得到替换文本的具体位置,虽然这个方法不怎么好,不过确实可行。使用到的工具是常用的Adobe Reader,以下是正常页面(PDF是网上搜的,百度key:“申请 filetype:pdf”):
 
Adobe提供了测量工具,我们可以通过“编辑-->分析-->测量工具”看到如下页面:
此时,我们虽然可以直接测量,但是测量默认显示的厘米,与itext需要设置的单位不一致,我们需要手工换算下(1英寸=72点)。不过,adobe可以帮我们省掉换算的工作,右键点击,出现以下选项(需要在测量功能下右键):
“更改比例”可以帮助我们完成换算工作。(ps:“显示标尺”是一个不错的选项)。最后的画面如下:
最后,需要提醒下,itext的Y是从下往上算的。
 
这样得到位置是不是太不方便了。那我们是否可以通过itext自动计算出我们需要的位置?代码如下(从网上COPY,不记得具体来源,支持作者)
[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : Demo.java 
  4.  * CLASS : Demo 
  5.  * 
  6.  * AUTHOR : caoxu-yiyang@qq.com 
  7.  * 
  8.  * FUNCTION : TODO 
  9.  * 
  10.  * 
  11.  *====================================================================== 
  12.  * CHANGE HISTORY LOG 
  13.  *---------------------------------------------------------------------- 
  14.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  15.  *---------------------------------------------------------------------- 
  16.  *          |2016年11月9日|caoxu-yiyang@qq.com| Created | 
  17.  * DESCRIPTION: 
  18.  * </pre> 
  19.  ***********************************************************************/  
  20.   
  21. package com.cx.itext;  
  22.   
  23. import java.io.IOException;  
  24. import com.itextpdf.awt.geom.Rectangle2D.Float;  
  25. import com.itextpdf.text.pdf.PdfReader;  
  26. import com.itextpdf.text.pdf.parser.ImageRenderInfo;  
  27. import com.itextpdf.text.pdf.parser.PdfReaderContentParser;  
  28. import com.itextpdf.text.pdf.parser.RenderListener;  
  29. import com.itextpdf.text.pdf.parser.TextRenderInfo;  
  30.   
  31. public class Demo  
  32. {  
  33.     // 定义关键字  
  34.     private static String KEY_WORD = "结算区分";  
  35.     // 定义返回值  
  36.     private static float[] resu = null;  
  37.     // 定义返回页码  
  38.     private static int i = 0;  
  39.   
  40.     public static void main(String[] args) {  
  41.         float[] point = getKeyWords("I://ticket_in.pdf");  
  42.     }  
  43.     /* 
  44.      * 返回关键字所在的坐标和页数 float[0] >> X float[1] >> Y float[2] >> page 
  45.      */  
  46.     private static float[] getKeyWords(String filePath)  
  47.     {  
  48.         try  
  49.         {  
  50.             PdfReader pdfReader = new PdfReader(filePath);  
  51.             int pageNum = pdfReader.getNumberOfPages();  
  52.             PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(  
  53.                     pdfReader);  
  54.   
  55.             // 下标从1开始  
  56.             for (i = 1; i <= pageNum; i++)  
  57.             {  
  58.                 pdfReaderContentParser.processContent(i, new RenderListener()  
  59.                 {  
  60.   
  61.                     @Override  
  62.                     public void renderText(TextRenderInfo textRenderInfo)  
  63.                     {  
  64.                         String text = textRenderInfo.getText();  
  65.                         if (null != text && text.contains(KEY_WORD))  
  66.                         {  
  67.                             Float boundingRectange = textRenderInfo  
  68.                                     .getBaseline().getBoundingRectange();  
  69.                             resu = new float[3];  
  70.                             System.out.println("======="+text);  
  71.                             System.out.println("h:"+boundingRectange.getHeight());  
  72.                             System.out.println("w:"+boundingRectange.width);  
  73.                             System.out.println("centerX:"+boundingRectange.getCenterX());  
  74.                             System.out.println("centerY:"+boundingRectange.getCenterY());  
  75.                             System.out.println("x:"+boundingRectange.getX());  
  76.                             System.out.println("y:"+boundingRectange.getY());  
  77.                             System.out.println("maxX:"+boundingRectange.getMaxX());  
  78.                             System.out.println("maxY:"+boundingRectange.getMaxY());  
  79.                             System.out.println("minX:"+boundingRectange.getMinX());  
  80.                             System.out.println("minY:"+boundingRectange.getMinY());  
  81.                             resu[0] = boundingRectange.x;  
  82.                             resu[1] = boundingRectange.y;  
  83.                             resu[2] = i;  
  84.                         }  
  85.                     }  
  86.   
  87.                     @Override  
  88.                     public void renderImage(ImageRenderInfo arg0)  
  89.                     {  
  90.                     }  
  91.   
  92.                     @Override  
  93.                     public void endTextBlock()  
  94.                     {  
  95.   
  96.                     }  
  97.   
  98.                     @Override  
  99.                     public void beginTextBlock()  
  100.                     {  
  101.                     }  
  102.                 });  
  103.             }  
  104.         } catch (IOException e)  
  105.         {  
  106.             e.printStackTrace();  
  107.         }  
  108.         return resu;  
  109.     }  
  110.   
  111. }  
结合以上的,我们就可以写一个自动替换PDF文本的类,具体使用如下:
[java] view plain copy
 
  1. public static void main(String[] args) throws IOException, DocumentException {  
  2.         PdfReplacer textReplacer = new PdfReplacer("I://test.pdf");  
  3.         textReplacer.replaceText("陈坤", "小白");  
  4.         textReplacer.replaceText("本科", "社会大学");  
  5.         textReplacer.replaceText("0755-29493863", "15112345678");  
  6.         textReplacer.toPdf("I://ticket_out.pdf");  
  7.     }  

原始PDF:
未替换
替换之后的(红色背景只是方便大家看到差别):
(第一次认真写博客,感觉感觉好花时间了,佩服那些坚持写博客的人~~)

补上相关代码(还在完善中),总共4个类

代码中有几个地方要说明下:

1、由于自动计算得到的高度都是0,所有我这边默认的都是12,大家要根据实际情况来设

2、除了可以让代码自己计算位置之外,也可以通过replaceText的重载方法强制指定替换区域。

[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : PdfTextReplacer.java 
  4.  * CLASS : PdfTextReplacer 
  5.  * 
  6.  * AUTHOR : caoxu-yiyang@qq.com 
  7.  * 
  8.  * FUNCTION : TODO 
  9.  * 
  10.  * 
  11.  *====================================================================== 
  12.  * CHANGE HISTORY LOG 
  13.  *---------------------------------------------------------------------- 
  14.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  15.  *---------------------------------------------------------------------- 
  16.  *          |2016年11月8日|caoxu-yiyang@qq.com| Created | 
  17.  * DESCRIPTION: 
  18.  * </pre> 
  19.  ***********************************************************************/  
  20.   
  21. package com.cx.itext;  
  22.   
  23. import java.io.ByteArrayOutputStream;  
  24. import java.io.FileInputStream;  
  25. import java.io.FileOutputStream;  
  26. import java.io.IOException;  
  27. import java.util.HashMap;  
  28. import java.util.Map;  
  29. import java.util.Map.Entry;  
  30. import java.util.Set;  
  31.   
  32. import com.itextpdf.text.BaseColor;  
  33. import com.itextpdf.text.DocumentException;  
  34. import com.itextpdf.text.Font;  
  35. import com.itextpdf.text.log.Logger;  
  36. import com.itextpdf.text.log.LoggerFactory;  
  37. import com.itextpdf.text.pdf.BaseFont;  
  38. import com.itextpdf.text.pdf.PdfContentByte;  
  39. import com.itextpdf.text.pdf.PdfReader;  
  40. import com.itextpdf.text.pdf.PdfStamper;  
  41.   
  42. /** 
  43.  * 替换PDF文件某个区域内的文本 
  44.  * @user : caoxu-yiyang@qq.com 
  45.  * @date : 2016年11月8日 
  46.  */  
  47. public class PdfReplacer {  
  48.     private static final Logger logger = LoggerFactory.getLogger(PdfReplacer.class);  
  49.       
  50.     private int fontSize;  
  51.     private Map<String, ReplaceRegion> replaceRegionMap = new HashMap<String, ReplaceRegion>();  
  52.     private Map<String, Object> replaceTextMap =new HashMap<String, Object>();  
  53.     private ByteArrayOutputStream output;  
  54.     private PdfReader reader;  
  55.     private PdfStamper stamper;  
  56.     private PdfContentByte canvas;  
  57.     private Font font;  
  58.       
  59.     public PdfReplacer(byte[] pdfBytes) throws DocumentException, IOException{  
  60.         init(pdfBytes);  
  61.     }  
  62.       
  63.     public PdfReplacer(String fileName) throws IOException, DocumentException{  
  64.         FileInputStream in = null;  
  65.         try{  
  66.             in =new FileInputStream(fileName);  
  67.             byte[] pdfBytes = new byte[in.available()];  
  68.             in.read(pdfBytes);  
  69.             init(pdfBytes);  
  70.         }finally{  
  71.             in.close();  
  72.         }  
  73.     }  
  74.       
  75.     private void init(byte[] pdfBytes) throws DocumentException, IOException{  
  76.         logger.info("初始化开始");  
  77.         reader = new PdfReader(pdfBytes);  
  78.         output = new ByteArrayOutputStream();  
  79.         stamper = new PdfStamper(reader, output);  
  80.         canvas = stamper.getOverContent(1);  
  81.         setFont(10);  
  82.         logger.info("初始化成功");  
  83.     }  
  84.       
  85.     private void close() throws DocumentException, IOException{  
  86.         if(reader != null){  
  87.             reader.close();  
  88.         }  
  89.         if(output != null){  
  90.             output.close();  
  91.         }  
  92.           
  93.         output=null;  
  94.         replaceRegionMap=null;  
  95.         replaceTextMap=null;  
  96.     }  
  97.       
  98.     public void replaceText(float x, float y, float w,float h, String text){  
  99.         ReplaceRegion region = new ReplaceRegion(text);     //用文本作为别名  
  100.         region.setH(h);  
  101.         region.setW(w);  
  102.         region.setX(x);  
  103.         region.setY(y);  
  104.         addReplaceRegion(region);  
  105.         this.replaceText(text, text);  
  106.     }  
  107.       
  108.     public void replaceText(String name, String text){  
  109.         this.replaceTextMap.put(name, text);  
  110.     }  
  111.       
  112.     /** 
  113.      * 替换文本 
  114.      * @throws IOException  
  115.      * @throws DocumentException  
  116.      * @user : caoxu-yiyang@qq.com 
  117.      * @date : 2016年11月9日 
  118.      */  
  119.     private void process() throws DocumentException, IOException{  
  120.         try{  
  121.             parseReplaceText();  
  122.             canvas.saveState();  
  123.             Set<Entry<String, ReplaceRegion>> entrys = replaceRegionMap.entrySet();  
  124.             for (Entry<String, ReplaceRegion> entry : entrys) {  
  125.                 ReplaceRegion value = entry.getValue();  
  126.                 canvas.setColorFill(BaseColor.RED);  
  127.                 canvas.rectangle(value.getX(),value.getY(),value.getW(),value.getH());  
  128.             }  
  129.             canvas.fill();  
  130.             canvas.restoreState();  
  131.             //开始写入文本   
  132.             canvas.beginText();   
  133.             for (Entry<String, ReplaceRegion> entry : entrys) {  
  134.                 ReplaceRegion value = entry.getValue();  
  135.                 //设置字体  
  136.                 canvas.setFontAndSize(font.getBaseFont(), getFontSize());    
  137.                 canvas.setTextMatrix(value.getX(),value.getY()+2/*修正背景与文本的相对位置*/);    
  138.                 canvas.showText((String) replaceTextMap.get(value.getAliasName()));     
  139.             }  
  140.             canvas.endText();  
  141.         }finally{  
  142.             if(stamper != null){  
  143.                 stamper.close();  
  144.             }  
  145.         }  
  146.     }  
  147.       
  148.     /** 
  149.      * 未指定具体的替换位置时,系统自动查找位置 
  150.      * @user : caoxu-yiyang@qq.com 
  151.      * @date : 2016年11月9日 
  152.      */  
  153.     private void parseReplaceText() {  
  154.         PdfPositionParse parse = new PdfPositionParse(reader);  
  155.         Set<Entry<String, Object>> entrys = this.replaceTextMap.entrySet();  
  156.         for (Entry<String, Object> entry : entrys) {  
  157.             if(this.replaceRegionMap.get(entry.getKey()) == null){  
  158.                 parse.addFindText(entry.getKey());  
  159.             }  
  160.         }  
  161.           
  162.         try {  
  163.             Map<String, ReplaceRegion> parseResult = parse.parse();  
  164.             Set<Entry<String, ReplaceRegion>> parseEntrys = parseResult.entrySet();  
  165.             for (Entry<String, ReplaceRegion> entry : parseEntrys) {  
  166.                 if(entry.getValue() != null){  
  167.                     this.replaceRegionMap.put(entry.getKey(), entry.getValue());  
  168.                 }  
  169.             }  
  170.         } catch (IOException e) {  
  171.             logger.error(e.getMessage(), e);  
  172.         }  
  173.           
  174.     }  
  175.   
  176.     /** 
  177.      * 生成新的PDF文件 
  178.      * @user : caoxu-yiyang@qq.com 
  179.      * @date : 2016年11月9日 
  180.      * @param fileName 
  181.      * @throws DocumentException 
  182.      * @throws IOException 
  183.      */  
  184.     public void toPdf(String fileName) throws DocumentException, IOException{  
  185.         FileOutputStream fileOutputStream = null;  
  186.         try{  
  187.             process();  
  188.             fileOutputStream = new FileOutputStream(fileName);  
  189.             fileOutputStream.write(output.toByteArray());  
  190.             fileOutputStream.flush();  
  191.         }catch(IOException e){  
  192.             logger.error(e.getMessage(), e);  
  193.             throw e;  
  194.         }finally{  
  195.             if(fileOutputStream != null){  
  196.                 fileOutputStream.close();  
  197.             }  
  198.             close();  
  199.         }  
  200.         logger.info("文件生成成功");  
  201.     }  
  202.       
  203.     /** 
  204.      * 将生成的PDF文件转换成二进制数组 
  205.      * @user : caoxu-yiyang@qq.com 
  206.      * @date : 2016年11月9日 
  207.      * @return 
  208.      * @throws DocumentException 
  209.      * @throws IOException 
  210.      */  
  211.     public byte[] toBytes() throws DocumentException, IOException{  
  212.         try{  
  213.             process();  
  214.             logger.info("二进制数据生成成功");  
  215.             return output.toByteArray();  
  216.         }finally{  
  217.             close();  
  218.         }  
  219.     }  
  220.       
  221.     /** 
  222.      * 添加替换区域 
  223.      * @user : caoxu-yiyang@qq.com 
  224.      * @date : 2016年11月9日 
  225.      * @param replaceRegion 
  226.      */  
  227.     public void addReplaceRegion(ReplaceRegion replaceRegion){  
  228.         this.replaceRegionMap.put(replaceRegion.getAliasName(), replaceRegion);  
  229.     }  
  230.       
  231.     /** 
  232.      * 通过别名得到替换区域 
  233.      * @user : caoxu-yiyang@qq.com 
  234.      * @date : 2016年11月9日 
  235.      * @param aliasName 
  236.      * @return 
  237.      */  
  238.     public ReplaceRegion getReplaceRegion(String aliasName){  
  239.         return this.replaceRegionMap.get(aliasName);  
  240.     }  
  241.   
  242.     public int getFontSize() {  
  243.         return fontSize;  
  244.     }  
  245.   
  246.     /** 
  247.      * 设置字体大小 
  248.      * @user : caoxu-yiyang@qq.com 
  249.      * @date : 2016年11月9日 
  250.      * @param fontSize 
  251.      * @throws DocumentException 
  252.      * @throws IOException 
  253.      */  
  254.     public void setFont(int fontSize) throws DocumentException, IOException{  
  255.         if(fontSize != this.fontSize){  
  256.             this.fontSize = fontSize;  
  257.             BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);  
  258.             font = new Font(bf,this.fontSize,Font.BOLD);  
  259.         }  
  260.     }  
  261.       
  262.     public void setFont(Font font){  
  263.         if(font == null){  
  264.             throw new NullPointerException("font is null");  
  265.         }  
  266.         this.font = font;  
  267.     }  
  268.       
  269.     public static void main(String[] args) throws IOException, DocumentException {  
  270.         PdfReplacer textReplacer = new PdfReplacer("I://test.pdf");  
  271.         textReplacer.replaceText("陈坤", "小白");  
  272.         textReplacer.replaceText("本科", "社会大学");  
  273.         textReplacer.replaceText("0755-29493863", "15112345678");  
  274.         textReplacer.toPdf("I://ticket_out.pdf");  
  275.     }  
  276. }  
[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : ReplaceRegion.java 
  4.  * CLASS : ReplaceRegion 
  5.  * 
  6.  * AUTHOR : caoxu-yiyang@qq.com 
  7.  * 
  8.  * FUNCTION : TODO 
  9.  * 
  10.  * 
  11.  *====================================================================== 
  12.  * CHANGE HISTORY LOG 
  13.  *---------------------------------------------------------------------- 
  14.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  15.  *---------------------------------------------------------------------- 
  16.  *          |2016年11月9日|caoxu-yiyang@qq.com| Created | 
  17.  * DESCRIPTION: 
  18.  * </pre> 
  19.  ***********************************************************************/  
  20.   
  21. package com.cx.itext;  
  22.   
  23. /** 
  24.  * 需要替换的区域 
  25.  * @user : caoxu-yiyang@qq.com 
  26.  * @date : 2016年11月9日 
  27.  */  
  28. public class ReplaceRegion {  
  29.   
  30.     private String aliasName;  
  31.     private Float x;  
  32.     private Float y;  
  33.     private Float w;  
  34.     private Float h;  
  35.       
  36.     public ReplaceRegion(String aliasName){  
  37.         this.aliasName = aliasName;  
  38.     }  
  39.       
  40.     /** 
  41.      * 替换区域的别名 
  42.      * @user : caoxu-yiyang@qq.com 
  43.      * @date : 2016年11月9日 
  44.      * @return 
  45.      */  
  46.     public String getAliasName() {  
  47.         return aliasName;  
  48.     }  
  49.     public void setAliasName(String aliasName) {  
  50.         this.aliasName = aliasName;  
  51.     }  
  52.     public Float getX() {  
  53.         return x;  
  54.     }  
  55.     public void setX(Float x) {  
  56.         this.x = x;  
  57.     }  
  58.     public Float getY() {  
  59.         return y;  
  60.     }  
  61.     public void setY(Float y) {  
  62.         this.y = y;  
  63.     }  
  64.     public Float getW() {  
  65.         return w;  
  66.     }  
  67.     public void setW(Float w) {  
  68.         this.w = w;  
  69.     }  
  70.     public Float getH() {  
  71.         return h;  
  72.     }  
  73.     public void setH(Float h) {  
  74.         this.h = h;  
  75.     }  
  76. }  
[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : PdfPositionParse.java 
  4.  * CLASS : PdfPositionParse 
  5.  * 
  6.  * AUTHOR : caoxu-yiyang@qq.com 
  7.  * 
  8.  * FUNCTION : TODO 
  9.  * 
  10.  * 
  11.  *====================================================================== 
  12.  * CHANGE HISTORY LOG 
  13.  *---------------------------------------------------------------------- 
  14.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  15.  *---------------------------------------------------------------------- 
  16.  *          |2016年11月9日|caoxu-yiyang@qq.com| Created | 
  17.  * DESCRIPTION: 
  18.  * </pre> 
  19.  ***********************************************************************/  
  20.   
  21. package com.cx.itext;  
  22.   
  23. import java.io.FileInputStream;  
  24. import java.io.IOException;  
  25. import java.util.ArrayList;  
  26. import java.util.List;  
  27. import java.util.Map;  
  28.   
  29. import com.cx.itext.listener.PositionRenderListener;  
  30. import com.itextpdf.text.pdf.PdfReader;  
  31. import com.itextpdf.text.pdf.parser.PdfReaderContentParser;  
  32.   
  33. /** 
  34.  * 解析PDF中文本的x,y位置 
  35.  * @user : caoxu-yiyang@qq.com 
  36.  * @date : 2016年11月9日 
  37.  */  
  38. public class PdfPositionParse {  
  39.   
  40.     private PdfReader reader;  
  41.     private List<String> findText = new ArrayList<String>();    //需要查找的文本  
  42.     private PdfReaderContentParser parser;  
  43.   
  44.     public PdfPositionParse(String fileName) throws IOException{  
  45.         FileInputStream in = null;  
  46.         try{  
  47.             in =new FileInputStream(fileName);  
  48.             byte[] bytes = new byte[in.available()];  
  49.             in.read(bytes);  
  50.             init(bytes);  
  51.         }finally{  
  52.             in.close();  
  53.         }  
  54.     }  
  55.       
  56.     public PdfPositionParse(byte[] bytes) throws IOException{  
  57.         init(bytes);  
  58.     }  
  59.       
  60.     private boolean needClose = true;  
  61.     /** 
  62.      * 传递进来的reader不会在PdfPositionParse结束时关闭 
  63.      * @user : caoxu-yiyang@qq.com 
  64.      * @date : 2016年11月9日 
  65.      * @param reader 
  66.      */  
  67.     public PdfPositionParse(PdfReader reader){  
  68.         this.reader = reader;  
  69.         parser = new PdfReaderContentParser(reader);  
  70.         needClose = false;  
  71.     }  
  72.   
  73.     public void addFindText(String text){  
  74.         this.findText.add(text);  
  75.     }  
  76.       
  77.     private void init(byte[] bytes) throws IOException {  
  78.         reader = new PdfReader(bytes);  
  79.         parser = new PdfReaderContentParser(reader);  
  80.     }  
  81.       
  82.     /** 
  83.      * 解析文本 
  84.      * @user : caoxu-yiyang@qq.com 
  85.      * @date : 2016年11月9日 
  86.      * @throws IOException 
  87.      */  
  88.     public Map<String, ReplaceRegion> parse() throws IOException{  
  89.         try{  
  90.             if(this.findText.size() == 0){  
  91.                 throw new NullPointerException("没有需要查找的文本");  
  92.             }  
  93.             PositionRenderListener listener = new PositionRenderListener(this.findText);  
  94.             parser.processContent(1, listener);  
  95.             return listener.getResult();  
  96.         }finally{  
  97.             if(reader != null && needClose){  
  98.                 reader.close();  
  99.             }  
  100.         }  
  101.     }  
  102. }  
[java] view plain copy
 
  1. /********************************************************************** 
  2.  * <pre> 
  3.  * FILE : PositionRenderListener.java 
  4.  * CLASS : PositionRenderListener 
  5.  * 
  6.  * AUTHOR : caoxu-yiyang@qq.com 
  7.  * 
  8.  * FUNCTION : TODO 
  9.  * 
  10.  * 
  11.  *====================================================================== 
  12.  * CHANGE HISTORY LOG 
  13.  *---------------------------------------------------------------------- 
  14.  * MOD. NO.|   DATE   |   NAME  | REASON  | CHANGE REQ. 
  15.  *---------------------------------------------------------------------- 
  16.  *          |2016年11月9日|caoxu-yiyang@qq.com| Created | 
  17.  * DESCRIPTION: 
  18.  * </pre> 
  19.  ***********************************************************************/  
  20.   
  21. package com.cx.itext.listener;  
  22.   
  23. import java.util.HashMap;  
  24. import java.util.List;  
  25. import java.util.Map;  
  26.   
  27. import com.cx.itext.ReplaceRegion;  
  28. import com.itextpdf.awt.geom.Rectangle2D.Float;  
  29. import com.itextpdf.text.pdf.parser.ImageRenderInfo;  
  30. import com.itextpdf.text.pdf.parser.RenderListener;  
  31. import com.itextpdf.text.pdf.parser.TextRenderInfo;  
  32.   
  33. /** 
  34.  * pdf渲染监听,当找到渲染的文本时,得到文本的坐标x,y,w,h 
  35.  * @user : caoxu-yiyang@qq.com 
  36.  * @date : 2016年11月9日 
  37.  */  
  38. public class PositionRenderListener implements RenderListener{  
  39.       
  40.     private List<String> findText;  
  41.     private float defaultH;     ///出现无法取到值的情况,默认为12  
  42.     private float fixHeight;    //可能出现无法完全覆盖的情况,提供修正的参数,默认为2  
  43.     public PositionRenderListener(List<String> findText, float defaultH,float fixHeight) {  
  44.         this.findText = findText;  
  45.         this.defaultH = defaultH;  
  46.         this.fixHeight = fixHeight;  
  47.     }  
  48.   
  49.     public PositionRenderListener(List<String> findText) {  
  50.         this.findText = findText;  
  51.         this.defaultH = 12;  
  52.         this.fixHeight = 2;  
  53.     }  
  54.       
  55.     @Override  
  56.     public void beginTextBlock() {  
  57.           
  58.     }  
  59.   
  60.     @Override  
  61.     public void endTextBlock() {  
  62.           
  63.     }  
  64.   
  65.     @Override  
  66.     public void renderImage(ImageRenderInfo imageInfo) {  
  67.     }  
  68.   
  69.     private Map<String, ReplaceRegion> result = new HashMap<String, ReplaceRegion>();  
  70.     @Override  
  71.     public void renderText(TextRenderInfo textInfo) {  
  72.         String text = textInfo.getText();  
  73.         for (String keyWord : findText) {  
  74.             if (null != text && text.equals(keyWord)){  
  75.                 Float bound = textInfo.getBaseline().getBoundingRectange();  
  76.                 ReplaceRegion region = new ReplaceRegion(keyWord);  
  77.                 region.setH(bound.height == 0 ? defaultH : bound.height);  
  78.                 region.setW(bound.width);  
  79.                 region.setX(bound.x);  
  80.                 region.setY(bound.y-this.fixHeight);  
  81.                 result.put(keyWord, region);  
  82.             }  
  83.         }  
  84.     }  
  85.   
  86.     public Map<String, ReplaceRegion> getResult() {  
  87.         for (String key : findText) {   //补充没有找到的数据  
  88.             if(this.result.get(key) == null){  
  89.                 this.result.put(key, null);  
  90.             }  
  91.         }  
  92.         return this.result;  
  93.     }  
  94. }  

我用到的jar包如下:


大家可以从官网下载,可以构建maven项目省去自己找包的麻烦。如果没有用maven又想下载具体的jar包,可以直接访问maven仓库下载:http://mvnrepository.com/

原文地址:https://www.cnblogs.com/Alex80/p/8116955.html