You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache PDFBox计算PDF洋红色路径总长度出错求助

问题根源与修正方案

1. 单位转换缺失

PDF内部坐标默认使用点(Point),1点 = 1/72英寸 ≈ 0.352778毫米(或1毫米≈2.83465点)。当前计算结果是点的总长度,未转换为毫米,这是数值偏大的核心原因。

2. 贝塞尔曲线长度计算公式错误

原bezierLength方法的三次贝塞尔导数公式完全错误,导致曲线长度计算偏差严重。

3. 颜色判断的精度问题

PDF浮点数值存在精度误差,直接用==判断CMYK值会漏掉接近洋红的路径(比如m=0.9999999而非1.0的情况)。

4. 路径闭合操作的颜色判断缺失

closePath()(对应PDF的h操作)未判断inMagentaPath,会将非洋红路径的闭合长度计入总长度。


修正后的代码

import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.PDImage;
import org.apache.pdfbox.pdmodel.graphics.stream.PDFGraphicsStreamEngine;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;

public class PdfCalculatorUtil extends PDFGraphicsStreamEngine {
    private double totalLength = 0;
    private Point2D.Double currentPoint = new Point2D.Double();
    private Point2D.Double startPoint = new Point2D.Double();
    private boolean inMagentaPath = false;
    // 单位转换系数:点转毫米(1点 = 1/72英寸,1英寸=25.4毫米)
    private static final double POINT_TO_MM = 25.4 / 72;

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

    @Override
    public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) {
    }

    @Override
    public void drawImage(PDImage pdImage) {
    }

    @Override
    public void clip(int windingRule) {
    }

    @Override
    public void moveTo(float x, float y) {
        currentPoint.setLocation(x, y);
        startPoint.setLocation(x, y);
    }

    @Override
    public void lineTo(float x, float y) {
        totalLength += currentPoint.distance(x, y);
        currentPoint.setLocation(x, y);
    }

    @Override
    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
        totalLength += calculateCubicBezierLength(currentPoint.x, currentPoint.y, x1, y1, x2, y2, x3, y3);
        currentPoint.setLocation(x3, y3);
    }

    @Override
    public Point2D getCurrentPoint() {
        return currentPoint;
    }

    @Override
    public void closePath() {
        if (inMagentaPath) {
            totalLength += currentPoint.distance(startPoint);
            currentPoint.setLocation(startPoint);
        }
    }

    @Override
    public void endPath() {
    }

    @Override
    public void strokePath() {
    }

    @Override
    public void fillPath(int windingRule) {
    }

    @Override
    public void fillAndStrokePath(int windingRule) {
    }

    @Override
    public void shadingFill(COSName shadingName) {
    }

    public double getTotalLengthInMm() {
        return totalLength * POINT_TO_MM;
    }

    public void extractPathLengths(PDDocument document) throws IOException {
        for (PDPage page : document.getPages()) {
            processPage(page);
        }
    }

    @Override
    public void processOperator(Operator operator, List<COSBase> operands) {
        String operation = operator.getName();
        if ("m".equals(operation)) {
            if (inMagentaPath) {
                float x = ((COSNumber) operands.get(0)).floatValue();
                float y = ((COSNumber) operands.get(1)).floatValue();
                moveTo(x, y);
            }
        } else if ("l".equals(operation)) {
            if (inMagentaPath) {
                float x = ((COSNumber) operands.get(0)).floatValue();
                float y = ((COSNumber) operands.get(1)).floatValue();
                lineTo(x, y);
            }
        } else if ("h".equals(operation)) {
            closePath();
        } else if ("c".equals(operation)) {
            if (inMagentaPath) {
                float x1 = ((COSNumber) operands.get(0)).floatValue();
                float y1 = ((COSNumber) operands.get(1)).floatValue();
                float x2 = ((COSNumber) operands.get(2)).floatValue();
                float y2 = ((COSNumber) operands.get(3)).floatValue();
                float x3 = ((COSNumber) operands.get(4)).floatValue();
                float y3 = ((COSNumber) operands.get(5)).floatValue();
                curveTo(x1, y1, x2, y2, x3, y3);
            }
        } else if ("K".equals(operation)) {
            // 处理归一化CMYK(0-1范围)
            float c = ((COSNumber) operands.get(0)).floatValue();
            float m = ((COSNumber) operands.get(1)).floatValue();
            float y = ((COSNumber) operands.get(2)).floatValue();
            float k = ((COSNumber) operands.get(3)).floatValue();
            inMagentaPath = Math.abs(c) < 1e-6 && Math.abs(m - 1.0) < 1e-6 && Math.abs(y) < 1e-6 && Math.abs(k) < 1e-6;
        } else if ("k".equals(operation)) {
            // 处理非归一化CMYK(0-255范围),转换为0-1
            float c = ((COSNumber) operands.get(0)).floatValue() / 255;
            float m = ((COSNumber) operands.get(1)).floatValue() / 255;
            float y = ((COSNumber) operands.get(2)).floatValue() / 255;
            float k = ((COSNumber) operands.get(3)).floatValue() / 255;
            inMagentaPath = Math.abs(c) < 1e-6 && Math.abs(m - 1.0) < 1e-6 && Math.abs(y) < 1e-6 && Math.abs(k) < 1e-6;
        }
        // 调用父类方法维持路径状态
        try {
            super.processOperator(operator, operands);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 数值积分法计算三次贝塞尔曲线长度
     */
    private double calculateCubicBezierLength(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) {
        int steps = 100;
        double length = 0;
        double prevX = x0;
        double prevY = y0;

        for (int i = 1; i <= steps; i++) {
            double t = (double) i / steps;
            double t2 = t * t;
            double t3 = t2 * t;
            double mt = 1 - t;
            double mt2 = mt * mt;
            double mt3 = mt2 * mt;

            // 计算t时刻的坐标点
            double x = mt3 * x0 + 3 * mt2 * t * x1 + 3 * mt * t2 * x2 + t3 * x3;
            double y = mt3 * y0 + 3 * mt2 * t * y1 + 3 * mt * t2 * y2 + t3 * y3;

            length += Math.hypot(x - prevX, y - prevY);
            prevX = x;
            prevY = y;
        }
        return length;
    }
}

关键修正说明

  • 单位转换:新增POINT_TO_MM系数,将点单位转换为毫米,通过getTotalLengthInMm()返回最终结果。
  • 贝塞尔曲线计算:重写calculateCubicBezierLength方法,使用标准三次贝塞尔参数方程做数值积分,确保长度计算准确。
  • 颜色判断优化:用误差范围判断代替直接相等,兼容PDF浮点精度;同时处理K和k两种CMYK颜色操作符。
  • 路径闭合判断:在closePath()中加入inMagentaPath判断,避免非洋红路径的闭合长度被计入。
  • 父类方法调用:在processOperator末尾调用父类方法,维持PDFBox的路径状态正确性。

内容的提问来源于stack exchange,提问作者Anorak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:14:55