Exemple #1
0
 /// <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;
 }
Exemple #2
0
        /// <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));
        }
Exemple #3
0
 /// <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;
 }
        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)));
        }
Exemple #5
0
        public static JointStyle AsJointStyle(this SvgStrokeLineJoin join)
        {
            switch (join)
            {
            case SvgStrokeLineJoin.Miter:
                return(JointStyle.Miter);

            case SvgStrokeLineJoin.Round:
                return(JointStyle.Round);

            case SvgStrokeLineJoin.Bevel:
                return(JointStyle.Square);

            case SvgStrokeLineJoin.Inherit:
            default:
                return(JointStyle.Miter);
            }
        }
Exemple #6
0
        public static LineJoin ConvertToDynamicPdf(this SvgStrokeLineJoin strokeLineJoin)
        {
            switch (strokeLineJoin)
            {
            case SvgStrokeLineJoin.Miter:
                return(LineJoin.Miter);

            case SvgStrokeLineJoin.Round:
                return(LineJoin.Round);

            case SvgStrokeLineJoin.Bevel:
                return(LineJoin.Bevel);

            case SvgStrokeLineJoin.Inherit:
                throw new Exception("Inherited value can not be resolved");

            default:
                throw new ArgumentOutOfRangeException(nameof(strokeLineJoin), strokeLineJoin, null);
            }
        }
