Apache PDFBox:编码问题

问题描述 投票:0回答:1

我有一个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 !!!”。前两个字符串正确搜索,但第三个看起来像“ KHOOR ...”:

enter image description here

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

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

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

java pdfbox
1个回答
0
投票

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

为什么/很难自动替换文本

有许多因素会阻止自动替换PDF中的文本,一些因素已经使finding的指令难以绘制有关文本的说明,还有一些因素使replaceing字符变得更加复杂。这些说明。

这里显示的问题列表并不详尽!

查找特定文字的说明

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

(首先绘制

“文件:///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

可以画

“ hello !!!”

((字符串参数写为十六进制字符串,在调试器中,您看到了

“ KHOOR ...”

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

但是PDF不需要包含从单个代码到Unicode字符的映射,在字体文件中可能仅存在到字形id的映射。如果是嵌入式字体文件,则这些字体文件也不需要包含任何到Unicode字符的映射。

[PDF文件通常确实具有与代码匹配的Unicode字符的信息,以允许文本提取,例如复制/粘贴;但是严格来说,这些信息是可选的;更糟糕的是,该信息可能包含错误,而在

显示

PDF时不会造成问题。在所有此类情况下,必须使用类似OCR的机制来识别与每个字形关联的Unicode字符。替换说明中的文本

一旦找到了负责绘制搜索文本的说明,就必须替换文本。这也可能暗示一些问题。

子集字体

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

Tahoma

甚至是新罗马字母(您可以识别文本的字体)只是嵌入了以下字形的子集:

Times New Roman

因此,即使您找到了“你好!!!”在Tahoma中,只需将字符代码替换为“ byebye ??”即可。只会显示“ e e”,因为嵌入字体中出现字形的唯一字符是“ e”。

因此,要进行替换,您可能需要编辑嵌入式字体文件和表示形式的PDF字体对象以包含和编码所有必需的字形,或者添加另一种字体和说明以切换到该字体以进行可操纵的文本绘制指令并返回此后再次。

字体编码

即使您的字体根本没有嵌入(因此将使用该字体的完整本地副本)或嵌入您需要的所有字形,字体使用的编码也可能受到限制。在基于西欧语言的PDF中,您通常会找到

WinAnsiEncoding

,其类似于Windows代码页1252的编码。如果要替换为西里尔字母,则这些字符没有字符代码。因此,在这种情况下,您可能必须更改编码以包含所需的所有字符(通过扫描有关字体的所有使用来在当前编码中找到未使用的字符,或者添加具有更适当编码的另一种字体。] >

布局注意事项

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

文本对齐方式也可能会损坏。

也请考虑标记的文本(下划线/删除线/背景色/ ...)。 PDF中的这些标记(通常)不是字体属性,而是单独的矢量图形。为了正确处理这些问题,您必须解析页面中的矢量图形和注释,试探性地识别文本标记,然后进行更新。

带标签的PDF

如果处理带标签的PDF(例如,为了可访问性),这可能会使

finding

文本更容易(因为可访问性应允许容易地提取文本),但是替换文本会更加困难,因为您可能还必须更新一些标签或结构树数据。不过如何实现通用文本替换

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

[要查找要替换的文本

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

要替换文本

,您可以询问PDFBox字体类(如果进行了相应扩展,则由PdfTextStripper提供)是否可以对替换文本进行编码。
并且总是拿在手中的PDF规范(ISO 32000-1或ISO 32000-2)的副本...

但是请注意,要获得一个不错的通用解决方案将花费您数周或数月的时间。

© www.soinside.com 2019 - 2024. All rights reserved.