Извлечение текстовой информации из pdf - PullRequest
0 голосов
/ 17 мая 2018

Как извлечь текстовую информацию, такую ​​как текстовые координаты, ширина, высота и т. Д., ?? Я пробовал это с 'Pdf clown' библиотекой. Она отлично работает для обычного текста, но для повернутый текст (90 / -90 градусов) выводит ширину / высоту как 0 (ноль).

А масштабные коэффициенты (scaleX, scaleY) для текстов с (90 / -90 градусов) отображаются как (0, 0) соответственно, где, как и для перевернутых текстов (повернутых на 180 градусов), это (-1, -1).

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

Файл, который я использую: https://nofile.io/f/Kvf2DkXvfj4/edit9.pdf

Код: Использование TextInfoExtractionSample.cs из примеров pdfclown

выход (для трех различных выравниваний текста в файле выше)

Текст [x: 283, y: 104, w: 126, h: -23] [размер шрифта: -24, стиль шрифта: ArialMT]: inverted_text

Текст [x: 265, y: 244, w: 0, h: 121] [размер шрифта: 0, стиль шрифта: ArialMT]: вертикальный_текст

Текст [x: 347, y: 131, w: 0, h: 167] [размер шрифта: 0, стиль шрифта: ArialMT]: vertical_minus90

1 Ответ

0 голосов
/ 22 мая 2018

Поскольку я больше дома с Java, чем с .Net, я проанализировал проблему и создал первый обходной путь в PDF Clown / Java;Я попробую портировать его на .Net позже.Тем не менее, это не должно быть слишком сложно, сделать это самостоятельно.

Проблема

Приведенный вами пример файла ясно показывает проблему при запуске его через PDF Clown TextInfoExtractionSample.

Снимок экрана edit9.pdf:

screen shot of original

Снимок экрана edit9.pdf после применения TextInfoExtractionSample:

imageTextInfoExtractionSample">

Вертикальный текст

Все выглядит хорошо.

Перевернутый текст

Поля для отдельных символов (зеленый) выглядит хорошо, но поле для всей строки "inverted_text" (пунктирный черный) исключает самые крайние символы.

Вертикальный текст

Размер отдельных полей для символов сокращается до 0x0 прямоугольников (невидимых вснимок экрана, но очевидный в анализе потока контента).Поле для всей строки уменьшается до линии (пунктирная черная) на базовой линии строки, в которой отсутствует длина в битах.

Текст под углами между

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

Блоки для целых строк также параллельны странице.

В двух словах

Текстовые поля и строковые блоки работают правильно только для вертикального текста.

В источниках

Это соответствует тому, что можно найти висходный код:

  • Классы Java Rectangle2D и .Net RectangleF, используемые для блоков символов при разработке, предназначены для прямоугольников, параллельных осям системы координат, и используются в нихманера в PDF клоун.Таким образом, они не могут правильно представлять ширину и высоту символов под произвольными углами.

  • Классы PDF Clown не включают атрибут Angle для представления поворотасимвол.

  • При расчете размеров блока символов учитываются только значения на главной диагонали агрегированной матрицы преобразования, т. е. ScaleX и ScaleY, и игнорируется ShearX и ShearY.Для текста, который не является вертикальным или перевернутым, однако, ShearX и ShearY важны, для вертикального текста ScaleX и ScaleY равны 0.

  • Переход отБазовая линия (собственный способ позиционирования текста в формате PDF) и верхняя часть символа (позиционирование текста в формате PDF Clown) выполняется только путем изменения координаты y и, следовательно, работает правильно только для текста в вертикальном и обратном направлениях.

Обходное решение

Реальное решение проблемы потребовало бы использования совершенно другого класса для блоков символов и строк, класса, который моделирует прямоугольники под произвольными углами.

Aоднако более быстрый обходной путь может заключаться в добавлении члена angle к классу TextChar и к ITextString и реализациям, а затем к рассмотрению этого угла при обработке блоков.Этот обходной путь реализован здесь.

Как уже упоминалось выше, обходной путь сначала реализуется в Java.

В Java

