如何在C#中使用iTextSharp获取pdf文件中的特定段落?

我在我的C#winform应用程序中使用iTextSharp。我想在PDF文件中获取特定段落。 在iTextSharp中这可能吗?

是的,不是。

首先是没有。 PDF格式没有文本结构的概念,例如段落,句子甚至单词,它只有文本的运行。 两行文本彼此接近,以便我们将它们视为结构化这一事实是人类的事情。 当您在PDF中看到看起来像三行段落的内容时,实际上生成PDF的程序实际上完成了将文本切割成三个不相关的文本行,然后在特定的x,y坐标处绘制每一行的工作。 更糟糕的是,根据设计师的需求,每行文本可以由较小的运行组成,可以是单词,甚至只是字符。 所以它可能是draw "the cat in the hat" at 10,10或者它可能draw "the cat in the hat" at 10,10 draw "t" at 10,10, then draw "h" at 14,10, then draw "e" at 18,10等等上。 对于像Adobe InDesign这样设计精良的程序中的PDF来说,这实际上很常见。

现在是的。 实际上它可能是一个。 如果你愿意投入一点点工作,你可能会得到iTextSharp来做你想要的。 有一个名为PdfTextExtractor的类,它有一个名为GetTextFromPage的方法,它将从页面获取所有原始文本。 此方法的最后一个参数是实现ITextExtractionStrategy接口的对象。 如果您创建自己的实现此接口的类,则可以处理每个文本运行并执行您自己的逻辑。

在这个接口中有一个名为RenderText的方法,它为每次运行的文本调用。 您将获得一个iTextSharp.text.pdf.parser.TextRenderInfo对象,您可以从该对象中获取运行中的原始文本以及其他内容,例如当前开始的坐标,当前字体等。由于视觉线文本可以由多次运行组成,您可以使用此方法将运行的基线(起始x坐标)与上一次运行进行比较,以确定它是否是同一视线的一部分。

以下是该接口的实现示例:

  public class TextAsParagraphsExtractionStrategy : iTextSharp.text.pdf.parser.ITextExtractionStrategy { //Text buffer private StringBuilder result = new StringBuilder(); //Store last used properties private Vector lastBaseLine; //Buffer of lines of text and their Y coordinates. NOTE, these should be exposed as properties instead of fields but are left as is for simplicity's sake public List strings = new List(); public List baselines = new List(); //This is called whenever a run of text is encountered public void RenderText(iTextSharp.text.pdf.parser.TextRenderInfo renderInfo) { //This code assumes that if the baseline changes then we're on a newline Vector curBaseline = renderInfo.GetBaseline().GetStartPoint(); //See if the baseline has changed if ((this.lastBaseLine != null) && (curBaseline[Vector.I2] != lastBaseLine[Vector.I2])) { //See if we have text and not just whitespace if ((!String.IsNullOrWhiteSpace(this.result.ToString()))) { //Mark the previous line as done by adding it to our buffers this.baselines.Add(this.lastBaseLine[Vector.I2]); this.strings.Add(this.result.ToString()); } //Reset our "line" buffer this.result.Clear(); } //Append the current text to our line buffer this.result.Append(renderInfo.GetText()); //Reset the last used line this.lastBaseLine = curBaseline; } public string GetResultantText() { //One last time, see if there's anything left in the buffer if ((!String.IsNullOrWhiteSpace(this.result.ToString()))) { this.baselines.Add(this.lastBaseLine[Vector.I2]); this.strings.Add(this.result.ToString()); } //We're not going to use this method to return a string, instead after callers should inspect this class's strings and baselines fields. return null; } //Not needed, part of interface contract public void BeginTextBlock() { } public void EndTextBlock() { } public void RenderImage(ImageRenderInfo renderInfo) { } } 

要打电话给我们,我们会这样做:

  PdfReader reader = new PdfReader(workingFile); TextAsParagraphsExtractionStrategy S = new TextAsParagraphsExtractionStrategy(); iTextSharp.text.pdf.parser.PdfTextExtractor.GetTextFromPage(reader, 1, S); for (int i = 0; i < S.strings.Count; i++) { Console.WriteLine("Line {0,-5}: {1}", S.baselines[i], S.strings[i]); } 

我们实际上是从GetTextFromPage丢弃了值,而是检查了worker的baselinesstrings数组字段。 下一步是比较基线并尝试确定如何将线组合在​​一起成为段落。

我应该注意,并非所有段落的间距都与单独的文本行不同。 例如,如果您通过上面的代码运行下面创建的PDF,您将看到每行文本彼此相距18个点,无论该行是否形成新段落。 如果您打开它在Acrobat中创建的PDF并覆盖除了每行的第一个字母之外的所有内容,您将看到您的眼睛甚至无法区分换行符和段落符号。

  using (FileStream fs = new FileStream(workingFile, FileMode.Create, FileAccess.Write, FileShare.None)) { using (Document doc = new Document(PageSize.LETTER)) { using (PdfWriter writer = PdfWriter.GetInstance(doc, fs)) { doc.Open(); doc.Add(new Paragraph("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.")); doc.Add(new Paragraph("This")); doc.Add(new Paragraph("Is")); doc.Add(new Paragraph("A")); doc.Add(new Paragraph("Test")); doc.Close(); } } }