【问题标题】:How to get page content height using pdfbox如何使用 pdfbox 获取页面内容高度
【发布时间】:2015-04-03 23:23:35
【问题描述】:

这是否可以使用 pdfbox 获取页面内容的高度? 我想我什么都试过了,但每个(PDRectangle)都返回页面的全高:842。 首先我认为这是因为页码位于页面底部,但是当我在 Illustrator 中打开 pdf 时,整个内容都在复合元素内,并且没有扩展到整个页面高度。所以如果illustrator可以把它看成单独的元素并计算它的高度,我想这在pdfbox中也应该是可能的。

示例页面:

【问题讨论】:

  • 如果文档是使用 illustrator 创建的... Illustrator 会在文档中保留自己的专有信息,从中它可能会显示一些复合元素。如果您分享有问题的 PDF,我们可能会判断是否有任何相应的 PDF 结构,或者这是否仅仅是插图主义。
  • PDF由app生成,与Illustrator无关,Illustrator只是用来检查pdf的。
  • 它可能是一个 xobject 或一个剪辑路径或类似的东西。如果您可以共享 PDF...
  • 这里是 PDF:d.pr/f/137PF 框中的文本可以有多行,因此大小不固定。这样可以得到这个header的位置和大小(白+灰框)吗?
  • 乍一看,那里只能看到一个剪辑路径。需要内容流分析才能找到这些。明天我会再调查一下,回到办公室。

标签: java pdf pdfbox


【解决方案1】:

一般

PDF 规范允许 PDF 提供多个页面边界,参见 this answer。除此之外,内容边界可能仅来自页面内容,例如来自

  • 表单 XObject:

    表单 XObject 是 PDF 内容流,它是任何图形对象序列(包括路径对象、文本对象和采样图像)的自包含描述。一个表单 XObject 可以被绘制多次(在多个页面上或在同一页面上的多个位置)并且每次都产生相同的结果,仅受调用时的图形状态的影响。

  • 剪切路径:

    图形状态应包含一个当前剪切路径,它限制了受绘画操作符影响的页面区域。该路径的封闭子路径应定义可以绘制的区域。落在该区域内的标记应应用于页面;那些落在它外面的人不会。

  • ...

要找到其中任何一个,必须解析页面内容,查找适当的操作,并计算结果边界。

在 OP 的情况下

每个示例 PDF 都明确定义了一个页面边界,即 MediaBox。因此,所有其他 PDF 页面边界(CropBoxBleedBoxTrimBoxArtBox)默认为它。所以难怪在你的尝试中

each (PDRectangle) 返回页面的全高:842

它们都不包含表单 XObject,但都使用剪切路径。

  • 如果是test-pdf4.pdf:

    Start at: 28.31999969482422, 813.6799926757812
    Line to: 565.9199829101562, 813.6799926757812
    Line to: 565.9199829101562, 660.2196655273438
    Line to: 28.31999969482422, 660.2196655273438
    Line to: 28.31999969482422, 813.6799926757812
    

    (这可能与您问题中的草图相匹配。)

  • 如果是test-pdf5.pdf:

    Start at: 23.0, 34.0
    Line to: 572.0, 34.0
    Line to: 572.0, -751.0
    Line to: 23.0, -751.0
    Line to: 23.0, 34.0
    

    Start at: 23.0, 819.0
    Line to: 572.0, 819.0
    Line to: 572.0, 34.0
    Line to: 23.0, 34.0
    Line to: 23.0, 819.0
    

由于与草图的匹配,我假设 Illustrator 会考虑在一个重要的剪切路径生效时绘制的所有内容,一个以剪切路径为边框的 复合元素

使用 PDFBox 查找剪切路径

我使用 PDFBox 来查找上面提到的剪切路径。我使用了目前正在开发的 2.0.0 版本的当前 SNAPSHOT,因为与当前版本 1.8.8 相比,所需的 API 已经有了很大改进。

我将PDFGraphicsStreamEngine 扩展为ClipPathFinder 类:

public class ClipPathFinder extends PDFGraphicsStreamEngine implements Iterable<Path>
{
    public ClipPathFinder(PDPage page)
    {
        super(page);
    }

    //
    // PDFGraphicsStreamEngine overrides
    //
    public void findClipPaths() throws IOException
    {
        processPage(getPage());
    }

    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
    {
        startPathIfNecessary();
        currentPath.appendRectangle(toFloat(p0), toFloat(p1), toFloat(p2), toFloat(p3));
    }

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

