某些 pdf 的 ITextSharp / PDFBox 文本提取失败
ITextSharp / PDFBox text extract fails for certain pdfs
在许多情况下,下面的代码通过 ITextSharp 正确地从 PDF 中提取文本。
using (var pdfReader = new PdfReader(filename))
{
ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
var currentText = PdfTextExtractor.GetTextFromPage(
pdfReader,
1,
strategy);
currentText =
Encoding.UTF8.GetString(Encoding.Convert(
Encoding.Default,
Encoding.UTF8,
Encoding.Default.GetBytes(currentText)));
Console.WriteLine(currentText);
}
但是,在这个 PDF 的情况下,我得到的不是文本:“\u0001\u0002\u0003\u0004\u0005\u0006\a\b\t\a\u0001\u0002\u0003\u0004\u0005\u0006\u0003”
我尝试了不同的编码,甚至尝试了 PDFBox,但仍然无法正确解码 PDF。关于如何解决这个问题有什么想法吗?
确定 PDF 是否允许 文本被正确提取的一个很好的测试方法是在 Adobe Reader 中打开它并复制并粘贴文字.
例如:我复制了单词 ABSTRACT 并将其粘贴到 Notepad++ 中:
你在Notepad++中看到抽象这个词了吗?不是,你看%&SOH
'"%GS
。A表示为%,B表示为&,依此类推。
这清楚地表明 PDF 的内容不可访问:使用的编码 (% = A, & = B,...) 与人类实际使用的字符之间没有映射可以理解。
简而言之:PDF 不允许您提取文本,iText 不行,iTextSharp 不行,PDFBox 不行。您必须找到一个 OCR 工具,然后对整个文档进行 OCR。
如需了解更多信息,您可能需要观看以下视频:
仍然提取文本
is the answer one should give here, the PDF clearly does not provide the information required to allow proper text extraction according to section 9.10 Extraction of Text Content of the PDF specification ISO 32000-1...
但是实际上有一种稍微邪恶的方法可以从手头的 PDF 中提取文本!
wrapping one's text extraction strategy in a instance of the following class,乱码文本被正确文本替换:
public class RemappingExtractionFilter : ITextExtractionStrategy
{
ITextExtractionStrategy strategy;
System.Reflection.FieldInfo stringField;
public RemappingExtractionFilter(ITextExtractionStrategy strategy)
{
this.strategy = strategy;
this.stringField = typeof(TextRenderInfo).GetField("text", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
}
public void RenderText(TextRenderInfo renderInfo)
{
DocumentFont font =renderInfo.GetFont();
PdfDictionary dict = font.FontDictionary;
PdfDictionary encoding = dict.GetAsDict(PdfName.ENCODING);
PdfArray diffs = encoding.GetAsArray(PdfName.DIFFERENCES);
;
StringBuilder builder = new StringBuilder();
foreach (byte b in renderInfo.PdfString.GetBytes())
{
PdfName name = diffs.GetAsName((char)b);
String s = name.ToString().Substring(2);
int i = Convert.ToInt32(s, 16);
builder.Append((char)i);
}
stringField.SetValue(renderInfo, builder.ToString());
strategy.RenderText(renderInfo);
}
public void BeginTextBlock()
{
strategy.BeginTextBlock();
}
public void EndTextBlock()
{
strategy.EndTextBlock();
}
public void RenderImage(ImageRenderInfo renderInfo)
{
strategy.RenderImage(renderInfo);
}
public String GetResultantText()
{
return strategy.GetResultantText();
}
}
可以这样使用:
ITextExtractionStrategy strategy = new RemappingExtractionFilter(new LocationTextExtractionStrategy());
string text = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);
当心,我不得不使用System.Reflection
来访问私有成员。有些环境可能禁止这样做。
同Java
我最初在 Java 中为 iText 编写代码,因为那是我的主要开发环境。因此,这里是初始 Java 版本:
public class RemappingExtractionFilter implements TextExtractionStrategy
{
public RemappingExtractionFilter(TextExtractionStrategy strategy) throws NoSuchFieldException, SecurityException
{
this.strategy = strategy;
this.stringField = TextRenderInfo.class.getDeclaredField("text");
this.stringField.setAccessible(true);
}
@Override
public void renderText(TextRenderInfo renderInfo)
{
DocumentFont font =renderInfo.getFont();
PdfDictionary dict = font.getFontDictionary();
PdfDictionary encoding = dict.getAsDict(PdfName.ENCODING);
PdfArray diffs = encoding.getAsArray(PdfName.DIFFERENCES);
;
StringBuilder builder = new StringBuilder();
for (byte b : renderInfo.getPdfString().getBytes())
{
PdfName name = diffs.getAsName((char)b);
String s = name.toString().substring(2);
int i = Integer.parseUnsignedInt(s, 16);
builder.append((char)i);
}
try
{
stringField.set(renderInfo, builder.toString());
}
catch (IllegalArgumentException | IllegalAccessException e)
{
e.printStackTrace();
}
strategy.renderText(renderInfo);
}
@Override
public void beginTextBlock()
{
strategy.beginTextBlock();
}
@Override
public void endTextBlock()
{
strategy.endTextBlock();
}
@Override
public void renderImage(ImageRenderInfo renderInfo)
{
strategy.renderImage(renderInfo);
}
@Override
public String getResultantText()
{
return strategy.getResultantText();
}
final TextExtractionStrategy strategy;
final Field stringField;
}
(RemappingExtractionFilter.java)
可以这样使用:
String extractRemapped(PdfReader reader, int pageNo) throws IOException, NoSuchFieldException, SecurityException
{
TextExtractionStrategy strategy = new RemappingExtractionFilter(new LocationTextExtractionStrategy());
return PdfTextExtractor.getTextFromPage(reader, pageNo, strategy);
}
为什么这样做?
首先,这不是所有提取问题的解决方案,仅用于像 OP 所展示的那样从 PDF 中提取文本。
此方法之所以有效,是因为 PDF 在其字体编码差异数组中使用的名称即使不是标准名称也可以被解释。这些名称构建为 /Gxx 其中 xx 是 ASCII 代码的十六进制表示这个名字代表的角色。
在许多情况下,下面的代码通过 ITextSharp 正确地从 PDF 中提取文本。
using (var pdfReader = new PdfReader(filename))
{
ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
var currentText = PdfTextExtractor.GetTextFromPage(
pdfReader,
1,
strategy);
currentText =
Encoding.UTF8.GetString(Encoding.Convert(
Encoding.Default,
Encoding.UTF8,
Encoding.Default.GetBytes(currentText)));
Console.WriteLine(currentText);
}
但是,在这个 PDF 的情况下,我得到的不是文本:“\u0001\u0002\u0003\u0004\u0005\u0006\a\b\t\a\u0001\u0002\u0003\u0004\u0005\u0006\u0003”
我尝试了不同的编码,甚至尝试了 PDFBox,但仍然无法正确解码 PDF。关于如何解决这个问题有什么想法吗?
确定 PDF 是否允许 文本被正确提取的一个很好的测试方法是在 Adobe Reader 中打开它并复制并粘贴文字.
例如:我复制了单词 ABSTRACT 并将其粘贴到 Notepad++ 中:
你在Notepad++中看到抽象这个词了吗?不是,你看%&SOH
'"%GS
。A表示为%,B表示为&,依此类推。
这清楚地表明 PDF 的内容不可访问:使用的编码 (% = A, & = B,...) 与人类实际使用的字符之间没有映射可以理解。
简而言之:PDF 不允许您提取文本,iText 不行,iTextSharp 不行,PDFBox 不行。您必须找到一个 OCR 工具,然后对整个文档进行 OCR。
如需了解更多信息,您可能需要观看以下视频:
仍然提取文本
但是实际上有一种稍微邪恶的方法可以从手头的 PDF 中提取文本!
wrapping one's text extraction strategy in a instance of the following class,乱码文本被正确文本替换:
public class RemappingExtractionFilter : ITextExtractionStrategy
{
ITextExtractionStrategy strategy;
System.Reflection.FieldInfo stringField;
public RemappingExtractionFilter(ITextExtractionStrategy strategy)
{
this.strategy = strategy;
this.stringField = typeof(TextRenderInfo).GetField("text", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
}
public void RenderText(TextRenderInfo renderInfo)
{
DocumentFont font =renderInfo.GetFont();
PdfDictionary dict = font.FontDictionary;
PdfDictionary encoding = dict.GetAsDict(PdfName.ENCODING);
PdfArray diffs = encoding.GetAsArray(PdfName.DIFFERENCES);
;
StringBuilder builder = new StringBuilder();
foreach (byte b in renderInfo.PdfString.GetBytes())
{
PdfName name = diffs.GetAsName((char)b);
String s = name.ToString().Substring(2);
int i = Convert.ToInt32(s, 16);
builder.Append((char)i);
}
stringField.SetValue(renderInfo, builder.ToString());
strategy.RenderText(renderInfo);
}
public void BeginTextBlock()
{
strategy.BeginTextBlock();
}
public void EndTextBlock()
{
strategy.EndTextBlock();
}
public void RenderImage(ImageRenderInfo renderInfo)
{
strategy.RenderImage(renderInfo);
}
public String GetResultantText()
{
return strategy.GetResultantText();
}
}
可以这样使用:
ITextExtractionStrategy strategy = new RemappingExtractionFilter(new LocationTextExtractionStrategy());
string text = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);
当心,我不得不使用System.Reflection
来访问私有成员。有些环境可能禁止这样做。
同Java
我最初在 Java 中为 iText 编写代码,因为那是我的主要开发环境。因此,这里是初始 Java 版本:
public class RemappingExtractionFilter implements TextExtractionStrategy
{
public RemappingExtractionFilter(TextExtractionStrategy strategy) throws NoSuchFieldException, SecurityException
{
this.strategy = strategy;
this.stringField = TextRenderInfo.class.getDeclaredField("text");
this.stringField.setAccessible(true);
}
@Override
public void renderText(TextRenderInfo renderInfo)
{
DocumentFont font =renderInfo.getFont();
PdfDictionary dict = font.getFontDictionary();
PdfDictionary encoding = dict.getAsDict(PdfName.ENCODING);
PdfArray diffs = encoding.getAsArray(PdfName.DIFFERENCES);
;
StringBuilder builder = new StringBuilder();
for (byte b : renderInfo.getPdfString().getBytes())
{
PdfName name = diffs.getAsName((char)b);
String s = name.toString().substring(2);
int i = Integer.parseUnsignedInt(s, 16);
builder.append((char)i);
}
try
{
stringField.set(renderInfo, builder.toString());
}
catch (IllegalArgumentException | IllegalAccessException e)
{
e.printStackTrace();
}
strategy.renderText(renderInfo);
}
@Override
public void beginTextBlock()
{
strategy.beginTextBlock();
}
@Override
public void endTextBlock()
{
strategy.endTextBlock();
}
@Override
public void renderImage(ImageRenderInfo renderInfo)
{
strategy.renderImage(renderInfo);
}
@Override
public String getResultantText()
{
return strategy.getResultantText();
}
final TextExtractionStrategy strategy;
final Field stringField;
}
(RemappingExtractionFilter.java)
可以这样使用:
String extractRemapped(PdfReader reader, int pageNo) throws IOException, NoSuchFieldException, SecurityException
{
TextExtractionStrategy strategy = new RemappingExtractionFilter(new LocationTextExtractionStrategy());
return PdfTextExtractor.getTextFromPage(reader, pageNo, strategy);
}
为什么这样做?
首先,这不是所有提取问题的解决方案,仅用于像 OP 所展示的那样从 PDF 中提取文本。
此方法之所以有效,是因为 PDF 在其字体编码差异数组中使用的名称即使不是标准名称也可以被解释。这些名称构建为 /Gxx 其中 xx 是 ASCII 代码的十六进制表示这个名字代表的角色。