#svg #itext #bezier
#svg #itext #безье
Вопрос:
Я пытаюсь нарисовать путь SVG в PDF с помощью itextsharp v5.
Подход, которому я следую, примерно таков:
- Чтение пути SVG из файла SVG (Svg.SVGPath)
- Получение списка сегментов из пути ({Svg.Pathing.SvgPathSegmentList})
- Создание обозначения iTextSharp PdfAnnotation и привязка к нему PDF Appearance
- Рисование каждого сегмента в 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
};
}