使用PDFBox查找空白空间(矩形)以进行签名字段

发布于 2025-01-26 02:08:54 字数 327 浏览 3 评论 0原文

当您要使用PDFBox创建可见的签名时,需要创建一个Rectangle2D对象。

Rectangle2d humanRect = new Rectangle2d.float(100,200,150,50);

我想知道是否可以在文档中找到所有白色空间(矩形)(或从第一个/最后一页)一定尺寸(宽度x高)。 我想为我的签名形式选择其中一个职位。

我想在以下示例中使用它:

rectangle2d humanRect = new Rectangle2d.float(fundx,findy,width,height);>

When you want to create a visible signature using PDFBox you need to create a Rectangle2D object.

Rectangle2D humanRect = new Rectangle2D.Float(100, 200, 150, 50);

I would like to know if it is possible to find all the white spaces(rectangles) in the document(or from the first/last page) of a certain size (width x height).
I would like to choose one of these positions for my signature form.

I would like to use it as in the following example:

Rectangle2D humanRect = new Rectangle2D.Float(foundX, foundY, width, height);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

咆哮 2025-02-02 02:08:54

在对问题的评论中已经确认,您本质上正在寻找 freeSpacefinder nofollow noreferrer“>从 to pdfbox。这是此答案的重点:

如果要从PDFBox的页面的内容流指令中确定某些内容,通常会根据pdfstreamengine或其一个子类创建类。对于不关注文本提取的任何事物,通常是pdfgraphicsStreamEngine是选择的基类。

基于此,我们可以从本质上复制上述itext类的功能:

public class FreeSpaceFinder extends PDFGraphicsStreamEngine {
    //
    // constructors
    //
    public FreeSpaceFinder(PDPage page, float minWidth, float minHeight) {
        this(page, page.getCropBox().toGeneralPath().getBounds2D(), minWidth, minHeight);
    }

    public FreeSpaceFinder(PDPage page, Rectangle2D initialBox, float minWidth, float minHeight) {
        this(page, Collections.singleton(initialBox), minWidth, minHeight);
    }

    public FreeSpaceFinder(PDPage page, Collection<Rectangle2D> initialBoxes, float minWidth, float minHeight) {
        super(page);

        this.minWidth = minWidth;
        this.minHeight = minHeight;
        this.freeSpaces = initialBoxes;
    }

    //
    // Result
    //
    public Collection<Rectangle2D> getFreeSpaces() {
        return freeSpaces;
    }

