Отфильтровать весь текст выше определенного размера шрифта из PDF - PullRequest
0 голосов
/ 20 октября 2019

Как видно из заголовка, я хочу отфильтровать весь текст из PDF, размер которого превышает определенный размер шрифта. В настоящее время я использую библиотеку PDFBox, но я открыт для использования любой другой бесплатной библиотеки для Java.

Мой подход заключался в использовании PDFStreamParser для итерации по токенам. Когда я передаю оператор Tf, размер которого больше моего порога, не добавляйте следующий увиденный Tj / TJ. Однако мне стало ясно, что этот относительно простой подход не будет работать, потому что текст может масштабироваться с помощью текущей матрицы преобразования.

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

1 Ответ

2 голосов
/ 22 октября 2019

Ваш подход

Когда я передаю оператор Tf, размер которого больше моего порога, не добавляйте следующий видимый Tj / TJ.

слишком просто.

С одной стороны, как вы сами отметили,

текст может масштабироваться с помощью текущей матрицы преобразования.

(На самом деле не только по матрице преобразования, но и по текстовой матрице!)

Таким образом, вы должны отслеживать эти матрицы.

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

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

Чтобы редактировать поток контента относительно текущего состояния, следовательно, вам необходимо отслеживать большое количество информации. К счастью, PDFBox содержит классы для тяжелой работы здесь, иерархию классов, основанную на PDFStreamEngine, что позволяет вам сконцентрироваться на своей задаче. Чтобы иметь как можно больше информации, доступной для редактирования, класс PDFGraphicsStreamEngine, по-видимому, является хорошим выбором для построения.

Универсальный класс редактора потока контента

Таким образом, давайте получим PdfContentStreamEditor из PDFGraphicsStreamEngine и добавьте некоторый код для генерации замещающего потока контента.

public class PdfContentStreamEditor extends PDFGraphicsStreamEngine {
    public PdfContentStreamEditor(PDDocument document, PDPage page) {
        super(page);
        this.document = document;
    }

    /**
     * <p>
     * This method retrieves the next operation before its registered
     * listener is called. The default does nothing.
     * </p>
     * <p>
     * Override this method to retrieve state information from before the
     * operation execution.
     * </p> 
     */
    protected void nextOperation(Operator operator, List<COSBase> operands) {

    }

    /**
     * <p>
     * This method writes content stream operations to the target canvas. The default
     * implementation writes them as they come, so it essentially generates identical
     * copies of the original instructions {@link #processOperator(Operator, List)}
     * forwards to it.
     * </p>
     * <p>
     * Override this method to achieve some fancy editing effect.
     * </p> 
     */
    protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException {
        contentStreamWriter.writeTokens(operands);
        contentStreamWriter.writeToken(operator);
    }

    // stub implementation of PDFGraphicsStreamEngine abstract methods
    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException { }

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

    @Override
    public void clip(int windingRule) throws IOException { }

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

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

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

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

    @Override
    public void closePath() throws IOException { }

    @Override
    public void endPath() throws IOException { }

    @Override
    public void strokePath() throws IOException { }

    @Override
    public void fillPath(int windingRule) throws IOException { }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException { }

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

    // PDFStreamEngine overrides to allow editing
    @Override
    public void processPage(PDPage page) throws IOException {
        PDStream stream = new PDStream(document);
        replacement = new ContentStreamWriter(replacementStream = stream.createOutputStream(COSName.FLATE_DECODE));
        super.processPage(page);
        replacementStream.close();
        page.setContents(stream);
        replacement = null;
        replacementStream = null;
    }

    @Override
    public void showForm(PDFormXObject form) throws IOException {
        // DON'T descend into XObjects
        // super.showForm(form);
    }

    @Override
    protected void processOperator(Operator operator, List<COSBase> operands) throws IOException {
        nextOperation(operator, operands);
        super.processOperator(operator, operands);
        write(replacement, operator, operands);
    }

    final PDDocument document;
    OutputStream replacementStream = null;
    ContentStreamWriter replacement = null;
}

( PdfContentStreamEditor class)

Этот код переопределяетprocessPage для создания нового потока контента страницы и в конечном итоге замены на него старого. И он переопределяет processOperator, чтобы предоставить обработанную инструкцию для редактирования.

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

Применение редактора как есть,

PDDocument document = PDDocument.load(SOURCE);
for (PDPage page : document.getDocumentCatalog().getPages()) {
    PdfContentStreamEditor identity = new PdfContentStreamEditor(document, page);
    identity.processPage(page);
}
document.save(RESULT);

( EditPageContent test testIdentityInput)

, следовательно, создаст PDF-файл результата с эквивалентными потоками содержимого.

Настройка редактора потока содержимого для вашего случая использования

Вы хотите

отфильтровать весь текст из PDF, размер которого превышает определенный размер шрифта.

Таким образом, мы должны проверить write является ли текущая инструкция текстовой инструкцией рисования, и если она есть, мы должны проверить текущий эффективный размер шрифта, то есть базовый размер шрифта, преобразованный текстовой матрицей и текущей матрицей преобразования. Если эффективный размер шрифта слишком велик, мы должны отбросить инструкцию.

Это можно сделать следующим образом:

PDDocument document = PDDocument.load(SOURCE);
for (PDPage page : document.getDocumentCatalog().getPages()) {
    PdfContentStreamEditor identity = new PdfContentStreamEditor(document, page) {
        @Override
        protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException {
            String operatorString = operator.getName();

            if (TEXT_SHOWING_OPERATORS.contains(operatorString))
            {
                float fs = getGraphicsState().getTextState().getFontSize();
                Matrix matrix = getTextMatrix().multiply(getGraphicsState().getCurrentTransformationMatrix());
                Point2D.Float transformedFsVector = matrix.transformPoint(0, fs);
                Point2D.Float transformedOrigin = matrix.transformPoint(0, 0);
                double transformedFs = transformedFsVector.distance(transformedOrigin);
                if (transformedFs > 100)
                    return;
            }

            super.write(contentStreamWriter, operator, operands);
        }

        final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
    };
    identity.processPage(page);
}
document.save(RESULT);

( EditPageContent testtestRemoveBigTextDocument)

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

Ограничения и замечания

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

Этот PdfContentStreamEditor по сути является портом PdfContentStreamEditorдля iText 5 (.Net / Java) от этот ответ и PdfCanvasEditor для iText 7 от этот ответ . Примеры использования этих классов редактора могут дать некоторые советы о том, как использовать этот PdfContentStreamEditor для PDFBox.

Подобный (но менее общий) подход использовался ранее в HelloSignManipulator в этом ответе .

...