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

4

我在我的C# winform应用程序中使用iTextSharp。我想要获取PDF文件中的特定段落。在iTextSharp中是否有可能实现这一点?

1个回答

24
是和不是。
首先是否定的。PDF格式没有文本结构概念,例如段落、句子甚至单词,它只有一串文本。我们认为两个文本串相近从而将它们视为结构化是人类的行为。当您在PDF中看到像三行段落的东西时,在实际情况下,生成PDF的程序实际上将文本切成了三个不相关的文本行,然后在特定的x,y坐标处绘制每行。更糟糕的是,根据设计师的要求,每行文本可能由较小的文本串组成,这些文本串可以是单词甚至只是字符。因此,它可能是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<string> strings = new List<String>();
        public List<float> baselines = new List<float>();

        //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获取的价值,而是检查工作线程的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();
                }
            }
        }

@非常好的解释。我尝试使用这段代码来构建一个段落。但是,知道坐标位置并没有帮助我,因为文本可以在PDF中的任何位置对齐。但是,非常好的解释..谢谢。 - Bibek Gautam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接