PDFBox中如何设置多边形描边为内部/外部模式?
在PDFBox 2.0.25中实现多边形的内部/外部描边
PDFBox的fillAndStroke()方法默认采用居中描边(描边宽度一半在路径内,一半在路径外),没有提供像JavaFXStrokeType那样直接指定描边位置的API。要实现内部或外部描边,需要通过路径偏移手动处理,核心思路是生成原路径向内/向外偏移后的路径,再分别执行填充和描边操作。
解决方案原理
- 内部描边:先填充原多边形路径,再对原路径向内偏移
描边宽度/2的距离,描边这个偏移后的路径,确保描边完全落在原路径内部。 - 外部描边:先对原路径向外偏移
描边宽度/2的距离并描边,再填充原路径,覆盖内部多余的描边部分,确保描边完全落在原路径外部。
代码实现
工具类:路径偏移与描边绘制
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.graphics.color.PDColor; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.graphics.PDPath; import java.awt.geom.Point2D; import java.io.IOException; public class StrokeUtils { // 生成偏移后的路径:offset为正向外偏移,负向内偏移(仅支持凸多边形) public static PDPath createOffsetPath(PDPath originalPath, float offset) throws IOException { java.util.List<Point2D.Float> points = new java.util.ArrayList<>(); originalPath.visit(pathSegment -> { float[] coords = pathSegment.getCoordinates(); if (pathSegment.getSegmentType() == PDPath.PathSegment.MOVETO || pathSegment.getSegmentType() == PDPath.PathSegment.LINETO) { points.add(new Point2D.Float(coords[0], coords[1])); } return true; }); if (points.isEmpty()) return originalPath; java.util.List<Point2D.Float> offsetPoints = new java.util.ArrayList<>(); int n = points.size(); for (int i = 0; i < n; i++) { Point2D.Float p0 = points.get(i); Point2D.Float p1 = points.get((i+1)%n); Point2D.Float p2 = points.get((i+2)%n); // 计算边的法向量(顺时针顶点的左侧法向量为向内方向) Point2D.Float v1 = new Point2D.Float(p1.x - p0.x, p1.y - p0.y); float len1 = (float) Math.hypot(v1.x, v1.y); Point2D.Float n1 = new Point2D.Float(-v1.y / len1, v1.x / len1); Point2D.Float v2 = new Point2D.Float(p2.x - p1.x, p2.y - p1.y); float len2 = (float) Math.hypot(v2.x, v2.y); Point2D.Float n2 = new Point2D.Float(-v2.y / len2, v2.x / len2); // 平均法向量,计算偏移后的顶点 Point2D.Float avgN = new Point2D.Float(n1.x + n2.x, n1.y + n2.y); float avgLen = (float) Math.hypot(avgN.x, avgN.y); avgN.x /= avgLen; avgN.y /= avgLen; offsetPoints.add(new Point2D.Float(p1.x + offset * avgN.x, p1.y + offset * avgN.y)); } PDPath offsetPath = new PDPath(); if (!offsetPoints.isEmpty()) { offsetPath.moveTo(offsetPoints.get(0).x, offsetPoints.get(0).y); for (int i = 1; i < offsetPoints.size(); i++) { offsetPath.lineTo(offsetPoints.get(i).x, offsetPoints.get(i).y); } offsetPath.closePath(); } return offsetPath; } // 绘制带内部描边的多边形 public static void drawInsideStrokePolygon(PDPageContentStream contentStream, float[] points, float fillR, float fillG, float fillB, float strokeR, float strokeG, float strokeB, float strokeWidth) throws IOException { // 创建原路径 PDPath originalPath = new PDPath(); originalPath.moveTo(points[0], points[1]); for (int i = 2; i < points.length; i += 2) { originalPath.lineTo(points[i], points[i+1]); } originalPath.closePath(); // 填充原路径 contentStream.setNonStrokingColor(new PDColor(new float[]{fillR, fillG, fillB}, PDDeviceRGB.INSTANCE)); contentStream.fill(originalPath); // 生成向内偏移的路径并描边 PDPath offsetPath = createOffsetPath(originalPath, -strokeWidth / 2); contentStream.setStrokingColor(new PDColor(new float[]{strokeR, strokeG, strokeB}, PDDeviceRGB.INSTANCE)); contentStream.setLineWidth(strokeWidth); contentStream.stroke(offsetPath); } // 绘制带外部描边的多边形 public static void drawOutsideStrokePolygon(PDPageContentStream contentStream, float[] points, float fillR, float fillG, float fillB, float strokeR, float strokeG, float strokeB, float strokeWidth) throws IOException { // 创建原路径 PDPath originalPath = new PDPath(); originalPath.moveTo(points[0], points[1]); for (int i = 2; i < points.length; i += 2) { originalPath.lineTo(points[i], points[i+1]); } originalPath.closePath(); // 生成向外偏移的路径并描边 PDPath offsetPath = createOffsetPath(originalPath, strokeWidth / 2); contentStream.setStrokingColor(new PDColor(new float[]{strokeR, strokeG, strokeB}, PDDeviceRGB.INSTANCE)); contentStream.setLineWidth(strokeWidth); contentStream.stroke(offsetPath); // 填充原路径,覆盖内部多余描边 contentStream.setNonStrokingColor(new PDColor(new float[]{fillR, fillG, fillB}, PDDeviceRGB.INSTANCE)); contentStream.fill(originalPath); } }
使用示例(对应JavaFX示例)
// 多边形顶点坐标,与JavaFX示例一致 float[] polygonPoints = {50.0f, 150.0f, 150.0f, 50.0f, 250.0f, 150.0f, 150.0f, 250.0f}; // 绘制内部描边的多边形(对应JavaFX的StrokeType.INSIDE) StrokeUtils.drawInsideStrokePolygon( contentStream, polygonPoints, 0.8f, 0.93f, 1.0f, // 填充色:浅蓝色 0.0f, 0.0f, 0.0f, // 描边色:黑色 3.0f // 描边宽度 );
注意事项
- 上述路径偏移方法仅针对凸多边形有效,如果需要处理凹多边形,建议借助Java2D的
Area类或第三方几何库实现更复杂的路径偏移逻辑。 - 偏移量必须设置为
描边宽度/2,才能保证描边完全落在原路径的内部或外部。
内容的提问来源于stack exchange,提问作者Guyard
相关产品推荐
相关产品推荐

