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
相关产品推荐
相关产品推荐

