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

标签 java pdf

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

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

byte[] target = "the target text".getBytes("UTF-8");
int index = Bytes.indexOf(pdfBytes, target); // Guava lib
System.out.println( index ); // always -1 (not found)

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

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

%PDF-1.4
%����
2 0 obj
<</Filter/FlateDecode/Length 71>>stream
x�+�r
�24U�02I�2P0Q�n�
�F
!i\�y�
%��
%E��
i��E
i�e��!Y0Ů!\�\���
endstream
endobj
4 0 obj
<</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]>>
endobj
1 0 obj
<</Subtype/Type1/Type/Font/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
endobj
3 0 obj
<</Kids[4 0 R]/Type/Pages/Count 1/ITXT(2.1.7)>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</ModDate(D:20171216101023Z)/CreationDate(D:20171216101023Z)/Producer(iText 2.1.7 by 1T3XT)>>
endobj
xref
0 7
0000000000 65535 f 
0000000309 00000 n 
0000000015 00000 n 
0000000397 00000 n 
0000000152 00000 n 
0000000460 00000 n 
0000000505 00000 n 
trailer
<</Info 6 0 R/ID [<9e1d205d229e3d1b5b56354a7da26844><7bf1bdf9e8d048c5795c7785954d9360>]/Root 5 0 R/Size 7>>
startxref
615
%%EOF

其中一些字符编码在复制和粘贴时未正确翻译,因此如果您复制并保存您在此处看到的内容,您将获得损坏的 PDF。 Here's a link到该 PDF 的副本。

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


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

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

private static boolean doesPDFContain(byte[] pdf, String text) throws Exception {
    PdfReader reader = new PdfReader(pdf);
    int numPages = reader.getNumberOfPages();
    PdfTextExtractor extractor = new PdfTextExtractor(reader);

    for (int i=1; i<=numPages; i++) {
        if ( extractor.getTextFromPage(i).contains(text) ){
            return true;
        }
    }
    return false;
}

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

最佳答案

您的天真方法——简单地查找特定编码的文本——通常行不通的原因有很多。

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

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

    (Hel) Tj (lo) Tj
    

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

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

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

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

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

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

在这个字符串中,您最终可以以一种天真的方式搜索文本。

关于java - 如何在 PDF (1.4) 字节数组中搜索目标字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47845441/

相关文章:

java - 过滤器列表 : Begin by. .. Android Studio

java - app.yaml vs appengine-web.xml 使用哪一个?

python - 如何使用 python 将 html 元素保存为 jpeg/png 或 pdf

google-chrome - 保存 PDF 表单 Chrome IFRAME

java - 我可以顺序使用许多 listIterators 来改变或删除 Java 中 ArrayList 中的列表元素吗?

java - 如果找到,则返回一个选项卡上的值 [java]

java - 返回已完成和待处理任务的计数

pdf - 如何更改数据表中的导出 PDF 页面大小?

c# - PDFsharp 保存到 MemoryStream

python - 如何使用 Python 在 Google Drive API 中操作 PDF