JAVA 替换pdf中文字

本文介绍如何使用Java和iText库实现PDF文件中的文本替换功能,包括关键字定位、字体格式保存及替换过程。

java 读取PDF文件内容进行替换
需要使用到的包
监听类(对需要替换的内容关键词进行匹配)
实体类(保存关键字字体格式信息以及其位置)
工具类(对关键字进行替换)
测试类
需要使用到的包
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.3</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

监听类(对需要替换的内容关键词进行匹配)
public class KeyWordPositionListener implements RenderListener {

    //存放匹配上的字符信息
    private List<MatchItem> matches = new ArrayList<MatchItem>();
    //存放所有的字符信息
    private List<MatchItem> allItems = new ArrayList<MatchItem>();

    private Rectangle curPageSize;

    /**
     * 匹配的关键字
     */
    private String keyword;
    /**
     * 匹配的当前页
     */
    private Integer pageNumber;

    @Override
    public void beginTextBlock() {
        //do nothing
    }

    @Override
    public void renderText(TextRenderInfo renderInfo) {
        //获取字符
        String content = renderInfo.getText();
        Rectangle2D.Float textRectangle = renderInfo.getDescentLine().getBoundingRectange();

        MatchItem item = new MatchItem();
        item.setContent(content);
        item.setPageNum(pageNumber);
        item.setFontHeight(textRectangle.height == 0 ? 12:textRectangle.height);//默认12
        item.setFontWidth(textRectangle.width);
        item.setPageHeight(curPageSize.getHeight());
        item.setPageWidth(curPageSize.getWidth());
        item.setX((float)textRectangle.getX());
        item.setY((float)textRectangle.getY());

        //若keyword是单个字符,匹配上的情况
        if(content.equalsIgnoreCase(keyword)) {
            matches.add(item);
        }
        //保存所有的项
        allItems.add(item);
    }

    @Override
    public void endTextBlock() {
        //do nothing
    }

    @Override
    public void renderImage(ImageRenderInfo renderInfo) {
        //do nothing
    }

    /**
     * 设置需要匹配的当前页
     * @param pageNumber
     */
    public void setPageNumber(Integer pageNumber) {
        this.pageNumber = pageNumber;
    }

    /**
     * 设置需要匹配的关键字,忽略大小写
     * @param keyword
     */
    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    /**
     * 返回匹配的结果列表
     * @return
     */
    public List<MatchItem> getMatches() {
        return matches;
    }

    public void setCurPageSize(Rectangle rect) {
        this.curPageSize = rect;
    }

    public List<MatchItem> getAllItems() {
        return allItems;
    }

    public void setAllItems(List<MatchItem> allItems) {
        this.allItems = allItems;
    }

}
实体类(保存关键字字体格式信息以及其位置)
public class MatchItem {

    //页数
    private Integer pageNum;
    //x坐标
    private Float x;
    //y坐标
    private Float y;
    //页宽
    private Float pageWidth;
    //页高
    private Float pageHeight;
    //匹配字符
    private String content;
    //字体宽
    private float fontWidth;
    //字体高
    private float fontHeight = 12;

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    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 getPageWidth() {
        return pageWidth;
    }

    public void setPageWidth(Float pageWidth) {
        this.pageWidth = pageWidth;
    }

    public Float getPageHeight() {
        return pageHeight;
    }

