PDF使用C#读取高亮度文本(突出显示注释)

我使用iTextSharp编写了一个提取工具,可以从PDF文档中提取注释信息。 对于高亮注释,我只得到页面上突出显示的区域的矩形。

我的目标是提取已突出显示的文本。 为此,我使用`PdfTextExtractor’。

Rectangle rect = new Rectangle( pdfArray.GetAsNumber(0).FloatValue, pdfArray.GetAsNumber(1).FloatValue, pdfArray.GetAsNumber(2).FloatValue, pdfArray.GetAsNumber(3).FloatValue); RenderFilter[] filter = { new RegionTextRenderFilter(rect) }; ITextExtractionStrategy strategy = new FilteredTextRenderListener(new LocationTextExtractionStrategy(), filter); string textInsideRect = PdfTextExtractor.GetTextFromPage(pdfReader, pageNo, strategy); return textInsideRect; 

PdfTextExtractor返回的结果并不完全正确。 例如,它返回“即将消除纸张追逐”,即使只突出显示“消除”

有趣的是,包含突出显示的“消除”的TJ的整个文本是“将要消除纸张追逐” (TJ是将文本写入页面的PDF指令)。

我很想听到有关此问题的任何意见 – 也包括不涉及iTextSharp的解决方案。

原因

有趣的是,包含突出显示的“消除”的TJ的整个文本是“将要消除纸张追逐”(TJ是将文本写入页面的PDF指令)。

这实际上是您的问题的原因。 iText解析器类将文本转发到它们在内容流中作为连续字符串找到的片段中的渲染侦听器。 您使用的过滤机制会过滤这些碎片。 因此,filter接受整个句子。

因此,您需要的是一些预处理步骤,将这些片段分成各自的字符,然后将它们分别转发到过滤后的渲染侦听器。

这实际上很容易实现。 转发文本片段的参数类型TextRenderInfo,提供了一种自我分割的方法:

 /** * Provides detail useful if a listener needs access to the position of each individual glyph in the text render operation * @return A list of {@link TextRenderInfo} objects that represent each glyph used in the draw operation. The next effect is if there was a separate Tj opertion for each character in the rendered string * @since 5.3.3 */ public List getCharacterRenderInfos() // iText / Java virtual public List GetCharacterRenderInfos() // iTextSharp / .Net 

因此,您所要做的就是创建并使用RenderListener / IRenderListener实现,该实现将它获取的所有调用转发给另一个侦听器(在您的情况下是您的过滤侦听器),其中包含renderText / RenderText拆分其TextRenderInfo参数并将碎片转发一个单独一个人。

一个Java示例

随着OP要求更多细节,这里有更多代码。 但是,由于我主要使用Java,因此我将使用Java为iText提供它。 但是对于iTextSharp来说很容易移植到C#。

如上所述,需要预处理步骤,将文本片段分成各自的字符,并将它们单独转发到过滤后的渲染器监听器。

对于此步骤,您可以使用此类TextRenderInfoSplitter

 package stackoverflow.itext.extraction; import com.itextpdf.text.pdf.parser.ImageRenderInfo; import com.itextpdf.text.pdf.parser.TextExtractionStrategy; import com.itextpdf.text.pdf.parser.TextRenderInfo; public class TextRenderInfoSplitter implements TextExtractionStrategy { public TextRenderInfoSplitter(TextExtractionStrategy strategy) { this.strategy = strategy; } public void renderText(TextRenderInfo renderInfo) { for (TextRenderInfo info : renderInfo.getCharacterRenderInfos()) { strategy.renderText(info); } } public void beginTextBlock() { strategy.beginTextBlock(); } public void endTextBlock() { strategy.endTextBlock(); } public void renderImage(ImageRenderInfo renderInfo) { strategy.renderImage(renderInfo); } public String getResultantText() { return strategy.getResultantText(); } final TextExtractionStrategy strategy; } 

如果你有一个TextExtractionStrategy strategy (比如new FilteredTextRenderListener(new LocationTextExtractionStrategy(), filter) ),你现在可以用单字符TextRenderInfo实例来提供它,如下所示:

 String textInsideRect = PdfTextExtractor.getTextFromPage(reader, pageNo, new TextRenderInfoSplitter(strategy)); 

我使用在该答案中为该区域创建的PDF对其进行了测试

 Rectangle rect = new Rectangle(200, 600, 200, 135); 

作为参考,我标记了PDF中的区域:

带有标记区域的PDF屏幕截图

按区域过滤的文本提取没有TextRenderInfoSplitter导致:

 I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox 

使用TextRenderInfoSplitter按区域过滤的文本提取结果为:

  to create a PDF f ntents in the docu ng PDF 

顺便说一句,你在这里看到了将文本分成早期单个字符的缺点:使用非常大的字符间距排版最终文本行。 如果您保留PDF中的文本片段,文本提取策略仍然可以很容易地看到该行包含两个单词usingPDFBox 。 只要您逐字地将文本片段提供给文本提取策略,他们就可能会将这些广泛设置的单词解释为多个单字母单词。

一种提升

突出显示的单词“消除”例如被提取为“消除t”。 通过双击单词并在Adobe Acrobat Reader中突出显示,突出显示了这一点。

在上面的示例中发生了类似的事情,几乎没有触及感兴趣区域的字母使其成为结果。

这是由于RegionTextRenderFilter实现允许所有文本继续其基线与相关矩形相交,即使交集只包含一个点:

 public boolean allowText(TextRenderInfo renderInfo){ LineSegment segment = renderInfo.getBaseline(); Vector startPoint = segment.getStartPoint(); Vector endPoint = segment.getEndPoint(); float x1 = startPoint.get(Vector.I1); float y1 = startPoint.get(Vector.I2); float x2 = endPoint.get(Vector.I1); float y2 = endPoint.get(Vector.I2); return filterRect.intersectsLine(x1, y1, x2, y2); } 

鉴于您首先将文本拆分为字符,您可能需要检查它们各自的基线是否完全包含在相关区域中,即通过复制RegionTextRenderFilter然后替换该行来实现自己的RenderFilter

 return filterRect.intersectsLine(x1, y1, x2, y2); 

通过

 return filterRect.contains(x1, y1) && filterRect.contains(x2, y2); 

但是,根据Adobe Acrobat Reader中突出显示文本的准确程度,您可能希望以完全自定义的方式更改此内容。

突出显示注释表示四边形的集合,这些四边形表示页面上的区域中的/QuadPoints条目中的注释所包围的区域。

他们为什么这样?

实际上,这是我的错。 在Acrobat 1.0中,我处理了“查找文本”代码,该代码最初仅使用矩形来表示页面上选定区域。 在处理代码时,我对结果非常不满意,特别是对于文本跟随土地细节的地图。

结果,我使查找工具在页面上构建了一组四边形,并在可能的情况下使它们退火以构建单词。

在Acrobat 2.0中,负责完全广义文本提取的工程师构建了一个名为Wordy的算法,该算法比我的第一次切割更好,但他保留了四边形代码,因为这是对页面上内容的最准确表示。

几乎所有与文本相关的代码都被重构为使用此代码。

然后我们得到高亮注释。 将标记注释添加到Acrobat时,它们用于装饰页面上已有的文本。 当用户点击页面时,Wordy将文本提取到适当的数据结构中,然后文本选择工具将鼠标移动映射到四边形集上。 当创建文本高亮注释时,来自Wordy的四边形子集将被置于新文本高亮注释中。

如何获得突出显示的页面上的文字。 棘手。 您必须在页面上提取文本(您没有Wordy,抱歉),然后从注释中查找集合中包含的所有四边形。