    //
    // Text
    //
    @Override
    protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement)
            throws IOException {
        super.showGlyph(textRenderingMatrix, font, code, displacement);
        Shape shape = calculateGlyphBounds(textRenderingMatrix, font, code);
        if (shape != null) {
            Rectangle2D rect = shape.getBounds2D();
            remove(rect);
        }
    }

    /**
     * Copy of <code>org.apache.pdfbox.examples.util.DrawPrintTextLocations.calculateGlyphBounds(Matrix, PDFont, int)</code>.
     */
    private Shape calculateGlyphBounds(Matrix textRenderingMatrix, PDFont font, int code) throws IOException
    {
        GeneralPath path = null;
        AffineTransform at = textRenderingMatrix.createAffineTransform();
        at.concatenate(font.getFontMatrix().createAffineTransform());
        if (font instanceof PDType3Font)
        {
            // It is difficult to calculate the real individual glyph bounds for type 3 fonts
            // because these are not vector fonts, the content stream could contain almost anything
            // that is found in page content streams.
            PDType3Font t3Font = (PDType3Font) font;
            PDType3CharProc charProc = t3Font.getCharProc(code);
            if (charProc != null)
            {
                BoundingBox fontBBox = t3Font.getBoundingBox();
                PDRectangle glyphBBox = charProc.getGlyphBBox();
                if (glyphBBox != null)
                {
                    // PDFBOX-3850: glyph bbox could be larger than the font bbox
                    glyphBBox.setLowerLeftX(Math.max(fontBBox.getLowerLeftX(), glyphBBox.getLowerLeftX()));
                    glyphBBox.setLowerLeftY(Math.max(fontBBox.getLowerLeftY(), glyphBBox.getLowerLeftY()));
                    glyphBBox.setUpperRightX(Math.min(fontBBox.getUpperRightX(), glyphBBox.getUpperRightX()));
                    glyphBBox.setUpperRightY(Math.min(fontBBox.getUpperRightY(), glyphBBox.getUpperRightY()));
                    path = glyphBBox.toGeneralPath();
                }
            }
        }
        else if (font instanceof PDVectorFont)
        {
            PDVectorFont vectorFont = (PDVectorFont) font;
            path = vectorFont.getPath(code);

            if (font instanceof PDTrueTypeFont)
            {
                PDTrueTypeFont ttFont = (PDTrueTypeFont) font;
                int unitsPerEm = ttFont.getTrueTypeFont().getHeader().getUnitsPerEm();
                at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
            }
            if (font instanceof PDType0Font)
            {
                PDType0Font t0font = (PDType0Font) font;
                if (t0font.getDescendantFont() instanceof PDCIDFontType2)
                {
                    int unitsPerEm = ((PDCIDFontType2) t0font.getDescendantFont()).getTrueTypeFont().getHeader().getUnitsPerEm();
                    at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
                }
            }
        }
        else if (font instanceof PDSimpleFont)
        {
            PDSimpleFont simpleFont = (PDSimpleFont) font;

            // these two lines do not always work, e.g. for the TT fonts in file 032431.pdf
            // which is why PDVectorFont is tried first.
            String name = simpleFont.getEncoding().getName(code);
            path = simpleFont.getPath(name);
        }
        else
        {
            // shouldn't happen, please open issue in JIRA
            System.out.println("Unknown font class: " + font.getClass());
        }
        if (path == null)
        {
            return null;
        }
        return at.createTransformedShape(path.getBounds2D());
    }

    //
    // Bitmaps
    //
    @Override
    public void drawImage(PDImage pdImage) throws IOException {
        Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
        Rectangle2D unitSquare = new Rectangle2D.Float(0, 0, 1, 1);
        Path2D path = new Path2D.Float(unitSquare);
        path.transform(ctm.createAffineTransform());
        remove(path.getBounds2D());
    }

    //
    // Paths
    //
    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
        currentPath.moveTo(p0.getX(), p0.getY());
        currentPath.lineTo(p1.getX(), p1.getY());
        currentPath.lineTo(p2.getX(), p2.getY());
        currentPath.lineTo(p3.getX(), p3.getY());
        currentPath.closePath();
    }

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

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

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

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

    @Override
    public Point2D getCurrentPoint() throws IOException {
        // To prevent many warnings...
        return new Point2D.Float();
    }

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

    @Override
    public void endPath() throws IOException {
        currentPath = new Path2D.Float();
    }

    @Override
    public void strokePath() throws IOException {
        // Better only remove the bounding boxes of the constituting strokes
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

    @Override
    public void fillPath(int windingRule) throws IOException {
        // Better only remove the bounding boxes of the constituting subpaths
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException {
        // Better only remove the bounding boxes of the constituting subpaths
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

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

    //
    // helpers
    //
    void remove(Rectangle2D usedSpace)
    {
        final double minX = usedSpace.getMinX();
        final double maxX = usedSpace.getMaxX();
        final double minY = usedSpace.getMinY();
        final double maxY = usedSpace.getMaxY();

        final Collection<Rectangle2D> newFreeSpaces = new ArrayList<Rectangle2D>();

        for (Rectangle2D freeSpace: freeSpaces)
        {
            final Collection<Rectangle2D> newFragments = new ArrayList<Rectangle2D>();
            if (freeSpace.intersectsLine(minX, minY, maxX, minY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), freeSpace.getWidth(), minY-freeSpace.getMinY()));
            if (freeSpace.intersectsLine(minX, maxY, maxX, maxY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), maxY, freeSpace.getWidth(), freeSpace.getMaxY() - maxY));
            if (freeSpace.intersectsLine(minX, minY, minX, maxY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), minX - freeSpace.getMinX(), freeSpace.getHeight()));
            if (freeSpace.intersectsLine(maxX, minY, maxX, maxY))
                newFragments.add(new Rectangle2D.Double(maxX, freeSpace.getMinY(), freeSpace.getMaxX() - maxX, freeSpace.getHeight()));
            if (newFragments.isEmpty())
            {
                add(newFreeSpaces, freeSpace);
            }
            else
            {
                for (Rectangle2D fragment: newFragments)
                {
                    if (fragment.getHeight() >= minHeight && fragment.getWidth() >= minWidth)
                    {
                        add(newFreeSpaces, fragment);
                    }
                }
            }
        }

        freeSpaces = newFreeSpaces;
    }

    void add(Collection<Rectangle2D> rectangles, Rectangle2D addition)
    {
        final Collection<Rectangle2D> toRemove = new ArrayList<Rectangle2D>();
        boolean isContained = false;
        for (Rectangle2D rectangle: rectangles)
        {
            if (rectangle.contains(addition))
            {
                isContained = true;
                break;
            }
            if (addition.contains(rectangle))
                toRemove.add(rectangle);
        }
        rectangles.removeAll(toRemove);
        if (!isContained)
            rectangles.add(addition);
    }

    //
    // hidden members
    //
    Path2D currentPath = new Path2D.Float();
    Collection<Rectangle2D> freeSpaces = null;
    final float minWidth;
    final float minHeight;
}

