繁体   English   中英

Apache PDFBox:编码问题

[英]Apache PDFBox: problems with encoding

我有一个 PDF 模板并试图替换其中的一些单词。 我使用这个代码:

private PDDocument replaceText(PDDocument document, String searchString, String replacement) throws IOException {
    if (searchString.isEmpty() || replacement.isEmpty()) {
        return document;
    }
    PDPageTree pages = document.getDocumentCatalog().getPages();
    for (PDPage page : pages) {
        PDFStreamParser parser = new PDFStreamParser(page);
        parser.parse();
        List<Object> tokens = parser.getTokens();
        for (int j = 0; j < tokens.size(); j++) {
            Object next = tokens.get(j);
            if (next instanceof Operator) {
                Operator op = (Operator) next;
                //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();
                    if (searchString.equals(string)) {
                        System.out.println(string);
                    }
                    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 (searchString.equals(string)) {
                                System.out.println(string);
                            }
                            string = StringUtils.replaceOnce(string, searchString, replacement);
                            cosString.setValue(string.getBytes());
                        }
                    }
                }
            }
        }
        // now that the tokens are updated we will replace the page content stream.
        PDStream updatedStream = new PDStream(document);
        OutputStream out = updatedStream.createOutputStream();
        ContentStreamWriter tokenWriter = new ContentStreamWriter(out);
        tokenWriter.writeTokens(tokens);
        page.setContents(updatedStream);
        out.close();
    }
    return document;
}

我的 PDF 模板只有 3 个字符串:“file:///C/Users/Mi/Downloads/converted.txt”、“[10.03.2020 18:43:57]”和“hello!!!”。 前 2 个字符串搜索正确,但第三个看起来像“KHOOR...”:

在此处输入图片说明

据我了解,存在编码不匹配。 当我尝试将“file:///C/Users/Mi/Downloads/converted.txt”替换为“Hello!”时,它会替换为“ello”,而不显示大写字母和标记。 据我了解,主要区别在于字体。 “你好”有字体设置,其他没有。

源 PDF 在这里: https : //yadi.sk/i/l0OAcFkAkUHKYg

请建议,如何从 PDF 中获取文本作为正确的字符串并替换它。

这个答案实际上解释了为什么针对您的任务的通用解决方案即使不是不可能也至少非常复杂。 在良性情况下,即对于受特定限制的 PDF,可以成功使用像您这样的代码,但是您的示例 PDF 表明您显然想要操作的 PDF 不受限制。

为什么自动替换文本很困难/不可能

有许多因素会阻碍 PDF 中文本的自动替换,有些因素已经使查找绘制相关文本的指令变得困难,有些则使替换这些指令参数中的字符变得复杂。

此处说明的问题列表并不详尽!

查找绘制特定文本的说明

PDF 包含内容流,其中包含告诉 PDF 处理器在哪里绘制什么的指令序列。 PDF 中的常规文本是通过设置当前字体(和字体大小)、设置要绘制文本的位置以及实际绘制文本的指令来绘制的。 这可以像这样容易理解和搜索:

