Проблемы с отображением контура дуги SVG в PDF с использованием itextsharp

#svg #itext #bezier

#svg #itext #безье

Вопрос:

Я пытаюсь нарисовать путь SVG в PDF с помощью itextsharp v5.

Подход, которому я следую, примерно таков:

  1. Чтение пути SVG из файла SVG (Svg.SVGPath)
  2. Получение списка сегментов из пути ({Svg.Pathing.SvgPathSegmentList})
  3. Создание обозначения iTextSharp PdfAnnotation и привязка к нему PDF Appearance
  4. Рисование каждого сегмента в SvgPathSegmentList с использованием соответствующего метода PdfContentByte (для SvgLineSegment я использую PdfContentByte.lineTo, для SvgCubicCurveSegment я использую PdfContentByte.Кривая )

Для большинства типов SvgPathSegments существует четкое сопоставление между значениями в SvgPathSegments и аргументами в методе PdfContentByte. Несколько примеров:

  • SvgMoveToSegment имеет атрибут End, который является целевой точкой (X, Y) и PdfContentByte.moveTo принимает два параметра: X, Y
  • SvgLineSegment, очень похожий на перемещение. У него есть целевой конец и PdfContentByte.lineTo принимает два параметра X и Y и рисует линию от текущей позиции до целевой точки.
      app.MoveTo(segment.Start.X, segment.Start.Y);
     
  • В SvgCubicCurveSegment есть все необходимое для создания кривой Безье (начальная точка, конечная точка, первая и вторая контрольные точки). Для этого я использую PdfContentByte.CurveTo и получите кривую в PDF, которая выглядит точно так же, как в редакторе SVG.
      var cubicCurve = (Svg.Pathing.SvgCubicCurveSegment)segment;
     app.CurveTo(                             
             cubicCurve.FirstControlPoint.X, cubicCurve.FirstControlPoint.Y, 
             cubicCurve.SecondControlPoint.X, cubicCurve.SecondControlPoint.Y,
             cubicCurve.End.X, cubicCurve.End.Y);
     

Проблема, с которой я столкнулся, связана с ARC (команда «A» в SVG, SvgArcSegment)

SvgArcSegment имеет следующие значения:

  • Угол
  • Start (X, Y)
  • Конец (X, Y)
  • RadiusX
  • Радиус
  • Начать
  • Развертка

С другой стороны, PdfContentByte.Метод Arc ожидает:

  • X1, X2, Y1, Y2
  • startAngle,
  • Степень

Согласно документации itextsharp, Arc рисует частичный эллипс, вписанный в прямоугольник x1, y1, x2, y2, начинающийся (против часовой стрелки) в градусах startAngle и охватывающий градусы extent. Т.Е. startAng = 0 и extent = 180 дают вписанный в прямоугольник полукруг открытой стороной вниз.

Мой вопрос: как «сопоставить» значения в SvgArcSegment, созданные из команды SVG A, с аргументами, которые PdfContentByte.Метод Arc ожидает. Я знаю, что начальное и конечное значения действительно являются источником и целью нужной мне кривой, но понятия не имею, что означают RadiusX и RadiusY.

Комментарии:

1. Возможно, так ?

Ответ №1:

Как указал @RobertLongson в своем комментарии, мне нужно было преобразовать параметризацию центра в конечную точку.

Я публикую свою собственную реализацию алгоритма на C #, описанную в документации SVG, на случай, если это понадобится кому-то еще.

         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 amp;amp; extent < 0)
            {
                extent = extent   360;
            }

            if (fS == 0 amp;amp; 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
            };
        }