    @Override
    public void clip(int windingRule) throws IOException
    {
        currentPath.complete(windingRule);
        paths.add(currentPath);
        currentPath = null;
    }

    @Override
    public void moveTo(float x, float y) throws IOException
    {
        startPathIfNecessary();
        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.Float getCurrentPoint() throws IOException
    {
        return currentPath.getCurrentPoint();
    }

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

    @Override
    public void endPath() throws IOException
    {
        currentPath = null;
    }

    @Override
    public void strokePath() throws IOException
    {
        currentPath = null;
    }

    @Override
    public void fillPath(int windingRule) throws IOException
    {
        currentPath = null;
    }

    @Override
    public void fillAndStrokePath(int windingRule) throws IOException
    {
        currentPath = null;
    }

    @Override
    public void shadingFill(COSName shadingName) throws IOException
    {
        currentPath = null;
    }

    void startPathIfNecessary()
    {
        if (currentPath == null)
            currentPath = new Path();
    }

    Point2D.Float toFloat(Point2D p)
    {
        if (p == null || (p instanceof Point2D.Float))
        {
            return (Point2D.Float)p;
        }
        return new Point2D.Float((float)p.getX(), (float)p.getY());
    }

    //
    // Iterable<Path> implementation
    //
    public Iterator<Path> iterator()
    {
        return paths.iterator();
    }

    Path currentPath = null;
    final List<Path> paths = new ArrayList<Path>();
}

它使用这个帮助类来表示路径:

public class Path implements Iterable<Path.SubPath>
{
    public static class Segment
    {
        Segment(Point2D.Float start, Point2D.Float end)
        {
            this.start = start;
            this.end = end;
        }

        public Point2D.Float getStart()
        {
            return start;
        }

        public Point2D.Float getEnd()
        {
            return end;
        }

        final Point2D.Float start, end; 
    }

    public class SubPath implements Iterable<Segment>
    {
        public class Line extends Segment
        {
            Line(Point2D.Float start, Point2D.Float end)
            {
                super(start, end);
            }

            //
            // Object override
            //
            @Override
            public String toString()
            {
                StringBuilder builder = new StringBuilder();
                builder.append("    Line to: ")
                       .append(end.getX())
                       .append(", ")
                       .append(end.getY())
                       .append('\n');
                return builder.toString();
            }
        }

        public class Curve extends Segment
        {
            Curve(Point2D.Float start, Point2D.Float control1, Point2D.Float control2, Point2D.Float end)
            {
                super(start, end);
                this.control1 = control1;
                this.control2 = control2;
            }

            public Point2D getControl1()
            {
                return control1;
            }

            public Point2D getControl2()
            {
                return control2;
            }

            //
            // Object override
            //
            @Override
            public String toString()
            {
                StringBuilder builder = new StringBuilder();
                builder.append("    Curve to: ")
                       .append(end.getX())
                       .append(", ")
                       .append(end.getY())
                       .append(" with Control1: ")
                       .append(control1.getX())
                       .append(", ")
                       .append(control1.getY())
                       .append(" and Control2: ")
                       .append(control2.getX())
                       .append(", ")
                       .append(control2.getY())
                       .append('\n');
                return builder.toString();
            }

            final Point2D control1, control2; 
        }

        SubPath(Point2D.Float start)
        {
            this.start = start;
            currentPoint = start;
        }

        public Point2D getStart()
        {
            return start;
        }

        void lineTo(float x, float y)
        {
            Point2D.Float end = new Point2D.Float(x, y);
            segments.add(new Line(currentPoint, end));
            currentPoint = end;
        }

        void curveTo(float x1, float y1, float x2, float y2, float x3, float y3)
        {
            Point2D.Float control1 = new Point2D.Float(x1, y1);
            Point2D.Float control2 = new Point2D.Float(x2, y2);
            Point2D.Float end = new Point2D.Float(x3, y3);
            segments.add(new Curve(currentPoint, control1, control2, end));
            currentPoint = end;
        }

        void closePath()
        {
            closed = true;
            currentPoint = start;
        }

        //
        // Iterable<Segment> implementation
        //
        public Iterator<Segment> iterator()
        {
            return segments.iterator();
        }

