/// <summary> /// Initializes a new instance of the <see cref="NGraphics.Custom.Models.Pen"/> class. /// </summary> /// <param name="color">Color.</param> /// <param name="width">Width.</param> public Pen(Color color, double width = 1.0) { Color = color; Width = width; LineJoin = SvgStrokeLineJoin.Miter; LineCap = SvgStrokeLineCap.Square; }
/// <summary> /// Compiles the stroke to the necessary triangles to draw it. /// </summary> /// <param name="path">The path to be compiled.</param> /// <param name="width">The width of the stroke.</param> /// <param name="lineCap">The line cap method for the stroke's ends.</param> /// <param name="lineJoin">The line join method for the stroke's midpoints</param> /// <param name="miterLimit">The miter limit value.</param> public static CompiledDrawing CompileStroke(SvgPathSegmentList path, double width, SvgStrokeLineCap lineCap = SvgStrokeLineCap.Butt, SvgStrokeLineJoin lineJoin = SvgStrokeLineJoin.Bevel, double miterLimit = double.PositiveInfinity) { // Return empty if stroke width == 0 if (width == 0) { return(CompiledDrawing.Empty); } // Divide the width by 2 to cope with the SVG documentation var halfWidth = width / 2; var curves = new List <Curve>(); // Convert each split path to a fill foreach (var data in path.SplitCurves()) { curves.AddRange(StrokeUtils.ConvertToFill(data, halfWidth, lineCap, lineJoin, miterLimit)); } // And compile return(CompileCurves(curves, SvgFillRule.NonZero)); }
/// <summary> /// Initializes a new instance of the <see cref="NGraphics.Custom.Models.Pen"/> class. /// </summary> public Pen() { Color = Colors.Black; Width = 1; LineJoin = SvgStrokeLineJoin.Miter; LineCap = SvgStrokeLineCap.Square; }
internal static XPen Stroke2XPen(SvgPaintServer stroke, SvgUnit strokeWidth, SvgStrokeLineCap strokeLineCap, SvgStrokeLineJoin strokeLineJoin) { var pen = stroke is SvgColourServer stroke1 ? new XPen(Color2XColor(stroke1), strokeWidth.Value) : new XPen(XColors.Black, strokeWidth.Value); switch (strokeLineCap) { case SvgStrokeLineCap.Inherit: pen.LineCap = XLineCap.Flat; break; case SvgStrokeLineCap.Butt: break; case SvgStrokeLineCap.Round: pen.LineCap = XLineCap.Round; break; case SvgStrokeLineCap.Square: pen.LineCap = XLineCap.Square; break; default: throw new ArgumentOutOfRangeException(); } switch (strokeLineJoin) { case SvgStrokeLineJoin.Inherit: break; case SvgStrokeLineJoin.Miter: pen.LineJoin = XLineJoin.Miter; break; case SvgStrokeLineJoin.Round: pen.LineJoin = XLineJoin.Round; break; case SvgStrokeLineJoin.Bevel: pen.LineJoin = XLineJoin.Bevel; break; default: throw new ArgumentOutOfRangeException(nameof(strokeLineJoin), strokeLineJoin, null); } return(pen); }
public static EndCapStyle AsEndCapStyle(this SvgStrokeLineCap join) { switch (join) { case SvgStrokeLineCap.Butt: return(EndCapStyle.Butt); case SvgStrokeLineCap.Round: return(EndCapStyle.Round); case SvgStrokeLineCap.Square: return(EndCapStyle.Square); case SvgStrokeLineCap.Inherit: default: return(EndCapStyle.Butt); } }
public static LineCap ConvertToDynamicPdf(this SvgStrokeLineCap strokeLineCap) { switch (strokeLineCap) { case SvgStrokeLineCap.Butt: return(LineCap.Butt); case SvgStrokeLineCap.Round: return(LineCap.Round); case SvgStrokeLineCap.Square: return(LineCap.ProjectedSquare); case SvgStrokeLineCap.Inherit: throw new Exception("Inherited value can not be resolved"); default: throw new ArgumentOutOfRangeException(nameof(strokeLineCap), strokeLineCap, null); } }
private static IEnumerable <Curve> GenerateLineCaps(Curve curve, bool atEnd, double halfWidth, SvgStrokeLineCap lineCap) { // First, get the offset of the curve var tangent = atEnd ? curve.ExitTangent : curve.EntryTangent; var p = curve.At(atEnd ? 1 : 0); // Now, get the offset, properly accounting for the direction var offset = halfWidth * tangent.CCWPerpendicular; // Finally, generate the line cap switch (lineCap) { case SvgStrokeLineCap.Butt: yield return(Curve.Line(p + offset, p - offset)); break; case SvgStrokeLineCap.Round: yield return(Curve.Circle(p, halfWidth, offset, -offset, !atEnd)); break; case SvgStrokeLineCap.Square: { // Generate a half-square var ext = offset.CCWPerpendicular; if (atEnd) { ext = -ext; } yield return(Curve.Line(p + offset, p + offset + ext)); yield return(Curve.Line(p + offset + ext, p - offset + ext)); yield return(Curve.Line(p - offset + ext, p - offset)); } break; default: throw new ArgumentException("Unrecognized lineCap", nameof(lineCap)); } }
public static IEnumerable <Curve> ConvertToFill(CurveData data, double halfWidth, SvgStrokeLineCap lineCap, SvgStrokeLineJoin lineJoin, double miterLimit) { // We are going to generate a "stroke fill" var leftCurves = new List <Curve>(); var rightCurves = new List <Curve>(); // Offset each curve for (int i = 0; i < data.Curves.Length; i++) { leftCurves.AddRange(OffsetCurve(data.Curves[i], halfWidth)); rightCurves.AddRange(OffsetCurve(data.Curves[i], -halfWidth)); // Detect where the line join should be added if (data.IsClosed || i < data.Curves.Length - 1) { var ik = (i + 1) % data.Curves.Length; var prevTangent = data.Curves[i].ExitTangent; var nextTangent = data.Curves[ik].EntryTangent; // If the angles are roughly equal, no line join if (RoughlyEquals(prevTangent, nextTangent)) { continue; } else { // Else, add to the appropriate list var lineJoint = GenerateLineJoins(data.Curves[i], data.Curves[ik], halfWidth, lineJoin, miterLimit); (prevTangent.Cross(nextTangent) > 0 ? rightCurves : leftCurves).AddRange(lineJoint); } } } // If the curve is open, add the line caps if (!data.IsClosed) { rightCurves.InsertRange(0, GenerateLineCaps(data.Curves[0], false, halfWidth, lineCap)); leftCurves.AddRange(GenerateLineCaps(data.Curves[data.Curves.Length - 1], true, halfWidth, lineCap)); } // Reverse the inner list and each curve var reverseRightCurves = rightCurves.Select(c => c.Reverse).Reverse(); // Generate the compiled fill return(leftCurves.Concat(reverseRightCurves).SelectMany(c => c.Simplify(true))); }
private Tuple <string, ICssValue> ParseValue(string name, string value, string priority, bool presentation) { var important = priority == "important"; if (!presentation) { name = name.ToLower(); } ICssValue parsedValue = null; switch (name) { case "fill": case "stroke": parsedValue = new SvgPaint(value); break; case "stroke-width": parsedValue = SvgLength.Parse(value, presentation); if (((SvgLength)parsedValue).Value < 0.0F) { return(null); } break; case "stroke-linecap": parsedValue = new SvgStrokeLineCap(value); break; case "stroke-linejoin": parsedValue = new SvgStrokeLineJoin(value); break; case "stroke-miterlimit": parsedValue = SvgNumber.Parse(value); break; case "color": parsedValue = new SvgColor(value); break; case "stop-color": parsedValue = new SvgStopColor(value); break; case "fill-opacity": case "stroke-opacity": case "stop-opacity": case "opacity": parsedValue = SvgNumber.Parse(value, 0.0F, 1.0F); break; case "clip-path": parsedValue = new SvgIri(value); break; case "fill-rule": case "clip-rule": parsedValue = new SvgFillRule(presentation ? value : value.ToLower()); break; } if (!this._cache.ContainsKey(name)) { var result = Tuple.Create(value, parsedValue); this._cache.Add(name, result); return(result); } else if (important) { var result = Tuple.Create(value, parsedValue); this._cache[name] = result; return(result); } return(null); }