Как я могу извлечь индекс / верхний индекс из PDF с помощью iTextSharp?

iTextSharp хорошо работает, извлекает простой текст из документов PDF, но у меня возникают проблемы с текстом индекса / надстрочного текста, распространенным в технических документах.

TextChunk.SameLine() требует, чтобы два куска имели одинаковое вертикальное позиционирование для «одной» строки, что не относится к надстрочному или индексному тексту. Например, на стр. 11 настоящего документа в разделе «ЭФФЕКТИВНОСТЬ СГОРАНИЯ»:

http://www.mass.gov/courts/docs/lawlib/300-399cmr/310cmr7.pdf

Ожидаемый текст:

 monoxide (CO) in flue gas in accordance with the following formula: CE = [CO2 /(CO + CO2)] 

Результат поиска:

 monoxide (CO) in flue gas in accordance with the following formula: CE = [CO /(CO + CO )] 2 2 

Я переместил SameLine() в LocationTextExtractionStrategy и сделал публичные геттеры для частных свойств TextChunk он читает. Это позволило мне настроить допуск «на лету» в моем собственном подclassе, показанный здесь:

 public class SubSuperStrategy : LocationTextExtractionStrategy { public int SameLineOrientationTolerance { get; set; } public int SameLineDistanceTolerance { get; set; } public override bool SameLine(TextChunk chunk1, TextChunk chunk2) { var orientationDelta = Math.Abs(chunk1.OrientationMagnitude - chunk2.OrientationMagnitude); if(orientationDelta > SameLineOrientationTolerance) return false; var distDelta = Math.Abs(chunk1.DistPerpendicular - chunk2.DistPerpendicular); return (distDelta <= SameLineDistanceTolerance); } } 

Используя SameLineDistanceTolerance 3 , это исправляет, к какой строке привязаны суб / SameLineDistanceTolerance , но относительное положение текста отключено:

 monoxide (CO) in flue gas in accordance with the following formula: CE = [CO /(CO + CO )] 2 2 

Иногда куски вставляются где-то посередине текста, а иногда (как в этом примере) в конце. В любом случае, они не попадают в нужное место. Я подозреваю, что это может иметь какое-то отношение к размерам шрифта, но я нахожусь в своем понимании недр этого кода.

Кто-нибудь нашел другой способ справиться с этим?

(Я рад представить запрос на перенос с моими изменениями, если это поможет.)

Чтобы правильно извлечь эти индексы и надстрочные индексы в строке, нужно использовать другой подход, чтобы проверить, находятся ли две текстовые fragmentы в одной строке. К таким подходам относятся следующие classы.

Я больше дома в Java / iText; таким образом, я реализовал этот подход в Java сначала и только потом перевел его на C # / iTextSharp.

Подход с использованием Java & iText

Я использую текущую ветку развития iText 5.5.8-SNAPSHOT.

Способ идентификации строк

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

 public class TextLineFinder implements RenderListener { @Override public void beginTextBlock() { } @Override public void endTextBlock() { } @Override public void renderImage(ImageRenderInfo renderInfo) { } /* * @see RenderListener#renderText(TextRenderInfo) */ @Override public void renderText(TextRenderInfo renderInfo) { LineSegment ascentLine = renderInfo.getAscentLine(); LineSegment descentLine = renderInfo.getDescentLine(); float[] yCoords = new float[]{ ascentLine.getStartPoint().get(Vector.I2), ascentLine.getEndPoint().get(Vector.I2), descentLine.getStartPoint().get(Vector.I2), descentLine.getEndPoint().get(Vector.I2) }; Arrays.sort(yCoords); addVerticalUseSection(yCoords[0], yCoords[3]); } /** * This method marks the given interval as used. */ void addVerticalUseSection(float from, float to) { if (to < from) { float temp = to; to = from; from = temp; } int i=0, j=0; for (; i i) verticalFlips.remove(j); if (toOutsideInterval) verticalFlips.add(i, to); if (fromOutsideInterval) verticalFlips.add(i, from); } final List verticalFlips = new ArrayList(); } 

( TextLineFinder.java )

Этот RenderListener пытается идентифицировать горизонтальные текстовые строки, проецируя ограничивающие текст поля на ось y. Предполагается, что эти outlookы не перекрываются для текста из разных строк, даже в случае индексов и надстрочных индексов.

Этот class по существу является приведенной формой используемого в этом ответе параметра PageVerticalAnalyzer .

