使用 itextsharp 在 PDF 中绘制 SVG 弧形路径时出现问题
Problems drawing an SVG arc path in a PDF using itextsharp
我正在尝试使用 itextsharp v5 在 PDF 中绘制 SVG 路径。
我采用的方法大致是这样的:
- 正在从 SVG 文件中读取 SVG 路径(Svg.SvgPath)
- 从路径中获取段列表 ({Svg.Pathing.SvgPathSegmentList})
- 创建 iTextSharp PdfAnnotation 并将 PdfAppearance 关联到它
- 使用相应的 PdfContentByte 方法绘制 SvgPathSegmentList 中的每个线段(对于 SvgLineSegment,我使用 PdfContentByte.LineTo,对于 SvgCubicCurveSegment,我使用 PdfContentByte.CurveTo)
对于大多数 SvgPathSegments 类型,SvgPathSegments 中的值与 PdfContentByte 方法中的参数之间存在明确的映射。几个例子:
SvgMoveToSegment 的属性 End 是目标点 (X, Y),PdfContentByte.MoveTo 有两个参数:X, Y
SvgLineSegment,与移动非常相似。它有 Target End 和 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(SVG 中的“A”命令,SvgArcSegment)
SvgArcSegment 具有以下值:
- 角度
- 开始 (X, Y)
- 结束(X,Y)
- RadiusX
- 半径 Y
- 开始
- 扫一扫
另一方面,PdfContentByte.Arc 方法 expect:
- X1, X2, Y1, Y2
- 起始角度,
- 范围
根据 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
};
}
我正在尝试使用 itextsharp v5 在 PDF 中绘制 SVG 路径。
我采用的方法大致是这样的:
- 正在从 SVG 文件中读取 SVG 路径(Svg.SvgPath)
- 从路径中获取段列表 ({Svg.Pathing.SvgPathSegmentList})
- 创建 iTextSharp PdfAnnotation 并将 PdfAppearance 关联到它
- 使用相应的 PdfContentByte 方法绘制 SvgPathSegmentList 中的每个线段(对于 SvgLineSegment,我使用 PdfContentByte.LineTo,对于 SvgCubicCurveSegment,我使用 PdfContentByte.CurveTo)
对于大多数 SvgPathSegments 类型,SvgPathSegments 中的值与 PdfContentByte 方法中的参数之间存在明确的映射。几个例子:
SvgMoveToSegment 的属性 End 是目标点 (X, Y),PdfContentByte.MoveTo 有两个参数:X, Y
SvgLineSegment,与移动非常相似。它有 Target End 和 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(SVG 中的“A”命令,SvgArcSegment)
SvgArcSegment 具有以下值:
- 角度
- 开始 (X, Y)
- 结束(X,Y)
- RadiusX
- 半径 Y
- 开始
- 扫一扫
另一方面,PdfContentByte.Arc 方法 expect:
- X1, X2, Y1, Y2
- 起始角度,
- 范围
根据 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
};
}