PDF Чтение высокоуровневого текста (выделение аннотаций) с использованием C #

Я написал инструмент извлечения, используя iTextSharp, который извлекает информацию из документов PDF. Для annotations выделения я получаю прямоугольник для области на выделенной странице.

Я собираюсь извлечь выделенный текст. Для этого я использую `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 передают текст слушателям рендеринга в частях, которые они находят в виде непрерывных строк в streamе содержимого. Фильтрующий механизм, который вы используете, фильтрует эти части. Таким образом, это предложение принимается фильтром.

Таким образом, вам необходим некоторый шаг предварительной обработки, который разбивает эти части на отдельные персонажи и пересылает их индивидуально в ваш отфильтрованный прослушиватель.

На самом деле это довольно просто реализовать. Тип аргумента, в котором пересылаются текстовые fragmentы, 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

Поскольку ОП запросил более подробную информацию, вот еще несколько кодов. Поскольку я преимущественно работаю с Java, я предоставляю его в Java для iText. Но легко переносить на C # для iTextSharp.

Как упоминалось выше, необходим этап предварительной обработки, который разбивает текстовые fragmentы на отдельные персонажи и пересылает их индивидуально в ваш отфильтрованный приемник визуализации.

Для этого шага вы можете использовать этот class 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 так, как они есть, страtagsи извлечения текста по-прежнему легко могут видеть, что строка состоит из двух слов и PDFBox . Как только вы передаете текстовые сегменты по символам в страtagsи выделения текста, они, вероятно, будут интерпретировать такие широко заданные слова, как много однобуквенных слов.

Улучшение

Выделенное слово «исключить», например, извлекается как «исключить t». Это было подчеркнуто двойным щелчком по слову и выделено в Adobe Acrobat Reader.

Что-то подобное происходит в моем примере выше, письма, едва затрагивающие интересующую область, делают это результатом.

Это связано с реализацией allowText позволяющей продолжить весь текст, чья базовая линия пересекает рассматриваемый прямоугольник, даже если пересечение состоит всего из одной точки:

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

Учитывая, что вы сначала разделили текст на символы, вам может потребоваться проверить, полностью ли их соответствующая базовая строка содержится в рассматриваемой области, т.е. реализовать собственный RenderFilter , скопировав RegionTextRenderFilter а затем заменив строку

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

от

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

В зависимости от того, насколько точно выделен текст в Adobe Acrobat Reader , вы можете изменить это полностью на обычном пути.

Выделение аннотаций представляет собой набор квадрилатеров, которые представляют область (области) на странице, окруженную аннотацией в записи /QuadPoints в словаре.

Почему они так?

На самом деле это моя вина. В Acrobat 1.0 я работал над кодом «найти текст», который первоначально использовал только прямоугольник для представления выбранной области на странице. Во время работы над кодом я был очень недоволен результатами, особенно с картами, где текст следовал за деталями земли.

В результате я заставил инструмент find создать на странице ряд квадрилатеров и отжигать их, когда это возможно, для создания слов.

В Acrobat 2.0 инженер, ответственный за полное обобщенное извлечение текста, создал алгоритм под названием Wordy, который был лучше моего первого разреза, но он сохранил четырехсторонний код, поскольку это было наиболее точное представление о том, что было на странице.

Почти весь текстовый код был реорганизован для использования этого кода.

Затем мы выделяем annotations. Когда в Acrobat были добавлены annotations разметки, они были использованы для украшения текста, который уже был на странице. Когда пользователь нажимает на страницу, Wordy извлекает текст в соответствующие структуры данных, а затем инструмент выбора текста отображает движение мыши на четырехугольные множества. Когда создается аннотация для выделения текста, поднабор quadrilaterals из Wordy помещается в новую аннотацию выделения текста.

Как вы получаете слова на выделенной странице. Tricky. Вы должны извлечь текст на странице (у вас нет Wordy, извините), а затем найдите все квадратики, которые содержатся в наборе из annotations.