praeclarum / NGraphics

NGraphics is a cross platform library for rendering vector graphics on .NET. It provides a unified API for both immediate and retained mode graphics using high quality native renderers.
MIT License
705 stars 133 forks source link

Bug in SvgReader for paths with 'm' command after 'z' command #75

Closed franzdevel closed 7 years ago

franzdevel commented 7 years ago

The following material design icon is not processed correctly by SvgReader:

<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
    <path d="M6 18v12h8l10 10V8L14 18H6zm27 6c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zM28 6.46v4.13c5.78 1.72 10 7.07 10 13.41s-4.22 11.69-10 13.41v4.13c8.01-1.82 14-8.97 14-17.54S36.01 8.28 28 6.46z"/>
</svg>

The problem lies in the fact that an 'm' command (relative moveto) follows a 'z' command (close). The information about the previous point in the path is lost after the 'z' command and cannot be applied to the 'm' command.

Posting here a corrected version of SvgReader.ReadPath(). However, I am not an SVG expert so please check the proposed solution for overall correctness.

void ReadPath (Path p, string pathDescriptor)
{
    Match m = pathRegex.Match(pathDescriptor);
    Point previousPoint = new Point();
    while(m.Success)
    {
        var match = m.Value.TrimStart ();
        var op = match[0];

        if (op == 'z' || op == 'Z') {
            p.Close ();
        } else {
            // make sure negative numbers are split properly
            match = negativeNumberRe.Replace(match.Substring(1), " -");
            var args = match.Split(WSC, StringSplitOptions.RemoveEmptyEntries);

            int index = 0;
            while(index < args.Length)
            {
                if ((op == 'M' || op == 'm') && args.Length >= index+2) {
                    var point = new Point (ReadNumber (args [index]), ReadNumber (args [index+1]));
                    if (op == 'm')
                        point += previousPoint;
                    p.MoveTo (point);
                    index += 2;
                } else if ((op == 'L' || op == 'l') && args.Length >= index+2) {
                    var point = new Point (ReadNumber (args [index]), ReadNumber (args [index+1]));
                    if (op == 'l')
                        point += previousPoint;
                    p.LineTo (point);
                    index += 2;
                } else if ((op == 'C' || op == 'c') && args.Length >= index+6) {
                    var c1 = new Point (ReadNumber (args [index]), ReadNumber (args [index+1]));
                    var c2 = new Point (ReadNumber (args [index+2]), ReadNumber (args [index+3]));
                    var pt = new Point (ReadNumber (args [index+4]), ReadNumber (args [index+5]));
                    if (op == 'c')
                    {
                        c1 += previousPoint;
                        c2 += previousPoint;
                        pt += previousPoint;
                    }
                    p.CurveTo (c1, c2, pt);
                    index += 6;
                } else if ((op == 'S' || op == 's') && args.Length >= index+4) {
                    var c  = new Point (ReadNumber (args [index]), ReadNumber (args [index+1]));
                    var pt = new Point (ReadNumber (args [index+2]), ReadNumber (args [index+3]));
                    if (op == 's')
                    {
                        c += previousPoint;
                        pt += previousPoint;
                    }
                    p.ContinueCurveTo (c, pt);
                    index += 4;
                } else if ((op == 'A' || op == 'a') && args.Length >= index+7) {
                    var r = new Size (ReadNumber (args [index]), ReadNumber (args [index+1]));
                    var laf = ReadNumber (args [index+3]) != 0;
                    var swf = ReadNumber (args [index+4]) != 0;
                    var pt = new Point (ReadNumber (args [index+5]), ReadNumber (args [index+6]));
                    if (op == 'a')
                        pt += previousPoint;
                    p.ArcTo (r, laf, swf, pt);
                    index += 7;
                } else if ((op == 'V' || op == 'v') && args.Length >= index+1 && p.Operations.Count > 0) {
                    var previousX = previousPoint.X;
                    var y = ReadNumber(args[index]);
                    if (op == 'v')
                        y += previousPoint.Y;
                    var point = new Point(previousX, y);
                    p.LineTo(point);
                    index += 1;
                } else if ((op == 'H' || op == 'h') && args.Length >= index+1 && p.Operations.Count > 0) {
                    var previousY = previousPoint.Y;
                    var x = ReadNumber(args[index]);
                    if (op == 'h')
                        x += previousPoint.X;
                    var point = new Point(x, previousY);
                    p.LineTo(point);
                    index += 1;
                } else {
                    throw new NotSupportedException ("Path Operation " + op);
                }
                previousPoint = p.Operations.Last().EndPoint;
            }
        }
        m = m.NextMatch();
    }
}