Firstмы добавляем угловой элемент к TextChar, вычисляем правильные значения для размеров блока и угла в ShowText классе операций и корректно устанавливаем эти значения в ContentScanner.TextStringWrapper.

Затем добавляем получатель угла кTextStringWrapperITextString в целом), который возвращает угол первого текстового символа строки.И мы улучшаем метод TextStringWrapper getBox, чтобы учитывать угол символов текста при определении строкового блока.

Наконец, мы расширим TextInfoExtractionSample, чтобы учитывать значения углов, когдарисование коробок.

Я назвал этот угловой элемент Alpha, как я назвал этот угол α в своих эскизах.Оглядываясь назад, Theta или просто Angle было бы более уместным.

TextChar

Новая переменная-член alpha

  private final double alpha;

Новый иизмененный конструктор

  // <constructors>
  public TextChar(
    char value,
    Rectangle2D box,
    TextStyle style,
    boolean virtual
    )
  {
      this(value, box, 0, style, virtual);
  }

  public TextChar(
    char value,
    Rectangle2D box,
    double alpha,
    TextStyle style,
    boolean virtual
    )
  {
    this.value = value;
    this.box = box;
    this.alpha = alpha;
    this.style = style;
    this.virtual = virtual;
  }
  // </constructors>

метод получения угла

  public double getAlpha() {
      return alpha;
  }

( TextChar.java )

ShowText

Обновление внутреннего интерфейса IScanner метод scanChar для переноса угла

void scanChar(
  char textChar,
  Rectangle2D textCharBox,
  double alpha
  );

( ShowText.java внутренний интерфейс IScanner)

Обновлен метод scan для правильного расчета размеров и угла прямоугольника и их перенаправления в IScanner реализацию

[...]
for(char textChar : textString.toCharArray())
{
  double charWidth = font.getWidth(textChar) * scaledFactor;

  if(textScanner != null)
  {
    /*
      NOTE: The text rendering matrix is recomputed before each glyph is painted
      during a text-showing operation.
    */
    AffineTransform trm = (AffineTransform)ctm.clone(); trm.concatenate(tm);
    double charHeight = font.getHeight(textChar,fontSize);

    // vvv--- changed
    double ascent = font.getAscent(fontSize);
    double x = trm.getTranslateX() + ascent * trm.getShearX();
    double y = contextHeight - trm.getTranslateY() - ascent * trm.getScaleY();
    double dx = charWidth * trm.getScaleX();
    double dy = charWidth * trm.getShearY();
    double alpha = Math.atan2(dy, dx);
    double w = Math.sqrt(dx*dx + dy*dy);
    dx = charHeight * trm.getShearX();
    dy = charHeight * trm.getScaleY();
    double h = Math.sqrt(dx*dx + dy*dy);
    Rectangle2D charBox = new Rectangle2D.Double(x, y, w, h);

    textScanner.scanChar(textChar,charBox, alpha);
    // ^^^--- changed
  }

  /*
    NOTE: After the glyph is painted, the text matrix is updated
    according to the glyph displacement and any applicable spacing parameter.
  */
  tm.translate(charWidth + charSpace + (textChar == ' ' ? wordSpace : 0), 0);
}
[...]

( ShowText.java )

Внутренний класс ContentScanner TextStringWrapper

Обновить TextStringWrapper конструктор ShowText.IScanner обратный вызов, чтобы принять аргумент угла и использовать его для построения TextChar

getBaseDataObject().scan(
  state,
  new ShowText.IScanner()
  {
    @Override
    public void scanChar(
      char textChar,
      Rectangle2D textCharBox,
      double alpha
      )
    {
      textChars.add(
        new TextChar(
          textChar,
          textCharBox,
          alpha,
          style,
          false
          )
        );
    }
  }
  );

Получатель для угла

public double getAlpha() {
    return textChars.isEmpty() ? 0 : textChars.get(0).getAlpha();
}

A getBox реализация, учитывающая угол

