PDFBox 2.0.7 ExtractText 不工作但 1.8.13 和 PDFReader 也一样
PDFBox 2.0.7 ExtractText not working but 1.8.13 does and PDFReader as well
希望您知道使用 pdfbox 2.0.7 从 PDF 中提取文本出了什么问题。结果很奇怪:
使用1.8.13,命令java -jar pdfbox-app-1.8.13.jar ExtractText -sort -nonSeq test.pdf
导致
Deutsche Bank Privat- und Geschäftskunden AG
Bruttoertrag 43,80 USD 37,15 EUR
Kapitalertragsteuer (KESt) - 5,36 USD - 4,55 EUR
Solidaritätszuschlag auf KESt - 0,29 USD - 0,25 EUR
Umrechnungskurs USD zu EUR 1,1791000000
Gutschrift mit Wert 15.08.2017 32,35 EUR
使用2.0.7,命令java -jar pdfbox-app-2.0.7.jar ExtractText -sort test.pdf
导致
aeutsche Bank mrivat- und deschäftskunden Ad
Bruttoertrag QPIUM rpa PTINR bro
hapitaäertragsteuer EhbptF - RIPS rpa - QIRR bro
poäidaritätszuschäag auf hbpt - MIOV rpa - MIOR bro
rmrechnungskurs rpa zu bro NINTVNMMMMMM
dutschrift mit tert NRKMUKOMNT POIPR bro
带有 java -jar pdfbox-app-2.0.7.jar PDFDebugger test.pdf
的调试器在 Root/Pages/Kids/[1]/Contents/[1]
中显示了正确的文本,因此不知何故文本被正确读取但未正确导出。
我试图比较两个 PDFDebugger 应用程序中显示的信息,但它们对我来说似乎非常相同(尽管我不知道 where/what 确切地寻找)。很遗憾,我无法共享 PDF 文档。
如果有任何关于如何解决甚至只解决这个问题的提示,我会很高兴,否则我将无法使用更新版本的 pdfbox。提前感谢您的宝贵时间!
这是文档中使用的字体的屏幕截图(使用 2.0.7 提取)。这正是显然没有执行的字母翻译:
条目 ToUnicode 说
%!PS-Adobe-3.0 Resource-CMap
/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo
<< /Registry (Adobe)
/Ordering (UCS)
/Supplement 0
>> def
/CMapName /AdHoc-UCS def
/CMapType 2 def
1 begincodespacerange
<0000> <FFFF>
endcodespacerange
68 beginbfchar
<0004> <0021>
<0009> <0026>
<000b> <0028>
<000c> <0029>
<000f> <002c>
<0010> <002d>
<0011> <002e>
<0012> <002f>
<0013> <0030>
<0014> <0031>
<0015> <0032>
<0016> <0033>
<0017> <0034>
<0018> <0035>
<0019> <0036>
<001a> <0037>
<001b> <0038>
<001c> <0039>
<001d> <003a>
<001e> <003b>
<0024> <0041>
<0025> <0042>
<0026> <0043>
<0027> <0044>
<0028> <0045>
<0029> <0046>
<002a> <0047>
<002b> <0048>
<002c> <0049>
<002e> <004b>
<0030> <004d>
<0031> <004e>
<0032> <004f>
<0033> <0050>
<0034> <0051>
<0035> <0052>
<0036> <0053>
<0037> <0054>
<0038> <0055>
<0039> <0056>
<003a> <0057>
<003d> <005a>
<0044> <0061>
<0045> <0062>
<0046> <0063>
<0047> <0064>
<0048> <0065>
<0049> <0066>
<004a> <0067>
<004b> <0068>
<004c> <0069>
<004d> <006a>
<004e> <006b>
<004f> <006c>
<0050> <006d>
<0051> <006e>
<0052> <006f>
<0053> <0070>
<0055> <0072>
<0056> <0073>
<0057> <0074>
<0058> <0075>
<0059> <0076>
<005a> <0077>
<005d> <007a>
<006c> <00e4>
<0081> <00fc>
<0089> <00df>
endbfchar
endcmap
CMapName currentdict /CMap defineresource pop
end
end
PDF 第 2 页的 TextView 已经显示了正确的文本,但是不知何故,上面显示的这些替换表似乎在 pdfbox 导出之前错误地修改了文本内容:
Root/Pages/Kids/[1]/Contents/[1]:
=================================
0 Tw
0 Tc
0 0 0 rg
0 0 0 RG
BT
/F1 10 Tf
1 0 0 1 69.449 697.11 Tm
(Wir) Tj
1 0 0 1 87.199 697.11 Tm
(4berweisen) Tj
1 0 0 1 141.099 697.11 Tm
(den) Tj
1 0 0 1 160.549 697.11 Tm
(Betrag) Tj
1 0 0 1 192.759 697.11 Tm
(von) Tj
1 0 0 1 211.649 697.11 Tm
(32,35) Tj
1 0 0 1 239.429 697.11 Tm
(EUR) Tj
1 0 0 1 263.299 697.11 Tm
(auf) Tj
1 0 0 1 279.959 697.11 Tm
(Ihr) Tj
1 0 0 1 294.389 697.11 Tm
(Konto) Tj
1 0 0 1 323.269 697.11 Tm
(XXXXXXX) Tj
1 0 0 1 364.959 697.11 Tm
(XX) Tj
1 0 0 1 376.079 697.11 Tm
(.) Tj
0 G
0 g
ET
69.449 669.448 m
69.449 669.698 l
549.921 669.698 l
549.921 669.448 l
549.921 669.198 l
69.449 669.198 l
h
f
0 0 0 rg
0 0 0 RG
BT
/F1 6 Tf
1 0 0 1 249.022 658.948 Tm
(Kapitalertr4ge) Tj
1 0 0 1 288.016 658.948 Tm
(sind) Tj
1 0 0 1 300.682 658.948 Tm
(einkommensteuerpflichtig!) Tj
1 0 0 1 213.865 652.783 Tm
(Diese) Tj
1 0 0 1 230.863 652.783 Tm
(Mitteilung) Tj
1 0 0 1 258.187 652.783 Tm
(wurde) Tj
1 0 0 1 276.187 652.783 Tm
(maschinell) Tj
1 0 0 1 306.187 652.783 Tm
(erstellt) Tj
1 0 0 1 325.507 652.783 Tm
(und) Tj
1 0 0 1 337.177 652.783 Tm
(wird) Tj
1 0 0 1 349.837 652.783 Tm
(nicht) Tj
1 0 0 1 364.165 652.783 Tm
(unterschrieben.) Tj
0 G
0 g
ET
q
1 0 0 1 504.562 772.646 cm
1 0 0 1 0 0 cm
q
0 Tw
0 Tc
45.36 0 0 45.36 0 0 cm
/I0 Do
Q
Q
0 0 0 rg
0 0 0 RG
BT
/F1 10.5 Tf
1 0 0 1 552.756 23.464 Tm
(2) Tj
1 0 0 1 558.594 23.464 Tm
(/) Tj
1 0 0 1 561.503 23.464 Tm
(2) Tj
ET
Q
q
0 0 m
0 841.89 l
595.276 841.89 l
595.276 0 l
h
0 0 m
595.276 0 l
595.276 841.89 l
0 841.89 l
h
W
n
Q
1.8.13 显示:
Wir überweisen den Betrag von 32,35 EUR auf Ihr Konto XXXXXXX XX.
Kapitalerträge sind einkommensteuerpflichtig!
Diese Mitteilung wurde maschinell erstellt und wird nicht unterschrieben.
2/2
2.0.7 显示:
tir überweisen den Betrag von POIPR bro auf fhr honto XXXXXXX XX
hapitaäerträge sind einkommensteuerpfäichtig!
aiese jitteiäung wurde maschineää ersteäät und wird nicht unterschriebenK
O/O
这是您要的文件:https://wetransfer.com/downloads/214674449c23713ee481c5a8f529418320170827201941/b2bea6
您的 PDF 中有关相关字体的信息相互矛盾且部分损坏。根据某些软件的反应,它可能会或可能不会正确提取文本。
一方面,字体有一个 Encoding 值 WinAnsiEncoding。这没问题,并且与我们在内容流中看到的相匹配,一个单字节编码涵盖了许多 ANSI 代码。
另一方面,我们有一个 ToUnicode 映射,这意味着底层编码是一些双字节编码(它有一个代码 space 范围 <0000> <ffff>
),即使忽略双字节性质,它也有映射,特别是将数字 ANSI 代码映射到大写字母,大写字母 ANSI 代码映射到其他小写字母,小写 'l' ANSI 代码映射到“ä”的 Unicode 值。
提取文本时,PDFBox 2.0.x 似乎遵循损坏的 ToUnicode 映射(将表格中的双字节代码解释为单字节代码,在可能的情况下忽略上面的 0)(导致垃圾),否则将字符代码解释为 ANSI(导致正确的文本)。 PDF 1.8.x 似乎忽略了 ToUnicode 映射,Adobe Reader.
也是如此
实际上看起来 ToUnicode 映射是为使用 Identity-H 编码的字体制作的。
如果您遇到这样的 PDF 并需要提取其文本,您可以对其进行预处理并删除 ToUnicode 条目;此后文本提取应该 return 正确的文本。例如
PDDocument document = PDDocument.load(SOURCE);
for (int pageNr = 0; pageNr < document.getNumberOfPages(); pageNr++)
{
PDPage page = document.getPage(pageNr);
PDResources resources = page.getResources();
removeToUnicodeMaps(resources);
}
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
(ExtractText测试方法testNoToUnicodeTest2
)
使用辅助方法
void removeToUnicodeMaps(PDResources pdResources) throws IOException
{
COSDictionary resources = pdResources.getCOSObject();
COSDictionary fonts = asDictionary(resources, COSName.FONT);
if (fonts != null)
{
for (COSBase object : fonts.getValues())
{
while (object instanceof COSObject)
object = ((COSObject)object).getObject();
if (object instanceof COSDictionary)
{
COSDictionary font = (COSDictionary)object;
font.removeItem(COSName.TO_UNICODE);
}
}
}
for (COSName name : pdResources.getXObjectNames())
{
PDXObject xobject = pdResources.getXObject(name);
if (xobject instanceof PDFormXObject)
{
PDResources xobjectPdResources = ((PDFormXObject)xobject).getResources();
removeToUnicodeMaps(xobjectPdResources);
}
}
}
COSDictionary asDictionary(COSDictionary dictionary, COSName name)
{
COSBase object = dictionary.getDictionaryObject(name);
return object instanceof COSDictionary ? (COSDictionary) object : null;
}
(来自ExtractText)
您应该在加载文档后尽早执行此预处理,以防止包含错误 ToUnicode 映射的字体被读入文档字体缓存。
希望您知道使用 pdfbox 2.0.7 从 PDF 中提取文本出了什么问题。结果很奇怪:
使用1.8.13,命令java -jar pdfbox-app-1.8.13.jar ExtractText -sort -nonSeq test.pdf
导致
Deutsche Bank Privat- und Geschäftskunden AG
Bruttoertrag 43,80 USD 37,15 EUR
Kapitalertragsteuer (KESt) - 5,36 USD - 4,55 EUR
Solidaritätszuschlag auf KESt - 0,29 USD - 0,25 EUR
Umrechnungskurs USD zu EUR 1,1791000000
Gutschrift mit Wert 15.08.2017 32,35 EUR
使用2.0.7,命令java -jar pdfbox-app-2.0.7.jar ExtractText -sort test.pdf
导致
aeutsche Bank mrivat- und deschäftskunden Ad
Bruttoertrag QPIUM rpa PTINR bro
hapitaäertragsteuer EhbptF - RIPS rpa - QIRR bro
poäidaritätszuschäag auf hbpt - MIOV rpa - MIOR bro
rmrechnungskurs rpa zu bro NINTVNMMMMMM
dutschrift mit tert NRKMUKOMNT POIPR bro
带有 java -jar pdfbox-app-2.0.7.jar PDFDebugger test.pdf
的调试器在 Root/Pages/Kids/[1]/Contents/[1]
中显示了正确的文本,因此不知何故文本被正确读取但未正确导出。
我试图比较两个 PDFDebugger 应用程序中显示的信息,但它们对我来说似乎非常相同(尽管我不知道 where/what 确切地寻找)。很遗憾,我无法共享 PDF 文档。
如果有任何关于如何解决甚至只解决这个问题的提示,我会很高兴,否则我将无法使用更新版本的 pdfbox。提前感谢您的宝贵时间!
这是文档中使用的字体的屏幕截图(使用 2.0.7 提取)。这正是显然没有执行的字母翻译:
条目 ToUnicode 说
%!PS-Adobe-3.0 Resource-CMap
/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo
<< /Registry (Adobe)
/Ordering (UCS)
/Supplement 0
>> def
/CMapName /AdHoc-UCS def
/CMapType 2 def
1 begincodespacerange
<0000> <FFFF>
endcodespacerange
68 beginbfchar
<0004> <0021>
<0009> <0026>
<000b> <0028>
<000c> <0029>
<000f> <002c>
<0010> <002d>
<0011> <002e>
<0012> <002f>
<0013> <0030>
<0014> <0031>
<0015> <0032>
<0016> <0033>
<0017> <0034>
<0018> <0035>
<0019> <0036>
<001a> <0037>
<001b> <0038>
<001c> <0039>
<001d> <003a>
<001e> <003b>
<0024> <0041>
<0025> <0042>
<0026> <0043>
<0027> <0044>
<0028> <0045>
<0029> <0046>
<002a> <0047>
<002b> <0048>
<002c> <0049>
<002e> <004b>
<0030> <004d>
<0031> <004e>
<0032> <004f>
<0033> <0050>
<0034> <0051>
<0035> <0052>
<0036> <0053>
<0037> <0054>
<0038> <0055>
<0039> <0056>
<003a> <0057>
<003d> <005a>
<0044> <0061>
<0045> <0062>
<0046> <0063>
<0047> <0064>
<0048> <0065>
<0049> <0066>
<004a> <0067>
<004b> <0068>
<004c> <0069>
<004d> <006a>
<004e> <006b>
<004f> <006c>
<0050> <006d>
<0051> <006e>
<0052> <006f>
<0053> <0070>
<0055> <0072>
<0056> <0073>
<0057> <0074>
<0058> <0075>
<0059> <0076>
<005a> <0077>
<005d> <007a>
<006c> <00e4>
<0081> <00fc>
<0089> <00df>
endbfchar
endcmap
CMapName currentdict /CMap defineresource pop
end
end
PDF 第 2 页的 TextView 已经显示了正确的文本,但是不知何故,上面显示的这些替换表似乎在 pdfbox 导出之前错误地修改了文本内容:
Root/Pages/Kids/[1]/Contents/[1]:
=================================
0 Tw
0 Tc
0 0 0 rg
0 0 0 RG
BT
/F1 10 Tf
1 0 0 1 69.449 697.11 Tm
(Wir) Tj
1 0 0 1 87.199 697.11 Tm
(4berweisen) Tj
1 0 0 1 141.099 697.11 Tm
(den) Tj
1 0 0 1 160.549 697.11 Tm
(Betrag) Tj
1 0 0 1 192.759 697.11 Tm
(von) Tj
1 0 0 1 211.649 697.11 Tm
(32,35) Tj
1 0 0 1 239.429 697.11 Tm
(EUR) Tj
1 0 0 1 263.299 697.11 Tm
(auf) Tj
1 0 0 1 279.959 697.11 Tm
(Ihr) Tj
1 0 0 1 294.389 697.11 Tm
(Konto) Tj
1 0 0 1 323.269 697.11 Tm
(XXXXXXX) Tj
1 0 0 1 364.959 697.11 Tm
(XX) Tj
1 0 0 1 376.079 697.11 Tm
(.) Tj
0 G
0 g
ET
69.449 669.448 m
69.449 669.698 l
549.921 669.698 l
549.921 669.448 l
549.921 669.198 l
69.449 669.198 l
h
f
0 0 0 rg
0 0 0 RG
BT
/F1 6 Tf
1 0 0 1 249.022 658.948 Tm
(Kapitalertr4ge) Tj
1 0 0 1 288.016 658.948 Tm
(sind) Tj
1 0 0 1 300.682 658.948 Tm
(einkommensteuerpflichtig!) Tj
1 0 0 1 213.865 652.783 Tm
(Diese) Tj
1 0 0 1 230.863 652.783 Tm
(Mitteilung) Tj
1 0 0 1 258.187 652.783 Tm
(wurde) Tj
1 0 0 1 276.187 652.783 Tm
(maschinell) Tj
1 0 0 1 306.187 652.783 Tm
(erstellt) Tj
1 0 0 1 325.507 652.783 Tm
(und) Tj
1 0 0 1 337.177 652.783 Tm
(wird) Tj
1 0 0 1 349.837 652.783 Tm
(nicht) Tj
1 0 0 1 364.165 652.783 Tm
(unterschrieben.) Tj
0 G
0 g
ET
q
1 0 0 1 504.562 772.646 cm
1 0 0 1 0 0 cm
q
0 Tw
0 Tc
45.36 0 0 45.36 0 0 cm
/I0 Do
Q
Q
0 0 0 rg
0 0 0 RG
BT
/F1 10.5 Tf
1 0 0 1 552.756 23.464 Tm
(2) Tj
1 0 0 1 558.594 23.464 Tm
(/) Tj
1 0 0 1 561.503 23.464 Tm
(2) Tj
ET
Q
q
0 0 m
0 841.89 l
595.276 841.89 l
595.276 0 l
h
0 0 m
595.276 0 l
595.276 841.89 l
0 841.89 l
h
W
n
Q
1.8.13 显示:
Wir überweisen den Betrag von 32,35 EUR auf Ihr Konto XXXXXXX XX.
Kapitalerträge sind einkommensteuerpflichtig!
Diese Mitteilung wurde maschinell erstellt und wird nicht unterschrieben.
2/2
2.0.7 显示:
tir überweisen den Betrag von POIPR bro auf fhr honto XXXXXXX XX
hapitaäerträge sind einkommensteuerpfäichtig!
aiese jitteiäung wurde maschineää ersteäät und wird nicht unterschriebenK
O/O
这是您要的文件:https://wetransfer.com/downloads/214674449c23713ee481c5a8f529418320170827201941/b2bea6
您的 PDF 中有关相关字体的信息相互矛盾且部分损坏。根据某些软件的反应,它可能会或可能不会正确提取文本。
一方面,字体有一个 Encoding 值 WinAnsiEncoding。这没问题,并且与我们在内容流中看到的相匹配,一个单字节编码涵盖了许多 ANSI 代码。
另一方面,我们有一个 ToUnicode 映射,这意味着底层编码是一些双字节编码(它有一个代码 space 范围 <0000> <ffff>
),即使忽略双字节性质,它也有映射,特别是将数字 ANSI 代码映射到大写字母,大写字母 ANSI 代码映射到其他小写字母,小写 'l' ANSI 代码映射到“ä”的 Unicode 值。
提取文本时,PDFBox 2.0.x 似乎遵循损坏的 ToUnicode 映射(将表格中的双字节代码解释为单字节代码,在可能的情况下忽略上面的 0)(导致垃圾),否则将字符代码解释为 ANSI(导致正确的文本)。 PDF 1.8.x 似乎忽略了 ToUnicode 映射,Adobe Reader.
也是如此实际上看起来 ToUnicode 映射是为使用 Identity-H 编码的字体制作的。
如果您遇到这样的 PDF 并需要提取其文本,您可以对其进行预处理并删除 ToUnicode 条目;此后文本提取应该 return 正确的文本。例如
PDDocument document = PDDocument.load(SOURCE);
for (int pageNr = 0; pageNr < document.getNumberOfPages(); pageNr++)
{
PDPage page = document.getPage(pageNr);
PDResources resources = page.getResources();
removeToUnicodeMaps(resources);
}
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
(ExtractText测试方法testNoToUnicodeTest2
)
使用辅助方法
void removeToUnicodeMaps(PDResources pdResources) throws IOException
{
COSDictionary resources = pdResources.getCOSObject();
COSDictionary fonts = asDictionary(resources, COSName.FONT);
if (fonts != null)
{
for (COSBase object : fonts.getValues())
{
while (object instanceof COSObject)
object = ((COSObject)object).getObject();
if (object instanceof COSDictionary)
{
COSDictionary font = (COSDictionary)object;
font.removeItem(COSName.TO_UNICODE);
}
}
}
for (COSName name : pdResources.getXObjectNames())
{
PDXObject xobject = pdResources.getXObject(name);
if (xobject instanceof PDFormXObject)
{
PDResources xobjectPdResources = ((PDFormXObject)xobject).getResources();
removeToUnicodeMaps(xobjectPdResources);
}
}
}
COSDictionary asDictionary(COSDictionary dictionary, COSName name)
{
COSBase object = dictionary.getDictionaryObject(name);
return object instanceof COSDictionary ? (COSDictionary) object : null;
}
(来自ExtractText)
您应该在加载文档后尽早执行此预处理,以防止包含错误 ToUnicode 映射的字体被读入文档字体缓存。