    public void setPageHeight(Float pageHeight) {
        this.pageHeight = pageHeight;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public float getFontWidth() {
        return fontWidth;
    }

    public void setFontWidth(float fontWidth) {
        this.fontWidth = fontWidth;
    }

    public float getFontHeight() {
        return fontHeight;
    }

    public void setFontHeight(float fontHeight) {
        this.fontHeight = fontHeight;
    }

    @Override
    public String toString() {
        return "MatchItem{" +
                "pageNum=" + pageNum +
                ", x=" + x +
                ", y=" + y +
                ", pageWidth=" + pageWidth +
                ", pageHeight=" + pageHeight +
                ", content='" + content + '\'' +
                '}';
    }
}

工具类(对关键字进行替换)
public class PdfUtil {
    /**
     * 根据关键字和pdf文件字节,全文搜索关键字
     * @param bytes pdf字节
     * @param keyword 关键字
     * @return
     * @throws Exception
     */
    private static List<MatchItem> matchAll(byte[] bytes, String keyword) throws Exception {
        List<MatchItem> items = new ArrayList<>();
        PdfReader reader = new PdfReader(bytes);
        //获取pdf页数
        int pageSize = reader.getNumberOfPages();
        //逐页匹配关键字
        for(int page = 1;page <= pageSize;page++){
            items.addAll(matchPage(reader,page,keyword));
        }
        return items;
    }

    /**
     * 根据关键字、文档路径、pdf页数寻找特定的文件内容
     * @param reader
     * @param pageNumber 页数
     * @param keyword 关键字
     * @return
     * @throws Exception
     */
    private static List<MatchItem> matchPage(PdfReader reader, Integer pageNumber,String keyword) throws Exception {
        PdfReaderContentParser parse = new PdfReaderContentParser(reader);
        Rectangle rectangle = reader.getPageSize(pageNumber);
        //匹配监听
        KeyWordPositionListener renderListener = new KeyWordPositionListener();
        renderListener.setKeyword(keyword);
        renderListener.setPageNumber(pageNumber);
        renderListener.setCurPageSize(rectangle);
        parse.processContent(pageNumber, renderListener);
        return findKeywordItems(renderListener,keyword);
    }

    /**
     * 找到匹配的关键词块
     * @param renderListener
     * @param keyword
     * @return
     */
    private static List<MatchItem> findKeywordItems(KeyWordPositionListener renderListener,String keyword){
        //先判断本页中是否存在关键词
        List<MatchItem> allItems = renderListener.getAllItems();//所有块LIST
        StringBuffer sbtemp = new StringBuffer("");

        for(MatchItem item : allItems){//将一页中所有的块内容连接起来组成一个字符串。
            sbtemp.append(item.getContent());
        }

        List<MatchItem> matches = renderListener.getMatches();

        //一页组成的字符串没有关键词,直接return
        //第一种情况:关键词与块内容完全匹配的项,直接返回
        if(sbtemp.toString().indexOf(keyword) == -1 || matches.size() > 0){
            return matches;
        }
        //第二种情况:多个块内容拼成一个关键词,则一个一个来匹配,组装成一个关键词
        sbtemp = new StringBuffer("");
        List<MatchItem> tempItems = new ArrayList();
        for(MatchItem item : allItems){
            if(keyword.indexOf(item.getContent()) != -1 ){
                tempItems.add(item);
                sbtemp.append(item.getContent());

                if(keyword.indexOf(sbtemp.toString()) == -1){//如果暂存的字符串和关键词 不再匹配时
                    sbtemp = new StringBuffer(item.getContent());
                    tempItems.clear();
                    tempItems.add(item);
                }

                if(sbtemp.toString().equalsIgnoreCase(keyword)){//暂存的字符串正好匹配到关键词时
                    matches.add(tempItems.get(0));//得到匹配的项
                    sbtemp = new StringBuffer("");//清空暂存的字符串
                    tempItems.clear();//清空暂存的LIST
                    continue;//继续查找
                }
            }else{//如果找不到则清空
                sbtemp = new StringBuffer("");
                tempItems.clear();
            }
        }
        return matches;
    }

    /**
     * 替换目标文字,生成新的pdf文件
     * @param bytes 目标pdf
     * @param outputStream
     * @throws Exception
     */
    private static void manipulatePdf(byte[] bytes,OutputStream outputStream,List<MatchItem> matchItems,String keyWord,String keyWordNew) throws Exception{
        PdfReader reader = new PdfReader(bytes);
        PdfStamper stamper = new PdfStamper(reader, outputStream);
        PdfContentByte canvas;
        Map<Integer,List<MatchItem>> mapItem = new HashMap<>();
        List<MatchItem> itemList;
        for(MatchItem item : matchItems){
            Integer pageNum = item.getPageNum();
            if(mapItem.containsKey(pageNum)){
                itemList = mapItem.get(pageNum);
                itemList.add(item);
                mapItem.put(pageNum,itemList);
            }else{
                itemList = new ArrayList<>();
                itemList.add(item);
                mapItem.put(pageNum,itemList);
            }
        }
        //遍历每一页去修改
        for(Integer page : mapItem.keySet()){
            List<MatchItem> items = mapItem.get(page);
            //遍历每一页中的匹配项
            for(MatchItem item : items){
                canvas = stamper.getOverContent(page);
                float x = item.getX();
                float y = item.getY();
                float fontWidth = item.getFontWidth();
                float fontHeight = item.getFontHeight();
                canvas.saveState();
                canvas.setColorFill(BaseColor.WHITE);
                canvas.rectangle(x, y,fontWidth*keyWord.length(),fontWidth+2);
                canvas.fill();
                canvas.restoreState();
                //开始写入文本
                canvas.beginText();
                BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
                Font font = new Font(bf,fontWidth,Font.BOLD);
                //设置字体和大小
                canvas.setFontAndSize(font.getBaseFont(), fontWidth);
                //设置字体的输出位置
                canvas.setTextMatrix(x, y+fontWidth/10+0.5f);
                //要输出的text
                canvas.showText(keyWordNew);

                canvas.endText();
            }
        }
        stamper.close();
        reader.close();
    }

