PDFBox - Извлечение линии / прямоугольника - PullRequest
1 голос
/ 14 марта 2019

Я пытаюсь извлечь текстовые координаты и линейные (или прямоугольные) координаты из PDF.

Класс TextPosition имеет методы getXDirAdj() и getYDirAdj(), которые преобразуют координаты в соответствии с направлениемфрагмент текста, который представляет соответствующий объект TextPosition (исправлено на основе комментария от @mkl) Окончательный вывод является согласованным, независимо от поворота страницы.

Координаты, необходимые для вывода, - это X0, Y0 (верхний левый уголPAGE)

Это небольшая модификация решения @Tilman Hausherr.Координаты y инвертированы (высота - y), чтобы сохранить их в соответствии с координатами процесса извлечения текста, а также выходные данные записываются в CSV.

    public class LineCatcher extends PDFGraphicsStreamEngine
{
    private static final GeneralPath linePath = new GeneralPath();
    private static ArrayList<Rectangle2D> rectList= new ArrayList<Rectangle2D>();
    private int clipWindingRule = -1;
    private static String headerRecord = "Text|Page|x|y|width|height|space|font";

    public LineCatcher(PDPage page)
    {
        super(page);
    }

    public static void main(String[] args) throws IOException
    {
        if( args.length != 4 )
        {
            usage();
        }
        else
        {
            PDDocument document = null;
            FileOutputStream fop = null;
            File file;
            Writer osw = null;
            int numPages;
            double page_height;
            try
            {
                document = PDDocument.load( new File(args[0], args[1]) );
                numPages = document.getNumberOfPages();
                file = new File(args[2], args[3]);
                fop = new FileOutputStream(file);

                // if file doesnt exists, then create it
                if (!file.exists()) {
                    file.createNewFile();
                }

                osw = new OutputStreamWriter(fop, "UTF8");
                osw.write(headerRecord + System.lineSeparator());
                System.out.println("Line Processing numPages:" + numPages);
                for (int n = 0; n < numPages; n++) {
                    System.out.println("Line Processing page:" + n);
                    rectList = new ArrayList<Rectangle2D>();
                    PDPage page = document.getPage(n);
                    page_height = page.getCropBox().getUpperRightY();
                    LineCatcher lineCatcher = new LineCatcher(page);
                    lineCatcher.processPage(page);

                    try{
                        for(Rectangle2D rect:rectList) {

                            String pageNum = Integer.toString(n + 1);
                            String x = Double.toString(rect.getX());
                            String y = Double.toString(page_height - rect.getY()) ;
                            String w = Double.toString(rect.getWidth());
                            String h = Double.toString(rect.getHeight());
                            writeToFile(pageNum, x, y, w, h, osw);

                        }
                        rectList = null;
                        page = null;
                        lineCatcher = null;
                    }
                    catch(IOException io){
                        throw new IOException("Failed to Parse document for line processing. Incorrect document format. Page:" + n);
                    }
                };

            }
            catch(IOException io){
                throw new IOException("Failed to Parse document for line processing. Incorrect document format.");
            }
            finally
            {
                if ( osw != null ){
                    osw.close();
                }
                if( document != null )
                {
                    document.close();
                }
            }
        }
    }

    private static void writeToFile(String pageNum, String x, String y, String w, String h, Writer osw) throws IOException {
        String c = "^" + "|" +
                pageNum + "|" +
                x + "|" +
                y + "|" +
                w + "|" +
                h + "|" +
                "999" + "|" +
                "marker-only";
        osw.write(c + System.lineSeparator());
    }

    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
    {
        // to ensure that the path is created in the right direction, we have to create
        // it by combining single lines instead of creating a simple rectangle
        linePath.moveTo((float) p0.getX(), (float) p0.getY());
        linePath.lineTo((float) p1.getX(), (float) p1.getY());
        linePath.lineTo((float) p2.getX(), (float) p2.getY());
        linePath.lineTo((float) p3.getX(), (float) p3.getY());

        // close the subpath instead of adding the last line so that a possible set line
        // cap style isn't taken into account at the "beginning" of the rectangle
        linePath.closePath();
    }

    @Override
    public void drawImage(PDImage pdi) throws IOException
    {
    }

    @Override
    public void clip(int windingRule) throws IOException
    {
        // the clipping path will not be updated until the succeeding painting operator is called
        clipWindingRule = windingRule;

    }

    @Override
    public void moveTo(float x, float y) throws IOException
    {
        linePath.moveTo(x, y);
    }

    @Override
    public void lineTo(float x, float y) throws IOException
    {
        linePath.lineTo(x, y);
    }

