使用 itextsharp 在 PDF 中绘制 SVG 弧形路径时出现问题

Problems drawing an SVG arc path in a PDF using itextsharp

我正在尝试使用 itextsharp v5 在 PDF 中绘制 SVG 路径。

我采用的方法大致是这样的:

  1. 正在从 SVG 文件中读取 SVG 路径(Svg.SvgPath)
  2. 从路径中获取段列表 ({Svg.Pathing.SvgPathSegmentList})
  3. 创建 iTextSharp PdfAnnotation 并将 PdfAppearance 关联到它
  4. 使用相应的 PdfContentByte 方法绘制 SvgPathSegmentList 中的每个线段(对于 SvgLineSegment,我使用 PdfContentByte.LineTo,对于 SvgCubicCurveSegment,我使用 PdfContentByte.CurveTo)

对于大多数 SvgPathSegments 类型,SvgPathSegments 中的值与 PdfContentByte 方法中的参数之间存在明确的映射。几个例子:

我遇到的问题是 ARC(SVG 中的“A”命令,SvgArcSegment)

SvgArcSegment 具有以下值:

另一方面,PdfContentByte.Arc 方法 expect:

根据 itextsharp 文档,Arc 在矩形 x1,y1,x2,y2 内绘制了一个部分椭圆,从 StartAngle 度开始(逆时针)并覆盖范围度。 IE。 startAng=0 和 extent=180 产生一个切入矩形的开口朝下的半圆。

我的问题是:如何将从 SVG A 命令创建的 SvgArcSegment 中的值“映射”到 PdfContentByte.Arc 方法期望的参数中。 我知道 Start 和 End 值确实是我想要的曲线的原点和目标,但不知道 RadiusX 和 RadiusY 是什么意思。

正如@RobertLongson 在他的评论中指出的那样,我需要的是将中心参数化转换为端点参数化。

我发布了我自己的 SVG 文档中记录的算法的 C# 实现,以防其他人需要它。

        public static SvgCenterParameters EndPointToCenterParametrization(Svg.Pathing.SvgArcSegment arc)
        {
            //// Conversion from endpoint to center parameterization as in SVG Implementation Notes:
            //// https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter

            var sinA = Math.Sin(arc.Angle);
            var cosA = Math.Cos(arc.Angle);

            //// Large arc flag
            var fA = arc.Size == Svg.Pathing.SvgArcSize.Large ? 1 : 0;

            //// Sweep flag
            var fS = arc.Sweep == Svg.Pathing.SvgArcSweep.Positive ? 1 : 0;

            var radiusX = arc.RadiusX;
            var radiusY = arc.RadiusY;
            var x1 = arc.Start.X;
            var y1 = arc.Start.Y;
            var x2 = arc.End.X;
            var y2 = arc.End.Y;

            /*
             *
             * Step 1: Compute (x1′, y1′)
             * 
             */

            //// Median between Start and End
            var midPointX = (x1 - x2) / 2;
            var midPointY = (y1 - y2) / 2;

            var x1p = (cosA * midPointX) + (sinA * midPointY);
            var y1p = (cosA * midPointY) - (sinA * midPointX);

            /*
             *
             * Step 2: Compute (cx′, cy′)
             * 
             */

            var rxry_2 = Math.Pow(radiusX, 2) * Math.Pow(radiusY, 2);
            var rxy1p_2 = Math.Pow(radiusX, 2) * Math.Pow(y1p, 2);
            var ryx1p_2 = Math.Pow(radiusY, 2) * Math.Pow(x1p, 2);
 
            var sqrt = Math.Sqrt(Math.Abs(rxry_2 - rxy1p_2 - ryx1p_2) / (rxy1p_2 + ryx1p_2));
            if (fA == fS)
            {
                sqrt = -sqrt;
            }

            var cXP = sqrt * (radiusX * y1p / radiusY);
            var cYP = sqrt * -(radiusY * x1p / radiusX);

            /*
             *
             * Step 3: Compute (cx, cy) from (cx′, cy′)
             * 
             */

            var cX = (cosA * cXP) - (sinA * cYP) + ((x1 + x2) / 2);
            var cY = (sinA * cXP) + (cosA * cYP) + ((y1 + y2) / 2);

            /*
             *
             * Step 4: Compute θ1 and Δθ
             * 
             */

            var x1pcxp_rx = (float)(x1p - cXP) / radiusX;
            var y1pcyp_ry = (float)(y1p - cYP) / radiusY;
            Vector2 vector1 = new Vector2(1f, 0f);
            Vector2 vector2 = new Vector2(x1pcxp_rx, y1pcyp_ry);

            var angle = Math.Acos(((vector1.x * vector2.x) + (vector1.y * vector2.y)) / (Math.Sqrt((vector1.x * vector1.x) + (vector1.y * vector1.y)) * Math.Sqrt((vector2.x * vector2.x) + (vector2.y * vector2.y)))) * (180 / Math.PI);

            if (((vector1.x * vector2.y) - (vector1.y * vector2.x)) < 0)
            {
                angle = angle * -1;
            }

            var vector3 = new Vector2(x1pcxp_rx, y1pcyp_ry);
            var vector4 = new Vector2((float)(-x1p - cXP) / radiusX, (float)(-y1p - cYP) / radiusY);

            var extent = (Math.Acos(((vector3.x * vector4.x) + (vector3.y * vector4.y)) / Math.Sqrt((vector3.x * vector3.x) + (vector3.y * vector3.y)) * Math.Sqrt((vector4.x * vector4.x) + (vector4.y * vector4.y))) * (180 / Math.PI)) % 360;

            if (((vector3.x * vector4.y) - (vector3.y * vector4.x)) < 0)
            {
                extent = extent * -1;
            }

            if (fS == 1 && extent < 0)
            {
                extent = extent + 360;
            }

            if (fS == 0 && extent > 0)
            {
                extent = extent - 360;
            }

            var rectLL_X = cX - radiusX;
            var rectLL_Y = cY - radiusY;

            var rectUR_X = cX + radiusX;
            var rectUR_Y = cY + radiusY;

            return new SvgCenterParameters
            {
                LlX = (float)rectLL_X,
                LlY = (float)rectLL_Y,
                UrX = (float)rectUR_X,
                UrY = (float)rectUR_Y,
                Angle = (float)angle,
                Extent = (float)extent
            };
        }