freespacefinder

使用此freeSpaceFinder您可以在给定的最小尺寸中,这样的方法:

public Collection<Rectangle2D> find(PDDocument pdDocument, PDPage pdPage, float minWidth, float minHeight) throws IOException {
    FreeSpaceFinder finder = new FreeSpaceFinder(pdPage, minWidth, minHeight);
    finder.processPage(pdPage);
    return finder.getFreeSpaces();
}

确定freEspaces 方法查找

)我们得到:

“屏幕截图”

与Itext变体的类似屏幕截图进行比较,我们看到这里有更多可能的矩形。

这是由于使用字体级别的升华和下降时的ITEXT解决方案时,我们在这里使用各个雕文框框。

As already confirmed in a comment to the question, you essentially are looking for a port of the functionality of the FreeSpaceFinder and FreeSpaceFinderExt classes for iText from this answer to PDFBox. This is the focus of this answer:

If you want to determine something from the content stream instructions of a page with PDFBox, you usually will create a class based on PDFStreamEngine or one of its subclasses. For anything that's not focusing on text extraction most often the PDFGraphicsStreamEngine is the base class of choice.

Based on that we can essentially copy the functionality of the mentioned iText based classes:

public class FreeSpaceFinder extends PDFGraphicsStreamEngine {
    //
    // constructors
    //
    public FreeSpaceFinder(PDPage page, float minWidth, float minHeight) {
        this(page, page.getCropBox().toGeneralPath().getBounds2D(), minWidth, minHeight);
    }

    public FreeSpaceFinder(PDPage page, Rectangle2D initialBox, float minWidth, float minHeight) {
        this(page, Collections.singleton(initialBox), minWidth, minHeight);
    }

    public FreeSpaceFinder(PDPage page, Collection<Rectangle2D> initialBoxes, float minWidth, float minHeight) {
        super(page);

        this.minWidth = minWidth;
        this.minHeight = minHeight;
        this.freeSpaces = initialBoxes;
    }

    //
    // Result
    //
    public Collection<Rectangle2D> getFreeSpaces() {
        return freeSpaces;
    }