    @Override
    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
    {
        linePath.curveTo(x1, y1, x2, y2, x3, y3);
    }

    @Override
    public Point2D getCurrentPoint() throws IOException
    {
        return linePath.getCurrentPoint();
    }

    @Override
    public void closePath() throws IOException
    {
        linePath.closePath();
    }

    @Override
    public void endPath() throws IOException
    {
        if (clipWindingRule != -1)
        {
            linePath.setWindingRule(clipWindingRule);
            getGraphicsState().intersectClippingPath(linePath);
            clipWindingRule = -1;
        }
        linePath.reset();

    }

    @Override
    public void strokePath() throws IOException
    {
        rectList.add(linePath.getBounds2D());
        linePath.reset();
    }

    @Override
    public void fillPath(int windingRule) throws IOException
    {
        linePath.reset();
    }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException
    {
        linePath.reset();
    }

    @Override
    public void shadingFill(COSName cosn) throws IOException
    {
    }

    /**
     * This will print the usage for this document.
     */
    private static void usage()
    {
        System.err.println( "Usage: java " + LineCatcher.class.getName() + " <input-pdf>"  + " <output-file>");
    }
}

Использовал класс PDFGraphicsStreamEngine для извлеченияКоординаты линии и прямоугольника.Координаты линий и прямоугольников не совпадают с координатами текста

PLOT of the text and line extract

Зеленый: текст Красный: координаты линии, полученные как черные: ожидаемые координаты(Получено после применения преобразования к выходу)

Попытка метода setRotation() исправить поворот перед запуском извлечения строки.Однако результаты не соответствуют.

Каковы возможные варианты получения поворота и получения согласованного вывода координат линии / прямоугольника с помощью PDFBox?

1 Ответ

2 голосов
/ 18 марта 2019

Насколько я понимаю требования здесь, ОП работает в системе координат с началом координат в верхнем левом углу видимой страницы (с учетом поворота страницы), x координаты увеличиваются досправа, y координаты увеличиваются вниз, а единицами измерения являются единицы пространства пользователя по умолчанию в формате PDF (обычно 1 / 72 дюйм).

В этой системе координат ему нужно извлечь (горизонтальные или вертикальные) линии в виде

  • координат левой / верхней конечной точки и
  • ширина / высота.

Преобразование LineCatcher результаты

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

. Для этого просто замените

for(Rectangle2D rect:rectList) {
    String pageNum = Integer.toString(n + 1);
    String x = Double.toString(rect.getX());
    String y = Double.toString(page_height - rect.getY()) ;
    String w = Double.toString(rect.getWidth());
    String h = Double.toString(rect.getHeight());
    writeToFile(pageNum, x, y, w, h, osw);
}

на

int pageRotation = page.getRotation();
PDRectangle pageCropBox = page.getCropBox();

for(Rectangle2D rect:rectList) {
    String pageNum = Integer.toString(n + 1);
    String x, y, w, h;
    switch(pageRotation) {
    case 0:
        x = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
        y = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
        w = Double.toString(rect.getWidth());
        h = Double.toString(rect.getHeight());
        break;
    case 90:
        x = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
        y = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
        w = Double.toString(rect.getHeight());
        h = Double.toString(rect.getWidth());
        break;
    case 180:
        x = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
        y = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
        w = Double.toString(rect.getWidth());
        h = Double.toString(rect.getHeight());
        break;
    case 270:
        x = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
        y = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
        w = Double.toString(rect.getHeight());
        h = Double.toString(rect.getWidth());
        break;
    default:
        throw new IOException(String.format("Unsupported page rotation %d on page %d.", pageRotation, page));
    }
    writeToFile(pageNum, x, y, w, h, osw);
}

( ExtractLinesWithDir test testExtractLineRotationTestWithDir)

Отношение к TextPosition.get?DirAdj() координатам

OP описывает координаты, ссылаясь на методы класса TextPosition getXDirAdj()и getYDirAdj().Действительно, эти методы возвращают координаты в системе координат с началом координат в верхнем левом углу страницы, а координаты y увеличиваются вниз после поворота страницы, так что текст рисуется вертикально .

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

Проблема с использованием значений TextPosition.get?DirAdj() в качестве координат в глобальном масштабе, однако, заключается в том, что в документах со страницами, текст которых нарисован в разных направлениях,собранные текстовые координаты внезапно относятся к разным системам координат.Таким образом, общее решение не должно собирать координаты так дико.Вместо этого он должен сначала определить ориентацию страницы (например, ориентацию, заданную поворотом страницы или ориентацию, разделяемую большей частью текста) и использовать координаты в фиксированной системе координат, заданной этой ориентацией, плюс указание направления письма текстакусок в вопросе.

...