漂流的老妖怪

导航

 

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

 

posted on 2018-03-14 20:02  漂流的老妖怪  阅读(5597)  评论(0编辑  收藏  举报