    //
    // Text
    //
    @Override
    protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement)
            throws IOException {
        super.showGlyph(textRenderingMatrix, font, code, displacement);
        Shape shape = calculateGlyphBounds(textRenderingMatrix, font, code);
        if (shape != null) {
            Rectangle2D rect = shape.getBounds2D();
            remove(rect);
        }
    }

    /**
     * Copy of <code>org.apache.pdfbox.examples.util.DrawPrintTextLocations.calculateGlyphBounds(Matrix, PDFont, int)</code>.
     */
    private Shape calculateGlyphBounds(Matrix textRenderingMatrix, PDFont font, int code) throws IOException
    {
        GeneralPath path = null;
        AffineTransform at = textRenderingMatrix.createAffineTransform();
        at.concatenate(font.getFontMatrix().createAffineTransform());
        if (font instanceof PDType3Font)
        {
            // It is difficult to calculate the real individual glyph bounds for type 3 fonts
            // because these are not vector fonts, the content stream could contain almost anything
            // that is found in page content streams.
            PDType3Font t3Font = (PDType3Font) font;
            PDType3CharProc charProc = t3Font.getCharProc(code);
            if (charProc != null)
            {
                BoundingBox fontBBox = t3Font.getBoundingBox();
                PDRectangle glyphBBox = charProc.getGlyphBBox();
                if (glyphBBox != null)
                {
                    // PDFBOX-3850: glyph bbox could be larger than the font bbox
                    glyphBBox.setLowerLeftX(Math.max(fontBBox.getLowerLeftX(), glyphBBox.getLowerLeftX()));
                    glyphBBox.setLowerLeftY(Math.max(fontBBox.getLowerLeftY(), glyphBBox.getLowerLeftY()));
                    glyphBBox.setUpperRightX(Math.min(fontBBox.getUpperRightX(), glyphBBox.getUpperRightX()));
                    glyphBBox.setUpperRightY(Math.min(fontBBox.getUpperRightY(), glyphBBox.getUpperRightY()));
                    path = glyphBBox.toGeneralPath();
                }
            }
        }
        else if (font instanceof PDVectorFont)
        {
            PDVectorFont vectorFont = (PDVectorFont) font;
            path = vectorFont.getPath(code);

            if (font instanceof PDTrueTypeFont)
            {
                PDTrueTypeFont ttFont = (PDTrueTypeFont) font;
                int unitsPerEm = ttFont.getTrueTypeFont().getHeader().getUnitsPerEm();
                at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
            }
            if (font instanceof PDType0Font)
            {
                PDType0Font t0font = (PDType0Font) font;
                if (t0font.getDescendantFont() instanceof PDCIDFontType2)
                {
                    int unitsPerEm = ((PDCIDFontType2) t0font.getDescendantFont()).getTrueTypeFont().getHeader().getUnitsPerEm();
                    at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
                }
            }
        }
        else if (font instanceof PDSimpleFont)
        {
            PDSimpleFont simpleFont = (PDSimpleFont) font;

            // these two lines do not always work, e.g. for the TT fonts in file 032431.pdf
            // which is why PDVectorFont is tried first.
            String name = simpleFont.getEncoding().getName(code);
            path = simpleFont.getPath(name);
        }
        else
        {
            // shouldn't happen, please open issue in JIRA
            System.out.println("Unknown font class: " + font.getClass());
        }
        if (path == null)
        {
            return null;
        }
        return at.createTransformedShape(path.getBounds2D());
    }

    //
    // Bitmaps
    //
    @Override
    public void drawImage(PDImage pdImage) throws IOException {
        Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
        Rectangle2D unitSquare = new Rectangle2D.Float(0, 0, 1, 1);
        Path2D path = new Path2D.Float(unitSquare);
        path.transform(ctm.createAffineTransform());
        remove(path.getBounds2D());
    }

    //
    // Paths
    //
    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
        currentPath.moveTo(p0.getX(), p0.getY());
        currentPath.lineTo(p1.getX(), p1.getY());
        currentPath.lineTo(p2.getX(), p2.getY());
        currentPath.lineTo(p3.getX(), p3.getY());
        currentPath.closePath();
    }

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

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

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

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

    @Override
    public Point2D getCurrentPoint() throws IOException {
        // To prevent many warnings...
        return new Point2D.Float();
    }

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

    @Override
    public void endPath() throws IOException {
        currentPath = new Path2D.Float();
    }

    @Override
    public void strokePath() throws IOException {
        // Better only remove the bounding boxes of the constituting strokes
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

    @Override
    public void fillPath(int windingRule) throws IOException {
        // Better only remove the bounding boxes of the constituting subpaths
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException {
        // Better only remove the bounding boxes of the constituting subpaths
        remove(currentPath.getBounds2D());
        currentPath = new Path2D.Float();
    }

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

    //
    // helpers
    //
    void remove(Rectangle2D usedSpace)
    {
        final double minX = usedSpace.getMinX();
        final double maxX = usedSpace.getMaxX();
        final double minY = usedSpace.getMinY();
        final double maxY = usedSpace.getMaxY();

        final Collection<Rectangle2D> newFreeSpaces = new ArrayList<Rectangle2D>();

        for (Rectangle2D freeSpace: freeSpaces)
        {
            final Collection<Rectangle2D> newFragments = new ArrayList<Rectangle2D>();
            if (freeSpace.intersectsLine(minX, minY, maxX, minY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), freeSpace.getWidth(), minY-freeSpace.getMinY()));
            if (freeSpace.intersectsLine(minX, maxY, maxX, maxY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), maxY, freeSpace.getWidth(), freeSpace.getMaxY() - maxY));
            if (freeSpace.intersectsLine(minX, minY, minX, maxY))
                newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), minX - freeSpace.getMinX(), freeSpace.getHeight()));
            if (freeSpace.intersectsLine(maxX, minY, maxX, maxY))
                newFragments.add(new Rectangle2D.Double(maxX, freeSpace.getMinY(), freeSpace.getMaxX() - maxX, freeSpace.getHeight()));
            if (newFragments.isEmpty())
            {
                add(newFreeSpaces, freeSpace);
            }
            else
            {
                for (Rectangle2D fragment: newFragments)
                {
                    if (fragment.getHeight() >= minHeight && fragment.getWidth() >= minWidth)
                    {
                        add(newFreeSpaces, fragment);
                    }
                }
            }
        }

        freeSpaces = newFreeSpaces;
    }

    void add(Collection<Rectangle2D> rectangles, Rectangle2D addition)
    {
        final Collection<Rectangle2D> toRemove = new ArrayList<Rectangle2D>();
        boolean isContained = false;
        for (Rectangle2D rectangle: rectangles)
        {
            if (rectangle.contains(addition))
            {
                isContained = true;
                break;
            }
            if (addition.contains(rectangle))
                toRemove.add(rectangle);
        }
        rectangles.removeAll(toRemove);
        if (!isContained)
            rectangles.add(addition);
    }

    //
    // hidden members
    //
    Path2D currentPath = new Path2D.Float();
    Collection<Rectangle2D> freeSpaces = null;
    final float minWidth;
    final float minHeight;
}

(FreeSpaceFinder)

Using this FreeSpaceFinder you can find empty areas with given minimum dimensions in a method like this:

public Collection<Rectangle2D> find(PDDocument pdDocument, PDPage pdPage, float minWidth, float minHeight) throws IOException {
    FreeSpaceFinder finder = new FreeSpaceFinder(pdPage, minWidth, minHeight);
    finder.processPage(pdPage);
    return finder.getFreeSpaces();
}

(DetermineFreeSpaces method find)

Applied to the same PDF page as was the iText centric solution with minimum width 200 and height 50, we get:

screen shot

Comparing to the analogous screen shot for the iText variant, we see that we get more possible rectangles here.

This is due to the iText solution using the font-level ascender and descender while we here use the individual glyph bounding boxes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文