    /**
     * 替换pdf中指定文字
     * @param srcBytes 目标pdf
     * @param outputStream 新pdf
     * @param keyWord 替换的文字
     * @param keyWordNew 替换后的文字
     * @throws Exception
     */
    public static void pdfReplace(byte[] srcBytes,OutputStream outputStream,String keyWord,String keyWordNew) throws Exception{
        manipulatePdf(srcBytes,outputStream,matchAll(srcBytes,keyWord),keyWord,keyWordNew);
    }
}

测试类
public class test{
public static void main(String[] args) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        FileInputStream inputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            //源文件pdf
            File file = new File("D:\\test.pdf");
            //目标文件
            File destFile = new File("D:\\dest.pdf");
            inputStream = new FileInputStream(file);
            fileOutputStream = new FileOutputStream(destFile);
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            //关键字
            String keyWord = "请选择";
            //替换后的内容
            String keyWordNew = "你爸爸";
            PdfUtil.pdfReplace(bytes,outputStream,keyWord,keyWordNew);
            //得到替换后的文件字节
            byte[] byteArray = outputStream.toByteArray();
            //输出
            fileOutputStream.write(byteArray);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fileOutputStream != null) fileOutputStream.close();
                if (inputStream != null) inputStream.close();
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
 

<think>嗯,用户想用Java实现替换PDF中的文字,我得先回忆一下相关的库和方法。之前看过一些资料,记得iText和Spire.PDF可能有用。iText是挺强大的,不过商用需要许可证。Spire.PDF可能更友好一些,有免费版本,但可能有页数限制。 首先,用户的需求是替换文本,可能需要找到特定的字符串然后替换PDF的结构比较复杂,直接替换文本不像处理Word那样简单。得确保库支持查找和替换功能。 想到引用3提到的几种方式,可能iText和Spire.PDF都是选项。还有引用2提到的e-iceblue的库,不过可能需要付费。用户可能需要免费方案,所以iText的开源版本可能更合适,但要注意AGPL协议,商用可能有风险。 然后,具体的代码实现步骤应该是怎样的?可能需要加载PDF文档,遍历页面,查找文本,替换后再保存。比如使用iText的话,PdfReader读取文件,PdfWriter输出,用PdfDocument处理内容。不过直接替换文本可能比较麻烦,因为PDF内容流是二进制格式,需要解析文本位置,可能涉及文本状态和字体的问题。 另外,引用4提到用模板替换内容,可能更适合动态生成PDF,比如填充表单字段。但如果用户是需要替换任意位置的文本,可能需要更底层的操作。这时候可能需要使用Spire.PDF的replace方法,像引用3中的示例那样,用pdf.replace(oldText, newText, false)来全局替换。 还要考虑替换后的格式是否保持一致,比如字体、大小等。直接替换文本可能会导致格式变化,特别是新旧文本长度不同时。这时候可能需要调整布局,或者确保替换文本的长度相近。 用户可能遇到的问题包括找不到文本、替换后格式错乱、特殊字符处理等。解决方案可能需要精确匹配文本,处理字体嵌入,或者使用正则表达式来查找文本。此外,处理中文时要注意编码是否正确,字体是否支持中文字符。 总结下来,应该推荐几个库,分别给出示例代码,并说明优缺点。同时提醒注意事项,比如许可证、格式问题、性能等。可能还需要提到其他库,如Apache PDFBox,虽然它的替换功能可能不如前两者方便。</think>### 使用Java替换PDF文本内容的实现方法 #### 一、主流Java库及选择建议 1. **iText库(推荐用于复杂操作)** - 开源版本遵循AGPL协议,商用需购买商业许可 - 支持精确文本定位和替换 - 示例代码: ```java PdfDocument pdfDoc = new PdfDocument(new PdfReader("input.pdf"), new PdfWriter("output.pdf")); for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) { PdfPage page = pdfDoc.getPage(i); PdfCanvasProcessor processor = new PdfCanvasProcessor(new TextReplaceListener("旧文本", "新文本")); processor.processPageContent(page); } pdfDoc.close(); ``` 2. **Spire.PDF(推荐快速实现)** - 免费版支持10页以内文档 - 提供直接替换方法: ```java PdfDocument pdf = new PdfDocument(); pdf.loadFromFile("input.pdf"); pdf.replace("旧文本", "新文本", false); pdf.saveToFile("output.pdf"); ``` 3. **Apache PDFBox** - 完全免费但替换逻辑需自行实现 - 核心代码片段: ```java PDDocument doc = PDDocument.load(new File("input.pdf")); PDFTextStripper stripper = new ReplacementStripper("旧文本", "新文本"); stripper.writeText(doc, new FileWriter("temp.txt")); // 需重新生成PDF(此方法会丢失格式) ``` #### 二、关键注意事项 1. **字体兼容性** - 新文本需使用文档原有字体(通过`getFont()`获取当前字体) - 中文替换需确认字体包含中文字符集[^3] 2. **格式保留策略** - 推荐先提取文本位置信息: $$ \text{坐标定位公式:} \quad (x,y) = (getUnscaledWidth() \times ratio, getUnscaledHeight() \times ratio) $$ - 使用`showText()`重写内容时保持原始变换矩阵 3. **性能优化** - 大文件建议分页处理 - 启用内存缓存模式: ```java MemoryLimitsAwareHandler handler = new MemoryLimitsAwareHandler(); PdfReader reader = new PdfReader("bigfile.pdf", new ReaderProperties().setMemoryLimitSize(1000000)); ``` #### 三、常见问题解决 1. **文本无法匹配** - 启用正则表达式模式: ```java pdf.replace("\\d{4}-\\d{2}-\\d{2}", "2023-07-01", true); ``` - 处理编码差异(特别针对GBK/UTF-8混用场景) 2. **内容错位** - 使用`getBaseline()`保持垂直对齐 - 计算文本宽度差值补偿: $$ \Delta = (oldTextWidth - newTextWidth) \times fontSize $$ 3. **签名失效** - 替换后需重新添加数字签名 - 使用`PdfSigner`进行二次签名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值