用 Java 中的 PDFbox 替换或删除 PDF 中的文本

Posted

技术标签:

【中文标题】用 Java 中的 PDFbox 替换或删除 PDF 中的文本【英文标题】:Replace or remove text from PDF with PDFbox in Java 【发布时间】:2020-08-26 06:54:33 【问题描述】:

我正在尝试使用 PDFBOX 2.0 替换空白或删除文本模式(在我的情况下,我想从所有 PDF 中删除所有“[QR]”字),但我找不到任何适用于我。

我尝试了itext,但还是一样,没有任何效果。

我的 pdf 中的“[QR]”字符串在创建 PDF 后被编辑,也许这就是它们不显示为 tj 运算符的原因?

我的主要:

replaceText(documentoPDF, "[QR]", "");

我的方法(我打印了 Tj 值并且我的模式没有出现在那里):

public void replaceText(PDDocument documentoPDF, String searchString, String replacement) throws IOException

    for ( PDPage page : documentoPDF.getPages())
        
        PDFStreamParser parser = new PDFStreamParser(page);
        parser.parse();
        List<?> tokens = parser.getTokens();
        
        for (int j = 0; j < tokens.size(); j++)
            
            Object next = tokens.get(j);
            if (next instanceof Operator)
                Operator op = (Operator) next;
                
                String pstring = "";
                int prej = 0;
                
                //Tj and TJ are the two operators that display strings in a PDF
                if (op.getName().equals("Tj")) 
                
                    // Tj takes one operator and that is the string to display so lets update that operator
                    COSString previous = (COSString) tokens.get(j - 1);
                    String string = previous.getString();
                    string = string.replaceFirst(searchString, replacement);
                    previous.setValue(string.getBytes());
                 else 
                if (op.getName().equals("TJ")) 
                
                    COSArray previous = (COSArray) tokens.get(j - 1);
                    for (int k = 0; k < previous.size(); k++) 
                    
                        Object arrElement = previous.getObject(k);
                        if (arrElement instanceof COSString) 
                        
                            COSString cosString = (COSString) arrElement;
                            String string = cosString.getString();
                            
                            if (j == prej) 
                                pstring += string;
                             else 
                                prej = j;
                                pstring = string;
                            
                                               
                                            
                    
                    System.out.println(pstring.trim());
                    
                    if (searchString.equals(pstring.trim())) 
                                                
                        COSString cosString2 = (COSString) previous.getObject(0);
                        cosString2.setValue(replacement.getBytes());                           

                        int total = previous.size()-1;    
                        for (int k = total; k > 0; k--) 
                            previous.remove(k);
                                                    
                    
                
            
        
        
        // now that the tokens are updated we will replace the page content stream.
        PDStream updatedStream = new PDStream(documentoPDF);
        OutputStream out = updatedStream.createOutputStream(COSName.FLATE_DECODE);
        ContentStreamWriter tokenWriter = new ContentStreamWriter(out);
        tokenWriter.writeTokens(tokens);            
        out.close();
        page.setContents(updatedStream);
    

    documentoPDF.save("resources\\resultado\\nuevo.pdf");

这是一个带有一些 [QR] 模式的 pdf 示例:http://www.mediafire.com/file/9w3kkc4yozwsfms/file

如果有人可以提供帮助,我将不胜感激。

如果你需要,我可以上传我的整个项目

提前致谢。

【问题讨论】:

这不起作用的原因很简单——您完全忽略了该文本字体的编码。在内容流中实际上有[( &gt;) ( 4) ( 5) ( @) ] TJ 指令(“>”、“4”、“5”和“@”之前的“空格”实际上是零字节,0x00)。因此,显然编码是一些 16 位编码,也没有自然嵌入 ASCII。 那么,做我想做的事是不可能的?我是 pdfbox 中的菜鸟,我不知道如何使用其他编码或转换它:( 这不是不可能的。至少通常不会;存在用于文本提取的信息不完整或不正确的 PDF,这通常使您的任务无法完成。对于其他 PDF,它只是比您的方法更复杂。 那么,有什么帮助吗?我怎样才能重新编码,并能够取出并比较它的纯文本,从而替换它? 【参考方案1】:

正如 cmets 中已经提到的,您的代码不起作用的原因很简单 - 您完全忽略了该文本字体的编码。在内容流中实际上有[( &gt;) ( 4) ( 5) ( @) ] TJ 指令(“>”、“4”、“5”和“@”之前的“空格”实际上是零字节,0x00)。因此,显然编码是一些 16 位编码,另外自然没有嵌入 ASCII。

要正确考虑字体,必须跟踪当前字体。这意味着解析整个内容流并分析文本字体设置调用、保存图形状态调用和恢复图形状态调用。然后你必须从正确的资源中检索正确的字体对象。

所有这些实际上已经由用于例如的 PDFBox 内容解析框架完成。文本提取。因此,我们可以围绕这个框架创建一个内容流编辑器。

其实这也已经做了,见this answer中的PdfContentStreamEditor

在您的文档中,要删除的文本块是由一个文本绘制指令绘制的,每个指令只绘制一个要删除的文本块,我们可以简单地查看当前指令绘制的文本,然后决定是否保留指令:

PDDocument document = ...;
for (PDPage page : document.getDocumentCatalog().getPages()) 
    PdfContentStreamEditor editor = new PdfContentStreamEditor(document, page) 
        final StringBuilder recentChars = new StringBuilder();

        @Override
        protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement)
                throws IOException 
            String string = font.toUnicode(code);
            if (string != null)
                recentChars.append(string);

            super.showGlyph(textRenderingMatrix, font, code, displacement);
        

        @Override
        protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException 
            String recentText = recentChars.toString();
            recentChars.setLength(0);
            String operatorString = operator.getName();

            if (TEXT_SHOWING_OPERATORS.contains(operatorString) && "[QR]".equals(recentText))
            
                return;
            

            super.write(contentStreamWriter, operator, operands);
        

        final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
    ;
    editor.processPage(page);

document.save("nuevo-noQrText.pdf");

(EditPageContent 测试testRemoveQrTextNuevo)

根据您的 PDFBox 版本,showGlyph 覆盖方法可能有第五个参数;因此,请检查您的 PDFBox 副本的showGlyph 签名,如果此代码不起作用,请进行调整。感谢@DanielNorberg 的提示!

结果二维码下方的“[QR]”文字消失了,例如

成为

【讨论】:

我尝试了这个答案中的代码和你的 PdfContentStreamEditor,它就像一个魅力,除了我必须添加 String 作为 showGlyph 的第四个参数。我正在使用 PDF 框 2.0.19,showGlyph 采用五个参数:PDFStreamEngine.showGlyph(矩阵 textRenderingMatrix,PDFont 字体,int 代码,字符串 unicode,矢量位移)。自从您写下答案后,也许该方法发生了变化?但是,在更改之后,我可以成功地从 PDF 中删除文本,而不会改变其他内容的布局。感谢您在这里分享您的知识,非常有帮助! @DanielNorberg “我使用的是 PDF box 2.0.19,showGlyph 需要五个参数” - 啊,很好找;在 3.0.0-SNAPSHOT 开发分支中,一个参数(代码中不再使用)已被删除。

以上是关于用 Java 中的 PDFbox 替换或删除 PDF 中的文本的主要内容,如果未能解决你的问题,请参考以下文章

使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行

PDFBox 创建带有外部 mp3 或 wav 文件的链接/引用的 Sound 对象

Java 使用PDFBox提取PDF文件中的图片

用Java中的编辑数组替换原始数组

Apache PDFbox开发指南之PDF文档读取

Delphi提取PDF文本