Exemple #7
0
        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);
        }
        private static IEnumerable <Curve> GenerateLineJoins(Curve prevCurve, Curve nextCurve, double halfWidth,
                                                             SvgStrokeLineJoin lineJoin, double miterLimit)
        {
            // First, calculate the cross-product between the tangents
            var exitTangent  = prevCurve.ExitTangent;
            var entryTangent = nextCurve.EntryTangent;
            var diff         = entryTangent.Cross(exitTangent);
            var sd           = diff < 0 ? -1 : 1; // Account for half-turns here too

            // The common point and the offset vectors
            var p           = (prevCurve.At(1) + nextCurve.At(0)) / 2;
            var entryOffset = sd * halfWidth * entryTangent.CCWPerpendicular;
            var exitOffset  = sd * halfWidth * exitTangent.CCWPerpendicular;

            // Now, create the next triangles if necessary
            switch (lineJoin)
            {
            case SvgStrokeLineJoin.Bevel:     // Just the bevel line
                yield return(Curve.Line(p + exitOffset, p + entryOffset));

                break;

            case SvgStrokeLineJoin.Miter:
            case SvgStrokeLineJoin.MiterClip:
            {
                // Calculate the bisector and miter length
                var cos   = exitOffset.Dot(entryOffset) / Math.Sqrt(exitOffset.LengthSquared * entryOffset.LengthSquared);
                var miter = halfWidth / Math.Sqrt(0.5 + 0.5 * cos);

                // Check the conditions for the miter (only clip if miter-clip is explicity selected)
                if (lineJoin == SvgStrokeLineJoin.Miter && miter >= halfWidth * miterLimit)
                {
                    goto case SvgStrokeLineJoin.Bevel;
                }

                // Generate the miter
                var miterWidth     = halfWidth * miterLimit;
                var bisectorOffset = miter * (entryOffset + exitOffset).Normalized;
                if (miter < miterWidth)
                {
                    yield return(Curve.Line(p + exitOffset, p + bisectorOffset));

                    yield return(Curve.Line(p + bisectorOffset, p + entryOffset));
                }
                else
                {
                    // Clip the miter
                    Double2 p1, p2;

                    // Account for the special case of a 180 degree turn
                    if (double.IsInfinity(miter))
                    {
                        var outwardOffset = entryOffset.Normalized.CCWPerpendicular;
                        p1 = entryOffset + miterWidth * outwardOffset;
                        p2 = exitOffset + miterWidth * outwardOffset;
                    }
                    else
                    {
                        p1 = entryOffset + miterWidth * (bisectorOffset - entryOffset) / miter;
                        p2 = exitOffset + miterWidth * (bisectorOffset - exitOffset) / miter;
                    }

                    yield return(Curve.Line(p + exitOffset, p + p2));

                    yield return(Curve.Line(p + p2, p + p1));

                    yield return(Curve.Line(p + p1, p + entryOffset));
                }
                break;
            }

            case SvgStrokeLineJoin.Round:
                // Generate the circle
                yield return(Curve.Circle(p, halfWidth, exitOffset, entryOffset, sd < 0));

                break;

            case SvgStrokeLineJoin.Arcs:
            {
                // Compute the curvatures of the curves
                var exitKappa  = prevCurve.ExitCurvature;
                var entryKappa = nextCurve.EntryCurvature;

                // If one of the curvatures is too large, fall back to round
                if (Math.Abs(exitKappa) * halfWidth >= 1 || Math.Abs(entryKappa) * halfWidth >= 1)
                {
                    goto case SvgStrokeLineJoin.Round;
                }

                // If both of them are zero, fall back to miter
                if (RoughlyZero(exitKappa) && RoughlyZero(entryKappa))
                {
                    goto case SvgStrokeLineJoin.MiterClip;
                }
                // If one (or both) are nonzero, build the possible circles
                else
                {
                    // The "arcs" must use a different sign convention
                    var sign = diff > 0 ? 1 : -1;

                    var exitRadius  = 1 / exitKappa - sign * halfWidth;
                    var entryRadius = 1 / entryKappa - sign * halfWidth;

                    var exitCenter  = exitTangent.CCWPerpendicular / exitKappa;
                    var entryCenter = entryTangent.CCWPerpendicular / entryKappa;

                    // Find the intersection points
                    Double2[] points;
                    if (!RoughlyZero(exitKappa) && !RoughlyZero(entryKappa))
                    {
                        points = GeometricUtils.CircleIntersection(exitCenter, exitRadius, entryCenter, entryRadius);
                    }
                    else if (!RoughlyZero(exitKappa))
                    {
                        points = GeometricUtils.CircleLineIntersection(exitCenter, exitRadius, entryOffset, entryTangent);
                    }
                    else
                    {
                        points = GeometricUtils.CircleLineIntersection(entryCenter, entryRadius, exitOffset, exitTangent);
                    }

                    // If there are no intersections, adjust the curves
                    if (points.Length == 0)
                    {
                        if (!RoughlyZero(exitKappa) && !RoughlyZero(entryKappa))
                        {
                            points = AdjustCircles(ref exitCenter, ref exitRadius, exitTangent.CCWPerpendicular,
                                                   ref entryCenter, ref entryRadius, entryTangent.CCWPerpendicular);
                        }
                        else if (!RoughlyZero(exitKappa))
                        {
                            points = AdjustCircleLine(ref exitCenter, ref exitRadius, exitTangent.CCWPerpendicular,
                                                      entryOffset, entryTangent);
                        }
                        else
                        {
                            points = AdjustCircleLine(ref entryCenter, ref entryRadius, entryTangent.CCWPerpendicular,
                                                      exitOffset, exitTangent);
                        }
                    }

                    // If still no solutions are found, go to the miter-clip case
                    if (points.Length == 0)
                    {
                        goto case SvgStrokeLineJoin.MiterClip;
                    }

                    // Check both which point have less travelled distance
                    double PointParameter(Double2 pt)
                    {
                        if (RoughlyZero(exitKappa))
                        {
                            var d = exitTangent.Dot(pt - exitOffset);
                            return(d < 0 ? double.PositiveInfinity : d);
                        }
                        return((Math.Sign(exitKappa) * (exitOffset - exitCenter).AngleBetween(pt - exitCenter)).WrapAngle360());
                    }

                    var angles = points.Select(PointParameter).ToArray();

                    // Pick the point with the least travelled angle
                    var point = angles[0] <= angles[1] ? points[0] : points[1];

                    // Generate the arcs to be rendered
                    if (!RoughlyZero(exitKappa))
                    {
                        yield return(Curve.Circle(p + exitCenter, Math.Abs(exitRadius), exitOffset - exitCenter,
                                                  point - exitCenter, exitKappa > 0));
                    }
                    else
                    {
                        yield return(Curve.Line(p + exitOffset, p + point));
                    }

                    if (!RoughlyZero(entryKappa))
                    {
                        yield return(Curve.Circle(p + entryCenter, Math.Abs(entryRadius), point - entryCenter,
                                                  entryOffset - entryCenter, entryKappa > 0));
                    }
                    else
                    {
                        yield return(Curve.Line(p + point, p + entryOffset));
                    }
                }
            }
            break;

            default: throw new ArgumentException("Unrecognized lineJoin", nameof(lineJoin));
            }
        }
        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);
        }