Как получить конкретный абзац в PDF-файл, используя iTextSharp в C #? - PullRequest
4 голосов
/ 13 января 2012

Я использую iTextSharp в своем приложении winform C #. Я хочу получить определенный параграф в файле PDF. Возможно ли это в iTextSharp?

1 Ответ

22 голосов
/ 13 января 2012

Да и нет.

Сначала нет.Формат 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 и так далее.На самом деле это довольно часто встречается в PDF-файлах из таких программ, как Adobe InDesign.

Теперь да.На самом деле это возможно.Если вы хотите немного поработать, вы можете заставить 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 и вместо этого проверяем работника baselines и strings поля массива.Следующим шагом для этого будет сравнение базовых линий и попытка определить, как сгруппировать строки, чтобы они стали абзацами.

Следует отметить, что не все абзацы имеют интервалы, которые отличаются от отдельных строк текста.Например, если вы запустите PDF-файл, созданный ниже, с помощью приведенного выше кода, вы увидите, что каждая строка текста находится на расстоянии 18 пунктов друг от друга, независимо от того, образует ли линия новый абзац или нет.Если вы откроете PDF-файл, созданный в Acrobat, и закроете все, кроме первой буквы каждой строки, вы увидите, что ваш глаз даже не различит разницу между разрывом строки и разрывом абзаца.

        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();
                }
            }
        }
...