        //
        // Object override
        //
        @Override
        public String toString()
        {
            StringBuilder builder = new StringBuilder();
            builder.append("  {\n    Start at: ")
                   .append(start.getX())
                   .append(", ")
                   .append(start.getY())
                   .append('\n');
            for (Segment segment : segments)
                builder.append(segment);
            if (closed)
                builder.append("    Closed\n");
            builder.append("  }\n");
            return builder.toString();
        }

        boolean closed = false;
        final Point2D.Float start;
        final List<Segment> segments = new ArrayList<Path.Segment>();
    }

    public class Rectangle extends SubPath
    {
        Rectangle(Point2D.Float p0, Point2D.Float p1, Point2D.Float p2, Point2D.Float p3)
        {
            super(p0);
            lineTo((float)p1.getX(), (float)p1.getY());
            lineTo((float)p2.getX(), (float)p2.getY());
            lineTo((float)p3.getX(), (float)p3.getY());
            closePath();
        }

        //
        // Object override
        //
        @Override
        public String toString()
        {
            StringBuilder builder = new StringBuilder();
            builder.append("  {\n    Rectangle\n    Start at: ")
                   .append(start.getX())
                   .append(", ")
                   .append(start.getY())
                   .append('\n');
            for (Segment segment : segments)
                builder.append(segment);
            if (closed)
                builder.append("    Closed\n");
            builder.append("  }\n");
            return builder.toString();
        }
    }

    public int getWindingRule()
    {
        return windingRule;
    }

    void complete(int windingRule)
    {
        finishSubPath();
        this.windingRule = windingRule;
    }

    void appendRectangle(Point2D.Float p0, Point2D.Float p1, Point2D.Float p2, Point2D.Float p3) throws IOException
    {
        finishSubPath();
        currentSubPath = new Rectangle(p0, p1, p2, p3);
        finishSubPath();
    }

    void moveTo(float x, float y) throws IOException
    {
        finishSubPath();
        currentSubPath = new SubPath(new Point2D.Float(x, y));
    }

    void lineTo(float x, float y) throws IOException
    {
        currentSubPath.lineTo(x, y);
    }

    void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
    {
        currentSubPath.curveTo(x1, y1, x2, y2, x3, y3);
    }

    Point2D.Float getCurrentPoint() throws IOException
    {
        return currentPoint;
    }

    void closePath() throws IOException
    {
        currentSubPath.closePath();
        finishSubPath();
    }

    void finishSubPath()
    {
        if (currentSubPath != null)
        {
            subPaths.add(currentSubPath);
            currentSubPath = null;
        }
    }

    //
    // Iterable<Path.SubPath> implementation
    //
    public Iterator<SubPath> iterator()
    {
        return subPaths.iterator();
    }

    //
    // Object override
    //
    @Override
    public String toString()
    {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n  Winding: ")
               .append(windingRule)
               .append('\n');
        for (SubPath subPath : subPaths)
            builder.append(subPath);
        builder.append("}\n");
        return builder.toString();
    }

    Point2D.Float currentPoint = null;
    SubPath currentSubPath = null;
    int windingRule = -1;
    final List<SubPath> subPaths = new ArrayList<Path.SubPath>();
}

ClipPathFinder 类是这样使用的:

PDDocument document = PDDocument.load(PDFRESOURCE, null);
PDPage page = document.getPage(PAGENUMBER);
ClipPathFinder finder = new ClipPathFinder(page);
finder.findClipPaths();

for (Path path : finder)
{
    System.out.println(path);
}

document.close();

【讨论】:

  • 这在 1.8.8 中可行吗?
  • PDFGraphicsStreamEngineClipPathFinder 派生自它)是 PDFBox 1.8.8 PageDrawer 基础的更通用的分支。也许可以调整PageDrawer class to serve the same purpose as PDFGraphicsStreamEngine. Else one has to do more copy&amp;paste and create one's own graphics stream engine based on the 1.8.8 PDFStreamEngine. To cut a long story short: It is possible but you have to find/create a replacement for the PDFGraphicsStreamEngine`基类。
  • 再看一下代码...如果我需要 1.8.8 中的功能,我想我会尝试向后移植 PDFGraphicsStreamEngine 及其所有 GraphicsOperatorProcessor 类。
  • 我想知道在phantomjs中设置artbox(或其他边界)是否更容易,然后可以检索...
  • grin,是的,使用这些结构确实最适合后处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-18
  • 2014-05-28
  • 1970-01-01
相关资源
最近更新 更多