gpt4 book ai didi

java - Apache PDFBox:编码问题

转载 作者:行者123 更新时间:2023-12-01 19:28:28 26 4
gpt4 key购买 nike

我有一个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 ...”:

enter image description here

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

源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字体用于显示“你好!!!”仅嵌入以下字形:

Tahoma

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

Times New Roman

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

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

字体编码

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

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

布局注意事项

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

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

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

带标签的PDF

如果您处理带标签的PDF(例如为了可访问性),这可能会使查找文本更容易(因为可访问性应允许轻松提取文本),但更难替换文本,因为您可能还必须更新一些标签或结构树数据。

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

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

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

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

而且始终要拿出PDF规范(ISO 32000-1或ISO 32000-2)的副本...

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

关于java - Apache PDFBox:编码问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60631596/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com