如何在 PDF (1.4) 字节数组中搜索目标字符串?

RTF*_*RTF 4 java pdf

我知道这可能有点不寻常,但我想知道 PDF 文档(字节数组)是否包含特定的文本片段。我使用 iText 库 v2.1.7 在 Java 中自行创建文档,该库生成符合 PDF 1.4 规范的文档。

\n\n

我最初的天真的尝试是这样的:

\n\n
byte[] target = "the target text".getBytes("UTF-8");\nint index = Bytes.indexOf(pdfBytes, target); // Guava lib\nSystem.out.println( index ); // always -1 (not found)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我只是不太了解这些类型的文档是如何编码的,无法弄清楚我需要做什么。我想我真正需要弄清楚的是,当我转换为字节时,我需要在目标文本上使用哪种编码,以便它与 PDF 使用的编码相匹配。

\n\n

我创建了一个小型 PDF 示例文档,其中除了包含以下单词的短语外什么都不包含one two three four five。cat如果我在 Linux 终端(或用于vim查看它)中查看该文件,则该 PDF 文件的内容如下所示:

\n\n
%PDF-1.4\n%\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\n2 0 obj\n<</Filter/FlateDecode/Length 71>>stream\nx\xef\xbf\xbd+\xef\xbf\xbdr\n\xef\xbf\xbd24U\xef\xbf\xbd02I\xef\xbf\xbd2P0Q\xef\xbf\xbdn\xef\xbf\xbd\n\xef\xbf\xbdF\n!i\\\xef\xbf\xbdy\xef\xbf\xbd\n%\xef\xbf\xbd\xef\xbf\xbd\n%E\xef\xbf\xbd\xef\xbf\xbd\ni\xef\xbf\xbd\xef\xbf\xbdE\ni\xef\xbf\xbde\xef\xbf\xbd\xef\xbf\xbd!Y0\xc5\xae!\\\xef\xbf\xbd\\\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\nendstream\nendobj\n4 0 obj\n<</Contents 2 0 R/Type/Page/Resources<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]/Font<</F1 1 0 R>>>>/Parent 3 0 R/MediaBox[0 0 595 842]>>\nendobj\n1 0 obj\n<</Subtype/Type1/Type/Font/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>\nendobj\n3 0 obj\n<</Kids[4 0 R]/Type/Pages/Count 1/ITXT(2.1.7)>>\nendobj\n5 0 obj\n<</Type/Catalog/Pages 3 0 R>>\nendobj\n6 0 obj\n<</ModDate(D:20171216101023Z)/CreationDate(D:20171216101023Z)/Producer(iText 2.1.7 by 1T3XT)>>\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000309 00000 n \n0000000015 00000 n \n0000000397 00000 n \n0000000152 00000 n \n0000000460 00000 n \n0000000505 00000 n \ntrailer\n<</Info 6 0 R/ID [<9e1d205d229e3d1b5b56354a7da26844><7bf1bdf9e8d048c5795c7785954d9360>]/Root 5 0 R/Size 7>>\nstartxref\n615\n%%EOF\n
Run Code Online (Sandbox Code Playgroud)\n\n

其中一些字符编码在复制和粘贴中未正确翻译,因此如果您复制并保存在那里看到的内容,您将得到一个损坏的 PDF。这是一个链接该 PDF 副本的链接。

\n\n

我尝试将目标字符串编码为各种编码,例如 CP-1252 和 WinAnsiEncoding,但这些是无法识别的字符集。

\n\n
\n\n

最初我认为这不会给我带来太多麻烦,但我还没有弄清楚如何做到这一点。我确实有一个解决方法,可以得到相同的结果,但它是专门用于 iText 库的解决方案,即不是在 PDF 字节数组中搜索文本的通用解决方案。

\n\n

如果我使用 iText 解析我想要搜索的字节数组,我可以迭代 PDF 的每一页并提取文本:

\n\n
private static boolean doesPDFContain(byte[] pdf, String text) throws Exception {\n    PdfReader reader = new PdfReader(pdf);\n    int numPages = reader.getNumberOfPages();\n    PdfTextExtractor extractor = new PdfTextExtractor(reader);\n\n    for (int i=1; i<=numPages; i++) {\n        if ( extractor.getTextFromPage(i).contains(text) ){\n            return true;\n        }\n    }\n    return false;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我仍然有兴趣听听是否可以做我最初尝试的事情。

\n

mkl*_*mkl 5

有很多原因可以解释为什么您的天真的方法(简单地查找特定编码中的文本)通常不起作用。

您正在寻找的文本,即屏幕上显示的文本,是由某些内容流中的文本绘制指令绘制的。(让我们忽略图形看起来像文本但使用矢量或位图图形命令绘制的情况以及丢失或不准确的字体编码信息的情况。)

  • 您要查找的文本不一定是由单个指令绘制的。例如,文本“Hello”可以使用两个连续的命令编写:

    (Hel) Tj (lo) Tj
    
    Run Code Online (Sandbox Code Playgroud)

    不同的命令甚至不需要在内容流中相互跟随,它们可以分布在内容流中。

  • PDF 中的每种字体都可以对其字符串使用不同的编码,并且这些编码甚至不需要是标准编码,它们可能是由 PDF 创建程序动态创建的临时编码。

  • 内容流可以(并且通常确实)需要过滤器进行解码,例如在对象 2 中的内容流上方的 PDF 中需要FlateDecode过滤(本质上是:解压缩)。

  • PDF 可以被加密(在这种情况下,更具体地说,字符串和流被加密);即使您可以在 PDF 查看器中轻松打开的 PDF 也可能使用默认密码进行加密(此技术用于编码权限)。

因此,要检查内容流的内容,您可能必须

  • 解密文件;然后
  • 使用各自适用的过滤器对内容流进行解码;然后
  • 解析内容流指令以了解每个文本绘制指令
    • 使用哪种字体来绘制文本以及
    • 文本绘制在哪个位置;然后
  • 根据字体中的信息解码字符串内容;然后
  • 根据位置信息对文本片段进行排序,并将它们组合在一起作为单个字符串。

在这个字符串中,您最终可以以简单的方式搜索文本。