Сортировка текстовых fragmentов по этим строкам

Выделив строки, подобные приведенным выше, можно настроить расположение LocationTextExtractionStrategy в iText для сортировки по этим строкам следующим образом:

 public class HorizontalTextExtractionStrategy extends LocationTextExtractionStrategy { public class HorizontalTextChunk extends TextChunk { public HorizontalTextChunk(String string, Vector startLocation, Vector endLocation, float charSpaceWidth) { super(string, startLocation, endLocation, charSpaceWidth); } @Override public int compareTo(TextChunk rhs) { if (rhs instanceof HorizontalTextChunk) { HorizontalTextChunk horRhs = (HorizontalTextChunk) rhs; int rslt = Integer.compare(getLineNumber(), horRhs.getLineNumber()); if (rslt != 0) return rslt; return Float.compare(getStartLocation().get(Vector.I1), rhs.getStartLocation().get(Vector.I1)); } else return super.compareTo(rhs); } @Override public boolean sameLine(TextChunk as) { if (as instanceof HorizontalTextChunk) { HorizontalTextChunk horAs = (HorizontalTextChunk) as; return getLineNumber() == horAs.getLineNumber(); } else return super.sameLine(as); } public int getLineNumber() { Vector startLocation = getStartLocation(); float y = startLocation.get(Vector.I2); List flips = textLineFinder.verticalFlips; if (flips == null || flips.isEmpty()) return 0; if (y < flips.get(0)) return flips.size() / 2 + 1; for (int i = 1; i < flips.size(); i+=2) { if (y < flips.get(i)) { return (1 + flips.size() - i) / 2; } } return 0; } } @Override public void renderText(TextRenderInfo renderInfo) { textLineFinder.renderText(renderInfo); LineSegment segment = renderInfo.getBaseline(); if (renderInfo.getRise() != 0){ // remove the rise from the baseline - we do this because the text from a super/subscript render operations should probably be considered as part of the baseline of the text the super/sub is relative to Matrix riseOffsetTransform = new Matrix(0, -renderInfo.getRise()); segment = segment.transformBy(riseOffsetTransform); } TextChunk location = new HorizontalTextChunk(renderInfo.getText(), segment.getStartPoint(), segment.getEndPoint(), renderInfo.getSingleSpaceWidth()); getLocationalResult().add(location); } public HorizontalTextExtractionStrategy() throws NoSuchFieldException, SecurityException { locationalResultField = LocationTextExtractionStrategy.class.getDeclaredField("locationalResult"); locationalResultField.setAccessible(true); textLineFinder = new TextLineFinder(); } @SuppressWarnings("unchecked") List getLocationalResult() { try { return (List) locationalResultField.get(this); } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException(e); } } final Field locationalResultField; final TextLineFinder textLineFinder; } 

( HorizontalTextExtractionStrategy.java )

Эта TextExtractionStrategy использует TextLineFinder для идентификации горизонтальных текстовых строк, а затем использует эту информацию для сортировки текстовых fragmentов.

Остерегайтесь, этот код использует reflection для доступа к частным родительским членам classа. Это может быть запрещено во всех средах. В таком случае просто скопируйте LocationTextExtractionStrategy и прямо вставьте код.

Извлечение текста

Теперь можно использовать эту страtagsю извлечения текста, чтобы извлечь текст с помощью встроенных надстроек и индексов, например:

 String extract(PdfReader reader, int pageNo) throws IOException, NoSuchFieldException, SecurityException { return PdfTextExtractor.getTextFromPage(reader, pageNo, new HorizontalTextExtractionStrategy()); } 

(из ExtractSuperAndSubInLine.java )

Текст примера на стр. 11 документа OP в разделе «ЭФФЕКТИВНОСТЬ СГОРАНИЯ» теперь извлекается следующим образом:

 monoxide (CO) in flue gas in accordance with the following formula: CE = [CO 2/(CO + CO 2 )] 

Тот же подход с использованием C # и iTextSharp

Объяснения, предупреждения и примеры результатов из раздела, ориентированного на Java, все еще применяются, вот код:

Я использую iTextSharp 5.5.7.

Способ идентификации строк

 public class TextLineFinder : IRenderListener { public void BeginTextBlock() { } public void EndTextBlock() { } public void RenderImage(ImageRenderInfo renderInfo) { } public void RenderText(TextRenderInfo renderInfo) { LineSegment ascentLine = renderInfo.GetAscentLine(); LineSegment descentLine = renderInfo.GetDescentLine(); float[] yCoords = new float[]{ ascentLine.GetStartPoint()[Vector.I2], ascentLine.GetEndPoint()[Vector.I2], descentLine.GetStartPoint()[Vector.I2], descentLine.GetEndPoint()[Vector.I2] }; Array.Sort(yCoords); addVerticalUseSection(yCoords[0], yCoords[3]); } void addVerticalUseSection(float from, float to) { if (to < from) { float temp = to; to = from; from = temp; } int i=0, j=0; for (; i i) verticalFlips.RemoveAt(j); if (toOutsideInterval) verticalFlips.Insert(i, to); if (fromOutsideInterval) verticalFlips.Insert(i, from); } public List verticalFlips = new List(); } 

Сортировка текстовых fragmentов по этим строкам

 public class HorizontalTextExtractionStrategy : LocationTextExtractionStrategy { public class HorizontalTextChunk : TextChunk { public HorizontalTextChunk(String stringValue, Vector startLocation, Vector endLocation, float charSpaceWidth, TextLineFinder textLineFinder) : base(stringValue, startLocation, endLocation, charSpaceWidth) { this.textLineFinder = textLineFinder; } override public int CompareTo(TextChunk rhs) { if (rhs is HorizontalTextChunk) { HorizontalTextChunk horRhs = (HorizontalTextChunk) rhs; int rslt = CompareInts(getLineNumber(), horRhs.getLineNumber()); if (rslt != 0) return rslt; return CompareFloats(StartLocation[Vector.I1], rhs.StartLocation[Vector.I1]); } else return base.CompareTo(rhs); } public override bool SameLine(TextChunk a) { if (a is HorizontalTextChunk) { HorizontalTextChunk horAs = (HorizontalTextChunk) a; return getLineNumber() == horAs.getLineNumber(); } else return base.SameLine(a); } public int getLineNumber() { Vector startLocation = StartLocation; float y = startLocation[Vector.I2]; List flips = textLineFinder.verticalFlips; if (flips == null || flips.Count == 0) return 0; if (y < flips[0]) return flips.Count / 2 + 1; for (int i = 1; i < flips.Count; i+=2) { if (y < flips[i]) { return (1 + flips.Count - i) / 2; } } return 0; } private static int CompareInts(int int1, int int2){ return int1 == int2 ? 0 : int1 < int2 ? -1 : 1; } private static int CompareFloats(float float1, float float2) { return float1 == float2 ? 0 : float1 < float2 ? -1 : 1; } TextLineFinder textLineFinder; } public override void RenderText(TextRenderInfo renderInfo) { textLineFinder.RenderText(renderInfo); LineSegment segment = renderInfo.GetBaseline(); if (renderInfo.GetRise() != 0){ // remove the rise from the baseline - we do this because the text from a super/subscript render operations should probably be considered as part of the baseline of the text the super/sub is relative to Matrix riseOffsetTransform = new Matrix(0, -renderInfo.GetRise()); segment = segment.TransformBy(riseOffsetTransform); } TextChunk location = new HorizontalTextChunk(renderInfo.GetText(), segment.GetStartPoint(), segment.GetEndPoint(), renderInfo.GetSingleSpaceWidth(), textLineFinder); getLocationalResult().Add(location); } public HorizontalTextExtractionStrategy() { locationalResultField = typeof(LocationTextExtractionStrategy).GetField("locationalResult", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); textLineFinder = new TextLineFinder(); } List getLocationalResult() { return (List) locationalResultField.GetValue(this); } System.Reflection.FieldInfo locationalResultField; TextLineFinder textLineFinder; } 

Извлечение текста

  string extract(PdfReader reader, int pageNo) { return PdfTextExtractor.GetTextFromPage(reader, pageNo, new HorizontalTextExtractionStrategy()); } 

UPDATE: изменения в LocationTextExtractionStrategy

В iText 5.5.9-SNAPSHOT Commits 53526e4854fcb80c86cbc2e113f7a07401dc9a67 («Рефактор LocationTextExtractionStrategy …») через 1ab350beae148be2a4bef5e663b3d67a004ff9f8 («Сделать TextChunkLocation a Comparable <> class …») архитектура LocationTextExtractionStrategy была изменена, чтобы разрешить такие настройки без необходимости reflection.

К сожалению, это изменение нарушает представленную выше HorizontalTextExtractionStrategy. Для версий iText после этих коммитов можно использовать следующую страtagsю:

 public class HorizontalTextExtractionStrategy2 extends LocationTextExtractionStrategy { public static class HorizontalTextChunkLocationStrategy implements TextChunkLocationStrategy { public HorizontalTextChunkLocationStrategy(TextLineFinder textLineFinder) { this.textLineFinder = textLineFinder; } @Override public TextChunkLocation createLocation(TextRenderInfo renderInfo, LineSegment baseline) { return new HorizontalTextChunkLocation(baseline.getStartPoint(), baseline.getEndPoint(), renderInfo.getSingleSpaceWidth()); } final TextLineFinder textLineFinder; public class HorizontalTextChunkLocation implements TextChunkLocation { /** the starting location of the chunk */ private final Vector startLocation; /** the ending location of the chunk */ private final Vector endLocation; /** unit vector in the orientation of the chunk */ private final Vector orientationVector; /** the orientation as a scalar for quick sorting */ private final int orientationMagnitude; /** perpendicular distance to the orientation unit vector (ie the Y position in an unrotated coordinate system) * we round to the nearest integer to handle the fuzziness of comparing floats */ private final int distPerpendicular; /** distance of the start of the chunk parallel to the orientation unit vector (ie the X position in an unrotated coordinate system) */ private final float distParallelStart; /** distance of the end of the chunk parallel to the orientation unit vector (ie the X position in an unrotated coordinate system) */ private final float distParallelEnd; /** the width of a single space character in the font of the chunk */ private final float charSpaceWidth; public HorizontalTextChunkLocation(Vector startLocation, Vector endLocation, float charSpaceWidth) { this.startLocation = startLocation; this.endLocation = endLocation; this.charSpaceWidth = charSpaceWidth; Vector oVector = endLocation.subtract(startLocation); if (oVector.length() == 0) { oVector = new Vector(1, 0, 0); } orientationVector = oVector.normalize(); orientationMagnitude = (int)(Math.atan2(orientationVector.get(Vector.I2), orientationVector.get(Vector.I1))*1000); // see http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html // the two vectors we are crossing are in the same plane, so the result will be purely // in the z-axis (out of plane) direction, so we just take the I3 component of the result Vector origin = new Vector(0,0,1); distPerpendicular = (int)(startLocation.subtract(origin)).cross(orientationVector).get(Vector.I3); distParallelStart = orientationVector.dot(startLocation); distParallelEnd = orientationVector.dot(endLocation); } public int orientationMagnitude() { return orientationMagnitude; } public int distPerpendicular() { return distPerpendicular; } public float distParallelStart() { return distParallelStart; } public float distParallelEnd() { return distParallelEnd; } public Vector getStartLocation() { return startLocation; } public Vector getEndLocation() { return endLocation; } public float getCharSpaceWidth() { return charSpaceWidth; } /** * @param as the location to compare to * @return true is this location is on the the same line as the other */ public boolean sameLine(TextChunkLocation as) { if (as instanceof HorizontalTextChunkLocation) { HorizontalTextChunkLocation horAs = (HorizontalTextChunkLocation) as; return getLineNumber() == horAs.getLineNumber(); } else return orientationMagnitude() == as.orientationMagnitude() && distPerpendicular() == as.distPerpendicular(); } /** * Computes the distance between the end of 'other' and the beginning of this chunk * in the direction of this chunk's orientation vector. Note that it's a bad idea * to call this for chunks that aren't on the same line and orientation, but we don't * explicitly check for that condition for performance reasons. * @param other * @return the number of spaces between the end of 'other' and the beginning of this chunk */ public float distanceFromEndOf(TextChunkLocation other) { float distance = distParallelStart() - other.distParallelEnd(); return distance; } public boolean isAtWordBoundary(TextChunkLocation previous) { /** * Here we handle a very specific case which in PDF may look like: * -.232 Tc [( P)-226.2(r)-231.8(e)-230.8(f)-238(a)-238.9(c)-228.9(e)]TJ * The font's charSpace width is 0.232 and it's compensated with charSpacing of 0.232. * And a resultant TextChunk.charSpaceWidth comes to TextChunk constructor as 0. * In this case every chunk is considered as a word boundary and space is added. * We should consider charSpaceWidth equal (or close) to zero as a no-space. */ if (getCharSpaceWidth() < 0.1f) return false; float dist = distanceFromEndOf(previous); return dist < -getCharSpaceWidth() || dist > getCharSpaceWidth()/2.0f; } public int getLineNumber() { Vector startLocation = getStartLocation(); float y = startLocation.get(Vector.I2); List flips = textLineFinder.verticalFlips; if (flips == null || flips.isEmpty()) return 0; if (y < flips.get(0)) return flips.size() / 2 + 1; for (int i = 1; i < flips.size(); i+=2) { if (y < flips.get(i)) { return (1 + flips.size() - i) / 2; } } return 0; } @Override public int compareTo(TextChunkLocation rhs) { if (rhs instanceof HorizontalTextChunkLocation) { HorizontalTextChunkLocation horRhs = (HorizontalTextChunkLocation) rhs; int rslt = Integer.compare(getLineNumber(), horRhs.getLineNumber()); if (rslt != 0) return rslt; return Float.compare(getStartLocation().get(Vector.I1), rhs.getStartLocation().get(Vector.I1)); } else { int rslt; rslt = Integer.compare(orientationMagnitude(), rhs.orientationMagnitude()); if (rslt != 0) return rslt; rslt = Integer.compare(distPerpendicular(), rhs.distPerpendicular()); if (rslt != 0) return rslt; return Float.compare(distParallelStart(), rhs.distParallelStart()); } } } } @Override public void renderText(TextRenderInfo renderInfo) { textLineFinder.renderText(renderInfo); super.renderText(renderInfo); } public HorizontalTextExtractionStrategy2() throws NoSuchFieldException, SecurityException { this(new TextLineFinder()); } public HorizontalTextExtractionStrategy2(TextLineFinder textLineFinder) throws NoSuchFieldException, SecurityException { super(new HorizontalTextChunkLocationStrategy(textLineFinder)); this.textLineFinder = textLineFinder; } final TextLineFinder textLineFinder; } 

( HorizontalTextExtractionStrategy2.java )

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

  Vector thisFacade = this.ascentLine.GetStartPoint().Subtract(this.descentLine.GetStartPoint()); Vector infoFacade = renderInfo.GetAscentLine().GetStartPoint().Subtract(renderInfo.GetDescentLine().GetStartPoint()); if (baseVector.Cross(ascent2base).Dot(baseVector.Cross(descent2base)) < 0 && infoFacade.LengthSquared < thisFacade.LengthSquared - sameHeightThreshols) 

Подробнее после Chistmass.

  • Можно ли удалить несколько страниц PDF-документа?
  • Сохранить pdf в jpeg с помощью c #
  • Сохранить несколько ссылок Pdfs сразу
  • Как обрезать навсегда в Acrobat?
  • Может ли PDF-файл содержать ресурсы, загружаемые из Интернета?
  • Настроить Okular для изменения свойств инструмента выделения
  • Как узнать, встроены ли шрифты в PDF-файл или нет?
  • Автоматически добавлять комментарии к файлам PDF с регулярными выражениями
  • Приложение преобразования DJVU в PDF
  • Есть ли PDF для конвертера Mobi?
  • Измените размер A4 PDF на SRA4 и содержимое центра
  • Interesting Posts

    Защита API: базовая аутентификация SSL и HTTP и подпись

    Можем ли мы сохранить делегатов в файле (C #)

    Создать новый class из переменной в Java

    Как разработать расширяемое программное обеспечение (архитектура плагина)?

    LibreOffice Calc: Как получить общее количество для HH: MM: SS-ячейки

    Цепочка Ajax вызывает в AngularJs

    Разрешение на создание символических ссылок в Windows 7?

    Автоматическое открытие windows завершения в Eclipse

    Что означает, что сравнение строк и символов в Swift не зависит от языка?

    Есть ли способ проверить, содержат ли две коллекции одни и те же элементы, независимо от порядка?

    методы getViewTypeCount и getItemViewType для ArrayAdapter

    Как сделать redirect в JSF

    Перемещение скрытых файлов / папок с помощью командной строки или пакетного файла

    Dropbox выборочная синхронизация – возможно ли, чтобы новые папки, созданные на других устройствах, не были автоматически добавлены в папки синхронизации на текущем устройстве?

    Перенаправить NSLog в файл в Swift не работает

    Давайте будем гением компьютера.