public Rectangle2D getBox(
  )
{
  if(box == null)
  {
    AffineTransform rot = null;
    Rectangle2D tempBox = null;
    for(TextChar textChar : textChars)
    {
      Rectangle2D thisBox = textChar.getBox();
      if (rot == null) {
          rot = AffineTransform.getRotateInstance(textChar.getAlpha(), thisBox.getX(), thisBox.getY());
          tempBox = (Rectangle2D)thisBox.clone();
      } else {
          Point2D corner = new Point2D.Double(thisBox.getX(), thisBox.getY());
          rot.transform(corner, corner);
          tempBox.add(new Rectangle2D.Double(corner.getX(), corner.getY(), thisBox.getWidth(), thisBox.getHeight()));
      }
    }
    if (tempBox != null) {
        try {
            Point2D corner = new Point2D.Double(tempBox.getX(), tempBox.getY());
            rot.invert();
            rot.transform(corner, corner);
            box = new Rectangle2D.Double(corner.getX(), corner.getY(), tempBox.getWidth(), tempBox.getHeight());
        } catch (NoninvertibleTransformException e) {
            e.printStackTrace();
        }
    }
  }
  return box;
}

( ContentScanner.java внутренний класс TextStringWrapper)

ITextString

Новый метод получения углов

  public double getAlpha();

( ITextString.java)

TextExtractor внутренний класс TextString

Новый получатель угла

public double getAlpha() {
    return textChars.isEmpty() ? 0 : textChars.get(0).getAlpha();
}

( TextExtractor.java )

TextInfoExtractionSample

Меняется на extract для правильного использования угла при выделении полей

[...]
for (ContentScanner.TextStringWrapper textString : text.getTextStrings())
{
    Rectangle2D textStringBox = textString.getBox();
    System.out.println("Text [" + "x:" + Math.round(textStringBox.getX()) + "," + "y:" + Math.round(textStringBox.getY()) + "," + "w:"
            + Math.round(textStringBox.getWidth()) + "," + "h:" + Math.round(textStringBox.getHeight()) + "] [font size:"
            + Math.round(textString.getStyle().getFontSize()) + "]: " + textString.getText());

    // Drawing text character bounding boxes...
    colorIndex = (colorIndex + 1) % textCharBoxColors.length;
    composer.setStrokeColor(textCharBoxColors[colorIndex]);
    for (TextChar textChar : textString.getTextChars())
    {
        // vvv--- changed
        Rectangle2D box = textChar.getBox();
        composer.beginLocalState();
        AffineTransform rot = AffineTransform.getRotateInstance(textChar.getAlpha());
        composer.applyMatrix(rot.getScaleX(), rot.getShearY(), rot.getShearX(), rot.getScaleY(),
                box.getX(), composer.getScanner().getContextSize().getHeight() - box.getY());
        composer.add(new DrawRectangle(0, - box.getHeight(), box.getWidth(), box.getHeight()));

        composer.stroke();
        composer.end();
        // ^^^--- changed
    }

    // Drawing text string bounding box...
    composer.beginLocalState();
    composer.setLineDash(new LineDash(new double[] { 5 }));
    composer.setStrokeColor(textStringBoxColor);
    // vvv--- changed
    AffineTransform rot = AffineTransform.getRotateInstance(textString.getAlpha());
    composer.applyMatrix(rot.getScaleX(), rot.getShearY(), rot.getShearX(), rot.getScaleY(),
            textStringBox.getX(), composer.getScanner().getContextSize().getHeight() - textStringBox.getY());
    composer.add(new DrawRectangle(0, - textStringBox.getHeight(), textStringBox.getWidth(), textStringBox.getHeight()));
    // ^^^--- changed
    composer.stroke();
    composer.end();
}
[...]

( TextInfoExtractionSample методextract)

Результат

И текстовые поля, и строковые блоки теперь соответствуют назначению:

Screenshot with the work-around in action

Значения ширины и высоты теперь тоже в порядке:

Text [x:415,y:104,w:138,h:23] [font size:-24]: inverted_text
Text [x:247,y:365,w:128,h:23] [font size:0]: vertical_text
Text [x:364,y:131,w:180,h:23] [font size:0]: vertical_minus90
...