/TT0 1 Tf
9 0 0 9 5 5 Tm
(file:///C/Users/Mi/Downloads/converted.txt[10.03.2020 18:43:57]) Tj 

(这里选择大小为 1 的字体TT0 ,然后应用仿射变换将文本缩放 9 倍并移动到位置 (5, 5),最后文本“file:///C/Users/ Mi/Downloads/converted.txt [10.03.2020 18:43:57]”被绘制。)

在这种情况下,搜索负责绘制给定文本的指令很容易。 但有问题的说明也可能看起来不同。

分割线

例如,字符串可能被分块绘制,而不是上面的Tj指令,我们可能有

[(file:///C/Users/Mi/Downloads/converted.txt)2 ([10.03.2020 18:43:57])] TJ

(这里先绘制“file:///C/Users/Mi/Downloads/converted.txt” ,然后稍微移动文字绘制位置,再绘制“[10.03.2020 18:43:57]” ,两者在同一个TJ指令中。)

或者你可能会看到

(file:///C/Users/Mi/Downloads/converted.txt) Tj
([10.03.2020 18:43:57]) Tj 

(不同说明中绘制的文本部分。)

文本片段的顺序也可能出乎意料:

([10.03.2020 18:43:57]) Tj 
-40 0 Td
(file:///C/Users/Mi/Downloads/converted.txt) Tj

(首先绘制日期字符串,然后在绘制日期之前向左移动文本位置,绘制 URL。)

一些 PDF 制作者分别绘制每个字符,在两者之间设置整个文本转换:

9 0 0 9 5 5 Tm
(f) Tj
9 0 0 9 14 5 Tm
(i) Tj
9 0 0 9 23 5 Tm
(l) Tj
...

并且这些不同的指令不需要像这里那样按顺序排列,它们可以分布在整个流中,甚至可以分布在多个流中,因为一个页面可以有一个内容流数组,而不是可以绘制单个或部分字符串从页面内容流引用的子对象的内容流。

因此,为了找到负责特定多字符文本的指令,您可能需要检查多个流并根据它们绘制的位置将找到的字符串粘合在一起。

连字

并非每个单个字符代码都可能对应于搜索字符串中的单个字符。 有许多用于字符组合的特殊字形,例如用于fl等。因此,为了搜索必须扩展这些连字。

编码

在上面的例子中,即使文本不是在一次运行中绘制的,文本的字符也很容易识别。 但是在 PDF 中,字符的编码不需要那么明显,实际上每种字体都可能带有自己的编码,例如

<004B0048004F004F0052000400040004>Tj 

可以画“你好!!!” .

(这里的字符串参数被写成十六进制字符串,在调试器中你看到了"KHOOR..." 。)

因此,为了搜索文本,首先需要根据当前字体的特定编码将文本绘制指令的字符串参数映射到 Unicode。

但是 PDF 不需要包含从单个代码到 Unicode 字符的映射,可能只存在到字体文件中字形 ID 的映射。 在嵌入字体文件的情况下,这些字体文件也不需要包含任何到 Unicode 字符的映射。

通常,PDF 文件确实有关于与代码匹配的 Unicode 字符的信息,以允许文本提取,例如复制/粘贴; 不过,严格来说,此类信息是可选的; 更糟糕的是,该信息可能包含错误,而不会在显示PDF 时产生问题。 在所有这些情况下,必须使用类似 OCR 的机制来识别与每个字形相关联的 Unicode 字符。

替换说明中的文本

找到负责绘制搜索文本的说明后,您必须替换文本。 这也可能意味着一些问题。

子集字体

如果字体文件嵌入到 PDF 中,它们通常只是作为原始字体的子集嵌入以节省空间。 例如,在您的示例 PDF 中,字体 Tahoma 用于显示“你好!!!” only 嵌入了以下字形:

塔霍马

甚至 Times New Roman(用于您可以识别的文本的字体)也只是嵌入了以下字形的子集:

英语字体格式一种

因此,即使你发现了“你好!!!” 在 Tahoma 中,只需将字符代码替换为“再见??” 只会显示“ee”,因为嵌入字体中存在字形的唯一字符是“e”。

因此,要替换,您可能必须编辑嵌入的字体文件和表示 PDF 字体对象以包含和编码所有必需的字形,或者添加另一种字体和指令以切换到该字体以用于操作的文本绘制指令,然后再返回.

字体编码

即使您的字体根本没有嵌入(因此将使用您完整的本地字体副本)或嵌入了您需要的所有字形,用于您的字体的编码可能会受到限制。 在基于西欧语言的 PDF 中,您经常会发现WinAnsiEncoding ,一种类似于 Windows 代码页 1252 的编码。如果您想用西里尔文本替换,这些字符没有字符代码。

因此,在这种情况下,您可能必须更改编码以包含您需要的所有字符(通过扫描相关字体的所有使用来查找当前编码中未使用的字符)或添加另一种具有更合适编码的字体。

布局注意事项

如果替换文本比替换文本长或短,并且 PDF 的同一行上还有其他文本,则您必须决定是否也应移动该文本。 它可能属于一起并且必须相应地移动,但它也可能来自单独的文本块或列,在这种情况下不应移动它。

文本对齐也可能被损坏。

还要考虑标记文本(下划线/删除线/背景颜色/...)。 PDF 中的这些标记(通常)不是字体属性,而是单独的矢量图形。 为了使这些正确,您必须解析页面中的矢量图形和注释,启发式识别文本标记并更新它们。

标记的 PDF

如果您处理带标签的 PDF(例如用于辅助功能),这可能会使查找文本更容易(因为辅助功能应该允许轻松提取文本),但替换文本会更困难,因为您可能还需要更新一些标签或结构树数据。

尽管如此,如何实现通用文本替换

如上所示,PDF 中的文本替换存在很多障碍。 因此,一个完整的解决方案(在可能的情况下)远远超出了堆栈溢出答案的范围。 一些指针,虽然:

要查找要替换的文本,您应该使用PdfTextStripper (用于文本提取的 PDFBox 实用程序类)并将其扩展为包含指向分别绘制每个字符的文本绘制指令的指针的所有文本。 这样您就不必实现文本的所有解码和排序。

要替换文本,您可以询问 PDFBox 字体类(如果相应扩展,则由PdfTextStripper提供)它们是否可以对您的替换文本进行编码。

并始终拥有一份 PDF 规范(ISO 32000-1 或 ISO 32000-2)的副本...

但请注意,您需要一段时间、数周或数月才能获得一个有点像样的通用解决方案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM