当前位置: 首页 > 面试题库 >

从PDF过滤出超过特定字体大小的所有文本

齐航
2023-03-14
问题内容

如标题所示,我想从PDF中过滤出超过特定字体大小的所有文本。当前,我正在使用PDFBox库,但可以使用其他任何Java免费库。

我的方法是使用PDFStreamParser遍历令牌。当我通过大小大于阈值的Tf运算符时,不要添加下一个看到的Tj /
TJ。但是,对我来说很清楚,这种相对简单的方法将不起作用,因为文本可能会被当前的转换矩阵缩放。

有没有我可能会采用的更好的方法,或者使我的方法行得通而不复杂的方法?


问题答案:

你的方法

当我通过大小大于阈值的Tf运算符时,不要添加下一个看到的Tj / TJ。

太简单了。

一方面,当您评论自己时,

文本可以通过当前变换矩阵缩放。

(实际上不仅是通过转换矩阵,而且还通过文本矩阵!)

因此,您必须跟踪这些矩阵。

另一方面, Tf 不仅设置了所 看到下一个文本绘图指令 的基本字体大小,还设置了字体大小,直到该字体大小被其他指令明确更改为止。

此外,文本字体大小和当前的转换矩阵是图形状态的一部分。因此,它们受保存状态和恢复状态指令的约束。

因此,要针对当前状态编辑内容流,您必须跟踪许多信息。幸运的是,PDFBox包含的类可以在此进行繁重的工作,基于的类层次结构PDFStreamEngine使您可以专注于自己的任务。为了使尽可能多的信息可用于编辑,PDFGraphicsStreamEngine该类似乎是一个不错的选择。

通用内容流编辑器类

因此,我们得出PdfContentStreamEditorPDFGraphicsStreamEngine和添加一些代码,用于生成替代内容流。

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类)

此代码将覆盖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测试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测试testRemoveBigTextDocument

严格说来,完全放弃所讨论的指令可能是不够的;取而代之的是,必须像替换放置的文本绘制指令那样,用替换文本矩阵的指令替换它。否则,以下未删除的文本可能会被移动。通常,这确实可以正常工作,因为为以下不同文本重新设置了文本矩阵。因此,让我们在这里保持简单。

约束和说明

PdfContentStreamEditor仅编辑页面内容流。从那里开始,可以使用XObjects和Patterns,它们当前未被编辑器编辑。但是,在编辑页面内容流之后,应该很容易地递归地迭代XObjects和Patterns并以类似的方式对其进行编辑。

PdfContentStreamEditor本质上是的一个端口PdfContentStreamEditor为iText的5(.NET
/爪哇)从这个答案和PdfCanvasEditor用于iText的7从这个答案。使用这些编辑器类的示例可能会提示如何将其PdfContentStreamEditor用于PDFBox。

之前在HelloSignManipulator类的此答案中使用了类似(但不太通用)的方法。

修正错误

在此问题的上下文中,PdfContentStreamEditor发现了一个错误,该错误导致该示例PDF中的某些文本行被移动了。

背景:一些PDF指令是通过其他指令定义的,例如 t x t y TD 被指定为具有与 -t y TL t x t
y Td 相同的效果。OperatorProcessor为了简化工作,相应的PDFBox 实现通过将等效指令反馈回流引擎来工作。

PdfContentStreamEditor在这种情况下,如上实现的检索替换指令和原始指令的信号,并将它们全部写回到结果流中。因此,这些指令的效果加倍。例如,使用
TD 指令时,文本插入点将前移两行,而不是前一行。

因此,我们必须忽略更换指示。为此,将processOperator上面的方法替换为

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

boolean inOperator = false;


 类似资料:
  • 正如标题所说,我想过滤掉PDF中超过一定字体大小的所有文本。目前,我正在使用PDFBox库,但我愿意使用任何其他Java免费库。 我的方法是使用PDFStreamParser来迭代令牌。当我通过一个大小大于我的阈值的Tf操作符时,不要添加看到的下一个Tj/Tj。然而,我已经很清楚,这种相对简单的方法不会奏效,因为文本可能会被当前的转换矩阵缩放。 有没有更好的方法可以让我采用,或者让我的方法不变得太

  • 问题内容: 在Java中,由于空间不足而无法显示JLabel的文本时,文本将被截断并在末尾添加“ …”。 如何轻松找出当前JLabel是显示全文还是被截断? 编辑: 我看到有一种方法可以使用来找出文本的大小。但是,此解决方案无法完全回答问题。如果JLabel的文本包含HTML装饰,则还将计算HTML标签的宽度。因此,可能发生的结果是比JLabel的宽度更大,但仍然可以正确显示文本。 有没有办法知道

  • 问题内容: 我知道我可以按文件类型进行过滤,但是可以按文件大小进行过滤吗? 例如,一个JFileChooser仅显示3 MB内的图片。 问题答案: 简短的答案应该是,您尝试了什么?长答案是肯定的… 从技术上讲,您可以过滤以下任何属性或属性组合

  • 问题内容: 我该如何过滤具有一个数组的字段并且具有超过N个元素的文档? 如何过滤具有为空数组的字段的文档? 是解决方案吗?如果是这样,怎么办? 问题答案: 我来看看脚本过滤器。以下过滤器应仅返回在字段中具有至少10个元素的文档,该文档是一个数组。请记住,这可能很昂贵,具体取决于索引中有多少个文档。 关于第二个问题:那里真的有一个空数组吗?还是仅仅是一个没有值的数组字段?您可以使用缺少的过滤器来获取

  • 我的表: 插入查询: 我的页面大小是16KB。因此,我的表中的一行最多可以包含8192字节(即8KB)。 我创建了11个列(每个255个字符),其中这11列最多可以容纳字符。 如果我存储2805-3字节的字符,它将需要(

  • 我尝试过滤包含值的所有“描述”键的JSON文件;自版本”来打印它们的路径和包含在该值中的版本。我在bash脚本中完成所有这些。 我过滤的JSON来自一个API,在不同的路径上有“description”键。 这是服务器JSON的摘录 到目前为止,我使用curl从服务器获取JSON,并应用以下过滤器将其传输到jq,以获得包含描述中的值的所有路径的列表: