/*
 * Copyright (c) 2013-14 Mikko Mononen memon@inside.org
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
 * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
 *
 * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
 *
 * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
 *
 * Fork developement, feature integration and new bugs:
 * Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
 * Contains code from various contributors.
 */
/**
  NanoVega.SVG is a simple stupid SVG parser. The output of the parser is a list of drawing commands.

  The library suits well for anything from rendering scalable icons in your editor application to prototyping a game.

  NanoVega.SVG supports a wide range of SVG features, but something may be missing. Your's Captain Obvious.


  The shapes in the SVG images are transformed by the viewBox and converted to specified units.
  That is, you should get the same looking data as your designed in your favorite app.

  NanoVega.SVG can return the paths in few different units. For example if you want to render an image, you may choose
  to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters.

  The units passed to NanoVega.SVG should be one of: 'px', 'pt', 'pc', 'mm', 'cm', 'in'.
  DPI (dots-per-inch) controls how the unit conversion is done.

  If you don't know or care about the units stuff, "px" and 96 should get you going.

  Example Usage:

  ---
    // Load
    NSVG* image = nsvgParseFromFile("test.svg", "px", 96);
    printf("size: %f x %f\n", image.width, image.height);
    // Use...
    image.forEachShape((in ref NSVG.Shape shape) {
      if (!shape.visible) return;
      shape.forEachPath((in ref NSVG.Path path) {
        // this will issue final `LineTo` for closed pathes
        path.forEachCommand!true(delegate (NSVG.Command cmd, const(float)[] args) nothrow @trusted @nogc {
          final switch (cmd) {
            case NSVG.Command.MoveTo: nvg.moveTo(args); break;
            case NSVG.Command.LineTo: nvg.lineTo(args); break;
            case NSVG.Command.QuadTo: nvg.quadTo(args); break;
            case NSVG.Command.BezierTo: nvg.bezierTo(args); break;
          }
        });
      });
    });

    NSVGrasterizer rast = nsvgCreateRasterizer();
    // Allocate memory for image
    ubyte* img = malloc(w*h*4);
    // Rasterize
    rasterize(rast, image, 0, 0, 1, img, w, h, w*4);

    // Delete
    image.kill();
  ---

  To turn a SVG into a png:
  ---
	import arsd.svg;
	import arsd.png;

	void main() {
	    // Load
	    NSVG* image = nsvgParseFromFile("test.svg", "px", 96);

	    int w = 200;
	    int h = 200;

	    NSVGrasterizer rast = nsvgCreateRasterizer();
	    // Allocate memory for image
	    auto img = new TrueColorImage(w, h);
	    // Rasterize
	    rasterize(rast, image, 0, 0, 1, img.imageData.bytes.ptr, w, h, w*4);

	    // Delete
	    image.kill();

	    writePng("test.png", img);


	}
  ---
 */
module arsd.svg;

private import core.stdc.math : fabs, fabsf, atan2f, acosf, cosf, sinf, tanf, sqrt, sqrtf, floorf, ceilf, fmodf;
//private import iv.vfs;

version(nanosvg_disable_vfs) {
  enum NanoSVGHasIVVFS = false;
} else {
  static if (is(typeof((){import iv.vfs;}))) {
    enum NanoSVGHasIVVFS = true;
    import iv.vfs;
  } else {
    enum NanoSVGHasIVVFS = false;
  }
}

version(aliced) {} else {
  private alias usize = size_t;
}

version = nanosvg_crappy_stylesheet_parser;
//version = nanosvg_debug_styles;
//version(rdmd) import iv.strex;

//version = nanosvg_use_beziers; // convert everything to beziers
//version = nanosvg_only_cubic_beziers; // convert everything to cubic beziers

///
public enum NSVGDefaults {
  CanvasWidth = 800,
  CanvasHeight = 600,
}


// ////////////////////////////////////////////////////////////////////////// //
public alias NSVGrasterizer = NSVGrasterizerS*; ///
public alias NSVGRasterizer = NSVGrasterizer; ///

///
struct NSVG {
  @disable this (this);

  ///
  enum Command : int {
    MoveTo, ///
    LineTo, ///
    QuadTo, ///
    BezierTo, /// cubic bezier
  }

  ///
  enum PaintType : ubyte {
    None, ///
    Color, ///
    LinearGradient, ///
    RadialGradient, ///
  }

  ///
  enum SpreadType : ubyte {
    Pad, ///
    Reflect, ///
    Repeat, ///
  }

  ///
  enum LineJoin : ubyte {
    Miter, ///
    Round, ///
    Bevel, ///
  }

  ///
  enum LineCap : ubyte {
    Butt, ///
    Round, ///
    Square, ///
  }

  ///
  enum FillRule : ubyte {
    NonZero, ///
    EvenOdd, ///
  }

  alias Flags = ubyte; ///
  enum : ubyte {
    Visible = 0x01, ///
  }

  ///
  static struct GradientStop {
    uint color; ///
    float offset; ///
  }

  ///
  static struct Gradient {
    float[6] xform; ///
    SpreadType spread; ///
    float fx, fy; ///
    int nstops; ///
    GradientStop[0] stops; ///
  }

  ///
  static struct Paint {
  pure nothrow @safe @nogc:
    @disable this (this);
    PaintType type; ///
    union {
      uint color; ///
      Gradient* gradient; ///
    }
    static uint rgb (ubyte r, ubyte g, ubyte b) { pragma(inline, true); return (r|(g<<8)|(b<<16)); } ///
    @property const {
      bool isNone () { pragma(inline, true); return (type == PaintType.None); } ///
      bool isColor () { pragma(inline, true); return (type == PaintType.Color); } ///
      // gradient types
      bool isLinear () { pragma(inline, true); return (type == PaintType.LinearGradient); } ///
      bool isRadial () { pragma(inline, true); return (type == PaintType.RadialGradient); } ///
      // color
      ubyte r () { pragma(inline, true); return color&0xff; } ///
      ubyte g () { pragma(inline, true); return (color>>8)&0xff; } ///
      ubyte b () { pragma(inline, true); return (color>>16)&0xff; } ///
      ubyte a () { pragma(inline, true); return (color>>24)&0xff; } ///
    }
  }

  ///
  static struct Path {
    @disable this (this);
    float* stream;   /// Command, args...; Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
    int nsflts;      /// Total number of floats in stream.
    bool closed;     /// Flag indicating if shapes should be treated as closed.
    float[4] bounds; /// Tight bounding box of the shape [minx,miny,maxx,maxy].
    NSVG.Path* next; /// Pointer to next path, or null if last element.

    ///
    @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (nsflts == 0); }

    ///
    float startX () const nothrow @trusted @nogc {
      pragma(inline, true);
      return (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo ? stream[1] : float.nan);
    }

    ///
    float startY () const nothrow @trusted @nogc {
      pragma(inline, true);
      return (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo ? stream[2] : float.nan);
    }

    ///
    bool startPoint (float* dx, float* dy) const nothrow @trusted @nogc {
      if (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo) {
        if (dx !is null) *dx = stream[1];
        if (dy !is null) *dy = stream[2];
        return true;
      } else {
        if (dx !is null) *dx = 0;
        if (dy !is null) *dy = 0;
        return false;
      }
    }

    ///
    int countCubics () const nothrow @trusted @nogc {
      if (nsflts < 3) return 0;
      int res = 0, argc;
      for (int pidx = 0; pidx+3 <= nsflts; ) {
        final switch (cast(Command)stream[pidx++]) {
          case Command.MoveTo: argc = 2; break;
          case Command.LineTo: argc = 2; ++res; break;
          case Command.QuadTo: argc = 4; ++res; break;
          case Command.BezierTo: argc = 6; ++res; break;
        }
        if (pidx+argc > nsflts) break; // just in case
        pidx += argc;
      }
      return res;
    }

    ///
    int countCommands(bool synthesizeCloseCommand=true) () const nothrow @trusted @nogc {
      if (nsflts < 3) return 0;
      int res = 0, argc;
      for (int pidx = 0; pidx+3 <= nsflts; ) {
        ++res;
        final switch (cast(Command)stream[pidx++]) {
          case Command.MoveTo: argc = 2; break;
          case Command.LineTo: argc = 2; break;
          case Command.QuadTo: argc = 4; break;
          case Command.BezierTo: argc = 6; break;
        }
        if (pidx+argc > nsflts) break; // just in case
        pidx += argc;
      }
      static if (synthesizeCloseCommand) { if (closed) ++res; }
      return res;
    }

    /// emits cubic beziers.
    /// if `withMoveTo` is `false`, issue 8-arg commands for cubic beziers (i.e. include starting point).
    /// if `withMoveTo` is `true`, issue 2-arg command for `moveTo`, and 6-arg command for cubic beziers.
    void asCubics(bool withMoveTo=false, DG) (scope DG dg) inout if (__traits(compiles, (){ DG xdg; float[] f; xdg(f); })) {
      if (dg is null) return;
      if (nsflts < 3) return;
      enum HasRes = __traits(compiles, (){ DG xdg; float[] f; bool res = xdg(f); });
      float cx = 0, cy = 0;
      float[8] cubic = void;

      void synthLine (in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
        immutable float dx = x-cx;
        immutable float dy = y-cy;
        cubic.ptr[0] = cx;
        cubic.ptr[1] = cy;
        cubic.ptr[2] = cx+dx/3.0f;
        cubic.ptr[3] = cy+dy/3.0f;
        cubic.ptr[4] = x-dx/3.0f;
        cubic.ptr[5] = y-dy/3.0f;
        cubic.ptr[6] = x;
        cubic.ptr[7] = y;
      }

      void synthQuad (in float cx, in float cy, in float x1, in float y1, in float x2, in float y2) nothrow @trusted @nogc {
        immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
        immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
        immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
        immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
        cubic.ptr[0] = cx;
        cubic.ptr[1] = cy;
        cubic.ptr[2] = cx1;
        cubic.ptr[3] = cy2;
        cubic.ptr[4] = cx2;
        cubic.ptr[5] = cy2;
        cubic.ptr[6] = x2;
        cubic.ptr[7] = y2;
      }

      for (int pidx = 0; pidx+3 <= nsflts; ) {
        final switch (cast(Command)stream[pidx++]) {
          case Command.MoveTo:
            static if (withMoveTo) {
              static if (HasRes) { if (dg(stream[pidx+0..pidx+2])) return; } else { dg(stream[pidx+0..pidx+2]); }
            }
            cx = stream[pidx++];
            cy = stream[pidx++];
            continue;
          case Command.LineTo:
            synthLine(cx, cy, stream[pidx+0], stream[pidx+1]);
            pidx += 2;
            break;
          case Command.QuadTo:
            synthQuad(cx, cy, stream[pidx+0], stream[pidx+1], stream[pidx+2], stream[pidx+3]);
            pidx += 4;
            break;
          case Command.BezierTo:
            cubic.ptr[0] = cx;
            cubic.ptr[1] = cy;
            cubic.ptr[2..8] = stream[pidx..pidx+6];
            pidx += 6;
            break;
        }
        cx = cubic.ptr[6];
        cy = cubic.ptr[7];
        static if (withMoveTo) {
          static if (HasRes) { if (dg(cubic[2..8])) return; } else { dg(cubic[2..8]); }
        } else {
          static if (HasRes) { if (dg(cubic[])) return; } else { dg(cubic[]); }
        }
      }
    }

    /// if `synthesizeCloseCommand` is true, and the path is closed, this emits line to the first point.
    void forEachCommand(bool synthesizeCloseCommand=true, DG) (scope DG dg) inout
    if (__traits(compiles, (){ DG xdg; Command c; const(float)[] f; xdg(c, f); }))
    {
      if (dg is null) return;
      if (nsflts < 3) return;
      enum HasRes = __traits(compiles, (){ DG xdg; Command c; const(float)[] f; bool res = xdg(c, f); });
      int argc;
      Command cmd;
      for (int pidx = 0; pidx+3 <= nsflts; ) {
        cmd = cast(Command)stream[pidx++];
        final switch (cmd) {
          case Command.MoveTo: argc = 2; break;
          case Command.LineTo: argc = 2; break;
          case Command.QuadTo: argc = 4; break;
          case Command.BezierTo: argc = 6; break;
        }
        if (pidx+argc > nsflts) break; // just in case
        static if (HasRes) { if (dg(cmd, stream[pidx..pidx+argc])) return; } else { dg(cmd, stream[pidx..pidx+argc]); }
        pidx += argc;
      }
      static if (synthesizeCloseCommand) {
        if (closed && cast(Command)stream[0] == Command.MoveTo) {
          static if (HasRes) { if (dg(Command.LineTo, stream[1..3])) return; } else { dg(Command.LineTo, stream[1..3]); }
        }
      }
    }
  }

  ///
  static struct Shape {
    @disable this (this);
    char[64] id = 0;          /// Optional 'id' attr of the shape or its group
    NSVG.Paint fill;          /// Fill paint
    NSVG.Paint stroke;        /// Stroke paint
    float opacity;            /// Opacity of the shape.
    float strokeWidth;        /// Stroke width (scaled).
    float strokeDashOffset;   /// Stroke dash offset (scaled).
    float[8] strokeDashArray; /// Stroke dash array (scaled).
    byte strokeDashCount;     /// Number of dash values in dash array.
    LineJoin strokeLineJoin;  /// Stroke join type.
    LineCap strokeLineCap;    /// Stroke cap type.
    float miterLimit;         /// Miter limit
    FillRule fillRule;        /// Fill rule, see FillRule.
    /*Flags*/ubyte flags;     /// Logical or of NSVG_FLAGS_* flags
    float[4] bounds;          /// Tight bounding box of the shape [minx,miny,maxx,maxy].
    NSVG.Path* paths;         /// Linked list of paths in the image.
    NSVG.Shape* next;         /// Pointer to next shape, or null if last element.

    @property bool visible () const pure nothrow @safe @nogc { pragma(inline, true); return ((flags&Visible) != 0); } ///

    /// delegate can accept:
    ///   NSVG.Path*
    ///   const(NSVG.Path)*
    ///   ref NSVG.Path
    ///   in ref NSVG.Path
    /// delegate can return:
    ///   void
    ///   bool (true means `stop`)
    void forEachPath(DG) (scope DG dg) inout
    if (__traits(compiles, (){ DG xdg; NSVG.Path s; xdg(&s); }) ||
        __traits(compiles, (){ DG xdg; NSVG.Path s; xdg(s); }))
    {
      if (dg is null) return;
      enum WantPtr = __traits(compiles, (){ DG xdg; NSVG.Path s; xdg(&s); });
      static if (WantPtr) {
        enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Path s; bool res = xdg(&s); });
      } else {
        enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Path s; bool res = xdg(s); });
      }
      static if (__traits(compiles, (){ NSVG.Path* s = this.paths; })) {
        alias TP = NSVG.Path*;
      } else {
        alias TP = const(NSVG.Path)*;
      }
      for (TP path = paths; path !is null; path = path.next) {
        static if (HasRes) {
          static if (WantPtr) {
            if (dg(path)) return;
          } else {
            if (dg(*path)) return;
          }
        } else {
          static if (WantPtr) dg(path); else dg(*path);
        }
      }
    }
  }

  float width;        /// Width of the image.
  float height;       /// Height of the image.
  NSVG.Shape* shapes; /// Linked list of shapes in the image.

  /// delegate can accept:
  ///   NSVG.Shape*
  ///   const(NSVG.Shape)*
  ///   ref NSVG.Shape
  ///   in ref NSVG.Shape
  /// delegate can return:
  ///   void
  ///   bool (true means `stop`)
  void forEachShape(DG) (scope DG dg) inout
  if (__traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(&s); }) ||
      __traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(s); }))
  {
    if (dg is null) return;
    enum WantPtr = __traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(&s); });
    static if (WantPtr) {
      enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Shape s; bool res = xdg(&s); });
    } else {
      enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Shape s; bool res = xdg(s); });
    }
    static if (__traits(compiles, (){ NSVG.Shape* s = this.shapes; })) {
      alias TP = NSVG.Shape*;
    } else {
      alias TP = const(NSVG.Shape)*;
    }
    for (TP shape = shapes; shape !is null; shape = shape.next) {
      static if (HasRes) {
        static if (WantPtr) {
          if (dg(shape)) return;
        } else {
          if (dg(*shape)) return;
        }
      } else {
        static if (WantPtr) dg(shape); else dg(*shape);
      }
    }
  }
}


// ////////////////////////////////////////////////////////////////////////// //
private:
nothrow @trusted @nogc {

// ////////////////////////////////////////////////////////////////////////// //
// sscanf replacement: just enough to replace all our cases
int xsscanf(A...) (const(char)[] str, const(char)[] fmt, ref A args) {
  int spos;
  while (spos < str.length && str.ptr[spos] <= ' ') ++spos;

  static int hexdigit() (char c) {
    pragma(inline, true);
    return
      (c >= '0' && c <= '9' ? c-'0' :
       c >= 'A' && c <= 'F' ? c-'A'+10 :
       c >= 'a' && c <= 'f' ? c-'a'+10 :
       -1);
  }

  bool parseInt(T : ulong) (ref T res) {
    res = 0;
    debug(xsscanf_int) { import std.stdio; writeln("parseInt00: str=", str[spos..$].quote); }
    bool neg = false;
         if (spos < str.length && str.ptr[spos] == '+') ++spos;
    else if (spos < str.length && str.ptr[spos] == '-') { neg = true; ++spos; }
    if (spos >= str.length || str.ptr[spos] < '0' || str.ptr[spos] > '9') return false;
    while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') res = res*10+str.ptr[spos++]-'0';
    debug(xsscanf_int) { import std.stdio; writeln("parseInt10: str=", str[spos..$].quote); }
    if (neg) res = -res;
    return true;
  }

  bool parseHex(T : ulong) (ref T res) {
    res = 0;
    debug(xsscanf_int) { import std.stdio; writeln("parseHex00: str=", str[spos..$].quote); }
    if (spos >= str.length || hexdigit(str.ptr[spos]) < 0) return false;
    while (spos < str.length) {
      auto d = hexdigit(str.ptr[spos]);
      if (d < 0) break;
      res = res*16+d;
      ++spos;
    }
    debug(xsscanf_int) { import std.stdio; writeln("parseHex10: str=", str[spos..$].quote); }
    return true;
  }

  bool parseFloat(T : real) (ref T res) {
    res = 0.0;
    debug(xsscanf_float) { import std.stdio; writeln("parseFloat00: str=", str[spos..$].quote); }
    bool neg = false;
         if (spos < str.length && str.ptr[spos] == '+') ++spos;
    else if (spos < str.length && str.ptr[spos] == '-') { neg = true; ++spos; }
    bool wasChar = false;
    // integer part
    debug(xsscanf_float) { import std.stdio; writeln("parseFloat01: str=", str[spos..$].quote); }
    if (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') wasChar = true;
    while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') res = res*10+str.ptr[spos++]-'0';
    // fractional part
    if (spos < str.length && str.ptr[spos] == '.') {
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat02: str=", str[spos..$].quote); }
      T div = 1.0/10;
      ++spos;
      if (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') wasChar = true;
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat03: str=", str[spos..$].quote); }
      while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') {
        res += div*(str.ptr[spos++]-'0');
        div /= 10.0;
      }
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat04: str=", str[spos..$].quote); }
      debug(xsscanf_float) { import std.stdio; writeln("div=", div, "; res=", res, "; str=", str[spos..$].quote); }
    }
    // '[Ee][+-]num' part
    if (wasChar && spos < str.length && (str.ptr[spos] == 'E' || str.ptr[spos] == 'e')) {
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat05: str=", str[spos..$].quote); }
      ++spos;
      bool xneg = false;
           if (spos < str.length && str.ptr[spos] == '+') ++spos;
      else if (spos < str.length && str.ptr[spos] == '-') { xneg = true; ++spos; }
      int n = 0;
      if (spos >= str.length || str.ptr[spos] < '0' || str.ptr[spos] > '9') return false; // number expected
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat06: str=", str[spos..$].quote); }
      while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') n = n*10+str.ptr[spos++]-'0';
      if (xneg) {
        while (n-- > 0) res /= 10;
      } else {
        while (n-- > 0) res *= 10;
      }
      debug(xsscanf_float) { import std.stdio; writeln("parseFloat07: str=", str[spos..$].quote); }
    }
    if (!wasChar) return false;
    debug(xsscanf_float) { import std.stdio; writeln("parseFloat10: str=", str[spos..$].quote); }
    if (neg) res = -res;
    return true;
  }

  int fpos;

  void skipXSpaces () {
    if (fpos < fmt.length && fmt.ptr[fpos] <= ' ') {
      while (fpos < fmt.length && fmt.ptr[fpos] <= ' ') ++fpos;
      while (spos < str.length && str.ptr[spos] <= ' ') ++spos;
    }
  }

  bool parseImpl(T/*, usize dummy*/) (ref T res) {
    while (fpos < fmt.length) {
      //{ import std.stdio; writeln("spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote); }
      if (fmt.ptr[fpos] <= ' ') {
        skipXSpaces();
        continue;
      }
      if (fmt.ptr[fpos] != '%') {
        if (spos >= str.length || str.ptr[spos] != fmt.ptr[spos]) return false;
        ++spos;
        ++fpos;
        continue;
      }
      if (fmt.length-fpos < 2) return false; // stray percent
      fpos += 2;
      bool skipAss = false;
      if (fmt.ptr[fpos-1] == '*') {
        ++fpos;
        if (fpos >= fmt.length) return false; // stray star
        skipAss = true;
      }
      switch (fmt.ptr[fpos-1]) {
        case '%':
          if (spos >= str.length || str.ptr[spos] != '%') return false;
          ++spos;
          break;
        case 'd':
          static if (is(T : ulong)) {
            if (skipAss) {
              long v;
              if (!parseInt!long(v)) return false;
            } else {
              return parseInt!T(res);
            }
          } else {
            if (!skipAss) assert(0, "invalid type");
            long v;
            if (!parseInt!long(v)) return false;
          }
          break;
        case 'x':
          static if (is(T : ulong)) {
            if (skipAss) {
              long v;
              if (!parseHex!long(v)) return false;
            } else {
              return parseHex!T(res);
            }
          } else {
            if (!skipAss) assert(0, "invalid type");
            ulong v;
            if (!parseHex!ulong(v)) return false;
          }
          break;
        case 'f':
          static if (is(T == float) || is(T == double) || is(T == real)) {
            if (skipAss) {
              double v;
              if (!parseFloat!double(v)) return false;
            } else {
              return parseFloat!T(res);
            }
          } else {
            if (!skipAss) assert(0, "invalid type");
            double v;
            if (!parseFloat!double(v)) return false;
          }
          break;
        case '[':
          if (fmt.length-fpos < 1) return false;
          auto stp = spos;
          while (spos < str.length) {
            bool ok = false;
            foreach (immutable cidx, char c; fmt[fpos..$]) {
              if (cidx != 0) {
                if (c == '-') assert(0, "not yet");
                if (c == ']') break;
              }
              if (c == ' ') {
                if (str.ptr[spos] <= ' ') { ok = true; break; }
              } else {
                if (str.ptr[spos] == c) { ok = true; break; }
              }
            }
            //{ import std.stdio; writeln("** spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote, "\nok: ", ok); }
            if (!ok) break; // not a match
            ++spos; // skip match
          }
          ++fpos;
          while (fpos < fmt.length && fmt[fpos] != ']') ++fpos;
          if (fpos < fmt.length) ++fpos;
          static if (is(T == const(char)[])) {
            if (!skipAss) {
              res = str[stp..spos];
              return true;
            }
          } else {
            if (!skipAss) assert(0, "invalid type");
          }
          break;
        case 's':
          auto stp = spos;
          while (spos < str.length && str.ptr[spos] > ' ') ++spos;
          static if (is(T == const(char)[])) {
            if (!skipAss) {
              res = str[stp..spos];
              return true;
            }
          } else {
            // skip non-spaces
            if (!skipAss) assert(0, "invalid type");
          }
          break;
        default: assert(0, "unknown format specifier");
      }
    }
    return false;
  }

  foreach (usize aidx, immutable T; A) {
    //pragma(msg, "aidx=", aidx, "; T=", T);
    if (!parseImpl!(T)(args[aidx])) return -(spos+1);
    //{ import std.stdio; writeln("@@@ aidx=", aidx+3, "; spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote); }
  }
  skipXSpaces();
  return (fpos < fmt.length ? -(spos+1) : spos);
}


// ////////////////////////////////////////////////////////////////////////// //
T* xalloc(T) (usize addmem=0) if (!is(T == class)) {
  import core.stdc.stdlib : malloc;
  if (T.sizeof == 0 && addmem == 0) addmem = 1;
  auto res = cast(ubyte*)malloc(T.sizeof+addmem+256);
  if (res is null) assert(0, "NanoVega.SVG: out of memory");
  res[0..T.sizeof+addmem] = 0;
  return cast(T*)res;
}

T* xcalloc(T) (usize count) if (!is(T == class) && !is(T == struct)) {
  import core.stdc.stdlib : malloc;
  usize sz = T.sizeof*count;
  if (sz == 0) sz = 1;
  auto res = cast(ubyte*)malloc(sz+256);
  if (res is null) assert(0, "NanoVega.SVG: out of memory");
  res[0..sz] = 0;
  return cast(T*)res;
}

void xfree(T) (ref T* p) {
  if (p !is null) {
    import core.stdc.stdlib : free;
    free(p);
    p = null;
  }
}


alias AttrList = const(const(char)[])[];

public enum NSVG_PI = 3.14159265358979323846264338327f; ///
enum NSVG_KAPPA90 = 0.5522847493f; // Lenght proportional to radius of a cubic bezier handle for 90deg arcs.

enum NSVG_ALIGN_MIN = 0;
enum NSVG_ALIGN_MID = 1;
enum NSVG_ALIGN_MAX = 2;
enum NSVG_ALIGN_NONE = 0;
enum NSVG_ALIGN_MEET = 1;
enum NSVG_ALIGN_SLICE = 2;


int nsvg__isspace() (char c) { pragma(inline, true); return (c && c <= ' '); } // because
int nsvg__isdigit() (char c) { pragma(inline, true); return (c >= '0' && c <= '9'); }
int nsvg__isnum() (char c) { pragma(inline, true); return ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E'); }

int nsvg__hexdigit() (char c) {
  pragma(inline, true);
  return
    (c >= '0' && c <= '9' ? c-'0' :
     c >= 'A' && c <= 'F' ? c-'A'+10 :
     c >= 'a' && c <= 'f' ? c-'a'+10 :
     -1);
}

float nsvg__minf() (float a, float b) { pragma(inline, true); return (a < b ? a : b); }
float nsvg__maxf() (float a, float b) { pragma(inline, true); return (a > b ? a : b); }


// Simple XML parser
enum NSVG_XML_TAG = 1;
enum NSVG_XML_CONTENT = 2;
enum NSVG_XML_MAX_ATTRIBS = 256;

void nsvg__parseContent (const(char)[] s, scope void function (void* ud, const(char)[] s) nothrow @nogc contentCb, void* ud) {
  // Trim start white spaces
  while (s.length && nsvg__isspace(s[0])) s = s[1..$];
  if (s.length == 0) return;
  //{ import std.stdio; writeln("s=", s.quote); }
  if (contentCb !is null) contentCb(ud, s);
}

static void nsvg__parseElement (const(char)[] s,
                 scope void function (void* ud, const(char)[] el, AttrList attr) nothrow @nogc startelCb,
                 scope void function (void* ud, const(char)[] el) nothrow @nogc endelCb,
                 void* ud)
{
  const(char)[][NSVG_XML_MAX_ATTRIBS] attr;
  int nattr = 0;
  const(char)[] name;
  int start = 0;
  int end = 0;
  char quote;

  // Skip white space after the '<'
  while (s.length && nsvg__isspace(s[0])) s = s[1..$];

  // Check if the tag is end tag
  if (s.length && s[0] == '/') {
    s = s[1..$];
    end = 1;
  } else {
    start = 1;
  }

  // Skip comments, data and preprocessor stuff.
  if (s.length == 0 || s[0] == '?' || s[0] == '!') return;

  // Get tag name
  //{ import std.stdio; writeln("bs=", s.quote); }
  {
    usize pos = 0;
    while (pos < s.length && !nsvg__isspace(s[pos])) ++pos;
    name = s[0..pos];
    s = s[pos..$];
  }
  //{ import std.stdio; writeln("name=", name.quote); }
  //{ import std.stdio; writeln("as=", s.quote); }

  // Get attribs
  while (!end && s.length && attr.length-nattr >= 2) {
    // skip white space before the attrib name
    while (s.length && nsvg__isspace(s[0])) s = s[1..$];
    if (s.length == 0) break;
    if (s[0] == '/') { end = 1; break; }
    // find end of the attrib name
    {
      usize pos = 0;
      while (pos < s.length && !nsvg__isspace(s[pos]) && s[pos] != '=') ++pos;
      attr[nattr++] = s[0..pos];
      s = s[pos..$];
    }
    // skip until the beginning of the value
    while (s.length && s[0] != '\"' && s[0] != '\'') s = s[1..$];
    if (s.length == 0) break;
    // store value and find the end of it
    quote = s[0];
    s = s[1..$];
    {
      usize pos = 0;
      while (pos < s.length && s[pos] != quote) ++pos;
      attr[nattr++] = s[0..pos];
      s = s[pos+(pos < s.length ? 1 : 0)..$];
    }
    //{ import std.stdio; writeln("n=", attr[nattr-2].quote, "\nv=", attr[nattr-1].quote, "\n"); }
  }

  debug(nanosvg) {
    import std.stdio;
    writeln("===========================");
    foreach (immutable idx, const(char)[] v; attr[0..nattr]) writeln("  #", idx, ": ", v.quote);
  }

  // Call callbacks.
  if (start && startelCb !is null) startelCb(ud, name, attr[0..nattr]);
  if (end && endelCb !is null) endelCb(ud, name);
}

void nsvg__parseXML (const(char)[] input,
                     scope void function (void* ud, const(char)[] el, AttrList attr) nothrow @nogc startelCb,
                     scope void function (void* ud, const(char)[] el) nothrow @nogc endelCb,
                     scope void function (void* ud, const(char)[] s) nothrow @nogc contentCb,
                     void* ud)
{
  usize cpos = 0;
  int state = NSVG_XML_CONTENT;
  while (cpos < input.length) {
    if (state == NSVG_XML_CONTENT && input[cpos] == '<') {
      if (input.length-cpos >= 9 && input[cpos..cpos+9] == "<![CDATA[") {
        cpos += 9;
        while (cpos < input.length) {
          if (input.length-cpos > 1 && input.ptr[cpos] == ']' && input.ptr[cpos+1] == ']') {
            cpos += 2;
            while (cpos < input.length && input.ptr[cpos] <= ' ') ++cpos;
            if (cpos < input.length && input.ptr[cpos] == '>') { ++cpos; break; }
          } else {
            ++cpos;
          }
        }
        continue;
      }
      // start of a tag
      //{ import std.stdio; writeln("ctx: ", input[0..cpos].quote); }
      ////version(nanosvg_debug_styles) { import std.stdio; writeln("ctx: ", input[0..cpos].quote); }
      nsvg__parseContent(input[0..cpos], contentCb, ud);
      input = input[cpos+1..$];
      if (input.length > 2 && input.ptr[0] == '!' && input.ptr[1] == '-' && input.ptr[2] == '-') {
        //{ import std.stdio; writeln("ctx0: ", input.quote); }
        // skip comments
        cpos = 3;
        while (cpos < input.length) {
          if (input.length-cpos > 2 && input.ptr[cpos] == '-' && input.ptr[cpos+1] == '-' && input.ptr[cpos+2] == '>') {
            cpos += 3;
            break;
          }
          ++cpos;
        }
        input = input[cpos..$];
        //{ import std.stdio; writeln("ctx1: ", input.quote); }
      } else {
        state = NSVG_XML_TAG;
      }
      cpos = 0;
    } else if (state == NSVG_XML_TAG && input[cpos] == '>') {
      // start of a content or new tag
      //{ import std.stdio; writeln("tag: ", input[0..cpos].quote); }
      nsvg__parseElement(input[0..cpos], startelCb, endelCb, ud);
      input = input[cpos+1..$];
      cpos = 0;
      state = NSVG_XML_CONTENT;
    } else {
      ++cpos;
    }
  }
}


/* Simple SVG parser. */

enum NSVG_MAX_ATTR = 128;

enum GradientUnits : ubyte {
  User,
  Object,
}

enum NSVG_MAX_DASHES = 8;

enum Units : ubyte {
  user,
  px,
  pt,
  pc,
  mm,
  cm,
  in_,
  percent,
  em,
  ex,
}

struct Coordinate {
  float value;
  Units units;
}

struct LinearData {
  Coordinate x1, y1, x2, y2;
}

struct RadialData {
  Coordinate cx, cy, r, fx, fy;
}

struct GradientData {
  char[64] id = 0;
  char[64] ref_ = 0;
  NSVG.PaintType type;
  union {
    LinearData linear;
    RadialData radial;
  }
  NSVG.SpreadType spread;
  GradientUnits units;
  float[6] xform;
  int nstops;
  NSVG.GradientStop* stops;
  GradientData* next;
}

struct Attrib {
  char[64] id = 0;
  float[6] xform;
  uint fillColor;
  uint strokeColor;
  float opacity;
  float fillOpacity;
  float strokeOpacity;
  char[64] fillGradient = 0;
  char[64] strokeGradient = 0;
  float strokeWidth;
  float strokeDashOffset;
  float[NSVG_MAX_DASHES] strokeDashArray;
  int strokeDashCount;
  NSVG.LineJoin strokeLineJoin;
  NSVG.LineCap strokeLineCap;
  float miterLimit;
  NSVG.FillRule fillRule;
  float fontSize;
  uint stopColor;
  float stopOpacity;
  float stopOffset;
  ubyte hasFill;
  ubyte hasStroke;
  ubyte visible;
}

version(nanosvg_crappy_stylesheet_parser) {
struct Style {
  const(char)[] name;
  const(char)[] value;
}
}

struct Parser {
  Attrib[NSVG_MAX_ATTR] attr;
  int attrHead;
  float* stream;
  int nsflts;
  int csflts;
  NSVG.Path* plist;
  NSVG* image;
  GradientData* gradients;
  NSVG.Shape* shapesTail;
  float viewMinx, viewMiny, viewWidth, viewHeight;
  int alignX, alignY, alignType;
  float dpi;
  bool pathFlag;
  bool defsFlag;
  int canvaswdt = -1;
  int canvashgt = -1;
  version(nanosvg_crappy_stylesheet_parser) {
    Style* styles;
    uint styleCount;
    bool inStyle;
  }
}

const(char)[] fromAsciiz (const(char)[] s) {
  //foreach (immutable idx, char ch; s) if (!ch) return s[0..idx];
  //return s;
  if (s.length) {
    import core.stdc.string : memchr;
    if (auto zp = cast(const(char)*)memchr(s.ptr, 0, s.length)) return s[0..cast(usize)(zp-s.ptr)];
  }
  return s;
}

// ////////////////////////////////////////////////////////////////////////// //
// matrix operations made public for the sake of... something.

///
public void nsvg__xformIdentity (float* t) {
  t[0] = 1.0f; t[1] = 0.0f;
  t[2] = 0.0f; t[3] = 1.0f;
  t[4] = 0.0f; t[5] = 0.0f;
}

///
public void nsvg__xformSetTranslation (float* t, in float tx, in float ty) {
  t[0] = 1.0f; t[1] = 0.0f;
  t[2] = 0.0f; t[3] = 1.0f;
  t[4] = tx; t[5] = ty;
}

///
public void nsvg__xformSetScale (float* t, in float sx, in float sy) {
  t[0] = sx; t[1] = 0.0f;
  t[2] = 0.0f; t[3] = sy;
  t[4] = 0.0f; t[5] = 0.0f;
}

///
public void nsvg__xformSetSkewX (float* t, in float a) {
  t[0] = 1.0f; t[1] = 0.0f;
  t[2] = tanf(a); t[3] = 1.0f;
  t[4] = 0.0f; t[5] = 0.0f;
}

///
public void nsvg__xformSetSkewY (float* t, in float a) {
  t[0] = 1.0f; t[1] = tanf(a);
  t[2] = 0.0f; t[3] = 1.0f;
  t[4] = 0.0f; t[5] = 0.0f;
}

///
public void nsvg__xformSetRotation (float* t, in float a) {
  immutable cs = cosf(a), sn = sinf(a);
  t[0] = cs; t[1] = sn;
  t[2] = -sn; t[3] = cs;
  t[4] = 0.0f; t[5] = 0.0f;
}

///
public void nsvg__xformMultiply (float* t, const(float)* s) {
  immutable t0 = t[0]*s[0]+t[1]*s[2];
  immutable t2 = t[2]*s[0]+t[3]*s[2];
  immutable t4 = t[4]*s[0]+t[5]*s[2]+s[4];
  t[1] = t[0]*s[1]+t[1]*s[3];
  t[3] = t[2]*s[1]+t[3]*s[3];
  t[5] = t[4]*s[1]+t[5]*s[3]+s[5];
  t[0] = t0;
  t[2] = t2;
  t[4] = t4;
}

///
public void nsvg__xformInverse (float* inv, const(float)* t) {
  immutable double det = cast(double)t[0]*t[3]-cast(double)t[2]*t[1];
  if (det > -1e-6 && det < 1e-6) {
    nsvg__xformIdentity(inv);
    return;
  }
  immutable double invdet = 1.0/det;
  inv[0] = cast(float)(t[3]*invdet);
  inv[2] = cast(float)(-t[2]*invdet);
  inv[4] = cast(float)((cast(double)t[2]*t[5]-cast(double)t[3]*t[4])*invdet);
  inv[1] = cast(float)(-t[1]*invdet);
  inv[3] = cast(float)(t[0]*invdet);
  inv[5] = cast(float)((cast(double)t[1]*t[4]-cast(double)t[0]*t[5])*invdet);
}

///
public void nsvg__xformPremultiply (float* t, const(float)* s) {
  float[6] s2 = s[0..6];
  //memcpy(s2.ptr, s, float.sizeof*6);
  nsvg__xformMultiply(s2.ptr, t);
  //memcpy(t, s2.ptr, float.sizeof*6);
  t[0..6] = s2[];
}

///
public void nsvg__xformPoint (float* dx, float* dy, in float x, in float y, const(float)* t) {
  if (dx !is null) *dx = x*t[0]+y*t[2]+t[4];
  if (dy !is null) *dy = x*t[1]+y*t[3]+t[5];
}

///
public void nsvg__xformVec (float* dx, float* dy, in float x, in float y, const(float)* t) {
  if (dx !is null) *dx = x*t[0]+y*t[2];
  if (dy !is null) *dy = x*t[1]+y*t[3];
}

///
public enum NSVG_EPSILON = (1e-12);

///
public int nsvg__ptInBounds (const(float)* pt, const(float)* bounds) {
  pragma(inline, true);
  return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
}

///
public double nsvg__evalBezier (double t, double p0, double p1, double p2, double p3) {
  pragma(inline, true);
  double it = 1.0-t;
  return it*it*it*p0+3.0*it*it*t*p1+3.0*it*t*t*p2+t*t*t*p3;
}

///
public void nsvg__curveBounds (float* bounds, const(float)* curve) {
  const float* v0 = &curve[0];
  const float* v1 = &curve[2];
  const float* v2 = &curve[4];
  const float* v3 = &curve[6];

  // Start the bounding box by end points
  bounds[0] = nsvg__minf(v0[0], v3[0]);
  bounds[1] = nsvg__minf(v0[1], v3[1]);
  bounds[2] = nsvg__maxf(v0[0], v3[0]);
  bounds[3] = nsvg__maxf(v0[1], v3[1]);

  // Bezier curve fits inside the convex hull of it's control points.
  // If control points are inside the bounds, we're done.
  if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) return;

  // Add bezier curve inflection points in X and Y.
  double[2] roots = void;
  foreach (int i; 0..2) {
    immutable double a = -3.0*v0[i]+9.0*v1[i]-9.0*v2[i]+3.0*v3[i];
    immutable double b = 6.0*v0[i]-12.0*v1[i]+6.0*v2[i];
    immutable double c = 3.0*v1[i]-3.0*v0[i];
    int count = 0;
    if (fabs(a) < NSVG_EPSILON) {
      if (fabs(b) > NSVG_EPSILON) {
        immutable double t = -c/b;
        if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
      }
    } else {
      immutable double b2ac = b*b-4.0*c*a;
      if (b2ac > NSVG_EPSILON) {
        double t = (-b+sqrt(b2ac))/(2.0*a);
        if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
        t = (-b-sqrt(b2ac))/(2.0*a);
        if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
      }
    }
    foreach (int j; 0..count) {
      immutable double v = nsvg__evalBezier(roots.ptr[j], v0[i], v1[i], v2[i], v3[i]);
      bounds[0+i] = nsvg__minf(bounds[0+i], cast(float)v);
      bounds[2+i] = nsvg__maxf(bounds[2+i], cast(float)v);
    }
  }
}


// ////////////////////////////////////////////////////////////////////////// //
Parser* nsvg__createParser () {
  Parser* p = xalloc!Parser;
  if (p is null) goto error;

  p.image = xalloc!NSVG;
  if (p.image is null) goto error;

  // Init style
  nsvg__xformIdentity(p.attr[0].xform.ptr);
  p.attr[0].id[] = 0;
  p.attr[0].fillColor = NSVG.Paint.rgb(0, 0, 0);
  p.attr[0].strokeColor = NSVG.Paint.rgb(0, 0, 0);
  p.attr[0].opacity = 1;
  p.attr[0].fillOpacity = 1;
  p.attr[0].strokeOpacity = 1;
  p.attr[0].stopOpacity = 1;
  p.attr[0].strokeWidth = 1;
  p.attr[0].strokeLineJoin = NSVG.LineJoin.Miter;
  p.attr[0].strokeLineCap = NSVG.LineCap.Butt;
  p.attr[0].miterLimit = 4;
  p.attr[0].fillRule = NSVG.FillRule.EvenOdd;
  p.attr[0].hasFill = 1;
  p.attr[0].visible = 1;

  return p;

error:
  if (p !is null) {
    xfree(p.image);
    xfree(p);
  }
  return null;
}

void nsvg__deletePaths (NSVG.Path* path) {
  while (path !is null) {
    NSVG.Path* next = path.next;
    xfree(path.stream);
    xfree(path);
    path = next;
  }
}

void nsvg__deletePaint (NSVG.Paint* paint) {
  if (paint.type == NSVG.PaintType.LinearGradient || paint.type == NSVG.PaintType.RadialGradient) xfree(paint.gradient);
}

void nsvg__deleteGradientData (GradientData* grad) {
  GradientData* next;
  while (grad !is null) {
    next = grad.next;
    xfree(grad.stops);
    xfree(grad);
    grad = next;
  }
}

void nsvg__deleteParser (Parser* p) {
  if (p !is null) {
    nsvg__deletePaths(p.plist);
    nsvg__deleteGradientData(p.gradients);
    kill(p.image);
    xfree(p.stream);
    version(nanosvg_crappy_stylesheet_parser) xfree(p.styles);
    xfree(p);
  }
}

void nsvg__resetPath (Parser* p) {
  p.nsflts = 0;
}

void nsvg__addToStream (Parser* p, in float v) {
  if (p.nsflts+1 > p.csflts) {
    import core.stdc.stdlib : realloc;
    p.csflts = (p.csflts == 0 ? 32 : p.csflts < 16384 ? p.csflts*2 : p.csflts+4096); //k8: arbitrary
    p.stream = cast(float*)realloc(p.stream, p.csflts*float.sizeof);
    if (p.stream is null) assert(0, "nanosvg: out of memory");
  }
  p.stream[p.nsflts++] = v;
}

void nsvg__addCommand (Parser* p, NSVG.Command c) {
  nsvg__addToStream(p, cast(float)c);
}

void nsvg__addPoint (Parser* p, in float x, in float y) {
  nsvg__addToStream(p, x);
  nsvg__addToStream(p, y);
}

void nsvg__moveTo (Parser* p, in float x, in float y) {
  // this is always called right after `nsvg__resetPath()`
  if (p.nsflts != 0) assert(0, "internal error in NanoVega.SVG");
  nsvg__addCommand(p, NSVG.Command.MoveTo);
  nsvg__addPoint(p, x, y);
  /*
  if (p.npts > 0) {
    p.pts[(p.npts-1)*2+0] = x;
    p.pts[(p.npts-1)*2+1] = y;
  } else {
    nsvg__addPoint(p, x, y);
  }
  */
}

void nsvg__lineTo (Parser* p, in float x, in float y) {
  if (p.nsflts > 0) {
    version(nanosvg_use_beziers) {
      immutable float px = p.pts[(p.npts-1)*2+0];
      immutable float py = p.pts[(p.npts-1)*2+1];
      immutable float dx = x-px;
      immutable float dy = y-py;
      nsvg__addCommand(NSVG.Command.BezierTo);
      nsvg__addPoint(p, px+dx/3.0f, py+dy/3.0f);
      nsvg__addPoint(p, x-dx/3.0f, y-dy/3.0f);
      nsvg__addPoint(p, x, y);
    } else {
      nsvg__addCommand(p, NSVG.Command.LineTo);
      nsvg__addPoint(p, x, y);
    }
  }
}

void nsvg__cubicBezTo (Parser* p, in float cpx1, in float cpy1, in float cpx2, in float cpy2, in float x, in float y) {
  nsvg__addCommand(p, NSVG.Command.BezierTo);
  nsvg__addPoint(p, cpx1, cpy1);
  nsvg__addPoint(p, cpx2, cpy2);
  nsvg__addPoint(p, x, y);
}

void nsvg__quadBezTo (Parser* p, in float cpx1, in float cpy1, in float x, in float y) {
  nsvg__addCommand(p, NSVG.Command.QuadTo);
  nsvg__addPoint(p, cpx1, cpy1);
  nsvg__addPoint(p, x, y);
}

Attrib* nsvg__getAttr (Parser* p) {
  return p.attr.ptr+p.attrHead;
}

void nsvg__pushAttr (Parser* p) {
  if (p.attrHead < NSVG_MAX_ATTR-1) {
    import core.stdc.string : memmove;
    ++p.attrHead;
    memmove(p.attr.ptr+p.attrHead, p.attr.ptr+(p.attrHead-1), Attrib.sizeof);
  }
}

void nsvg__popAttr (Parser* p) {
  if (p.attrHead > 0) --p.attrHead;
}

float nsvg__actualOrigX (Parser* p) { pragma(inline, true); return p.viewMinx; }
float nsvg__actualOrigY (Parser* p) { pragma(inline, true); return p.viewMiny; }
float nsvg__actualWidth (Parser* p) { pragma(inline, true); return p.viewWidth; }
float nsvg__actualHeight (Parser* p) { pragma(inline, true); return p.viewHeight; }

float nsvg__actualLength (Parser* p) {
  immutable float w = nsvg__actualWidth(p);
  immutable float h = nsvg__actualHeight(p);
  return sqrtf(w*w+h*h)/sqrtf(2.0f);
}

float nsvg__convertToPixels (Parser* p, Coordinate c, float orig, float length) {
  Attrib* attr = nsvg__getAttr(p);
  switch (c.units) {
    case Units.user: return c.value;
    case Units.px: return c.value;
    case Units.pt: return c.value/72.0f*p.dpi;
    case Units.pc: return c.value/6.0f*p.dpi;
    case Units.mm: return c.value/25.4f*p.dpi;
    case Units.cm: return c.value/2.54f*p.dpi;
    case Units.in_: return c.value*p.dpi;
    case Units.em: return c.value*attr.fontSize;
    case Units.ex: return c.value*attr.fontSize*0.52f; // x-height of Helvetica.
    case Units.percent: return orig+c.value/100.0f*length;
    default: return c.value;
  }
  assert(0);
  //return c.value;
}

GradientData* nsvg__findGradientData (Parser* p, const(char)[] id) {
  GradientData* grad = p.gradients;
  id = id.fromAsciiz;
  while (grad !is null) {
    if (grad.id.fromAsciiz == id) return grad;
    grad = grad.next;
  }
  return null;
}

NSVG.Gradient* nsvg__createGradient (Parser* p, const(char)[] id, const(float)* localBounds, NSVG.PaintType* paintType) {
  Attrib* attr = nsvg__getAttr(p);
  GradientData* data = null;
  GradientData* ref_ = null;
  NSVG.GradientStop* stops = null;
  NSVG.Gradient* grad;
  float ox = void, oy = void, sw = void, sh = void;
  int nstops = 0;

  id = id.fromAsciiz;
  data = nsvg__findGradientData(p, id);
  if (data is null) return null;

  // TODO: use ref_ to fill in all unset values too.
  ref_ = data;
  while (ref_ !is null) {
    if (stops is null && ref_.stops !is null) {
      stops = ref_.stops;
      nstops = ref_.nstops;
      break;
    }
    ref_ = nsvg__findGradientData(p, ref_.ref_[]);
  }
  if (stops is null) return null;

  grad = xalloc!(NSVG.Gradient)(NSVG.GradientStop.sizeof*nstops);
  if (grad is null) return null;

  // The shape width and height.
  if (data.units == GradientUnits.Object) {
    ox = localBounds[0];
    oy = localBounds[1];
    sw = localBounds[2]-localBounds[0];
    sh = localBounds[3]-localBounds[1];
  } else {
    ox = nsvg__actualOrigX(p);
    oy = nsvg__actualOrigY(p);
    sw = nsvg__actualWidth(p);
    sh = nsvg__actualHeight(p);
  }
  immutable float sl = sqrtf(sw*sw+sh*sh)/sqrtf(2.0f);

  if (data.type == NSVG.PaintType.LinearGradient) {
    immutable float x1 = nsvg__convertToPixels(p, data.linear.x1, ox, sw);
    immutable float y1 = nsvg__convertToPixels(p, data.linear.y1, oy, sh);
    immutable float x2 = nsvg__convertToPixels(p, data.linear.x2, ox, sw);
    immutable float y2 = nsvg__convertToPixels(p, data.linear.y2, oy, sh);
    // Calculate transform aligned to the line
    immutable float dx = x2-x1;
    immutable float dy = y2-y1;
    grad.xform[0] = dy; grad.xform[1] = -dx;
    grad.xform[2] = dx; grad.xform[3] = dy;
    grad.xform[4] = x1; grad.xform[5] = y1;
  } else {
    immutable float cx = nsvg__convertToPixels(p, data.radial.cx, ox, sw);
    immutable float cy = nsvg__convertToPixels(p, data.radial.cy, oy, sh);
    immutable float fx = nsvg__convertToPixels(p, data.radial.fx, ox, sw);
    immutable float fy = nsvg__convertToPixels(p, data.radial.fy, oy, sh);
    immutable float r = nsvg__convertToPixels(p, data.radial.r, 0, sl);
    // Calculate transform aligned to the circle
    grad.xform[0] = r; grad.xform[1] = 0;
    grad.xform[2] = 0; grad.xform[3] = r;
    grad.xform[4] = cx; grad.xform[5] = cy;
    // fix from https://github.com/memononen/nanosvg/issues/26#issuecomment-278713651
    grad.fx = (fx-cx)/r; // was fx/r;
    grad.fy = (fy-cy)/r; // was fy/r;
  }

  nsvg__xformMultiply(grad.xform.ptr, data.xform.ptr);
  nsvg__xformMultiply(grad.xform.ptr, attr.xform.ptr);

  grad.spread = data.spread;
  //memcpy(grad.stops.ptr, stops, nstops*NSVG.GradientStop.sizeof);
  grad.stops.ptr[0..nstops] = stops[0..nstops];
  grad.nstops = nstops;

  *paintType = data.type;

  return grad;
}

float nsvg__getAverageScale (float* t) {
  float sx = sqrtf(t[0]*t[0]+t[2]*t[2]);
  float sy = sqrtf(t[1]*t[1]+t[3]*t[3]);
  return (sx+sy)*0.5f;
}

void nsvg__quadBounds (float* bounds, const(float)* curve) nothrow @trusted @nogc {
  // cheat: convert quadratic bezier to cubic bezier
  immutable float cx = curve[0];
  immutable float cy = curve[1];
  immutable float x1 = curve[2];
  immutable float y1 = curve[3];
  immutable float x2 = curve[4];
  immutable float y2 = curve[5];
  immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
  immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
  immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
  immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
  float[8] cubic = void;
  cubic.ptr[0] = cx;
  cubic.ptr[1] = cy;
  cubic.ptr[2] = cx1;
  cubic.ptr[3] = cy1;
  cubic.ptr[4] = cx2;
  cubic.ptr[5] = cy2;
  cubic.ptr[6] = x2;
  cubic.ptr[7] = y2;
  nsvg__curveBounds(bounds, cubic.ptr);
}

void nsvg__getLocalBounds (float* bounds, NSVG.Shape* shape, const(float)* xform) {
  bool first = true;

  void addPoint (in float x, in float y) nothrow @trusted @nogc {
    if (!first) {
      bounds[0] = nsvg__minf(bounds[0], x);
      bounds[1] = nsvg__minf(bounds[1], y);
      bounds[2] = nsvg__maxf(bounds[2], x);
      bounds[3] = nsvg__maxf(bounds[3], y);
    } else {
      bounds[0] = bounds[2] = x;
      bounds[1] = bounds[3] = y;
      first = false;
    }
  }

  void addRect (in float x0, in float y0, in float x1, in float y1) nothrow @trusted @nogc {
    addPoint(x0, y0);
    addPoint(x1, y0);
    addPoint(x1, y1);
    addPoint(x0, y1);
  }

  float cx = 0, cy = 0;
  for (NSVG.Path* path = shape.paths; path !is null; path = path.next) {
    path.forEachCommand!false(delegate (NSVG.Command cmd, const(float)[] args) nothrow @trusted @nogc {
      import core.stdc.string : memmove;
      assert(args.length <= 6);
      float[8] xpt = void;
      // transform points
      foreach (immutable n; 0..args.length/2) {
        nsvg__xformPoint(&xpt.ptr[n*2+0], &xpt.ptr[n*2+1], args.ptr[n*2+0], args.ptr[n*2+1], xform);
      }
      // add to bounds
      final switch (cmd) {
        case NSVG.Command.MoveTo:
          cx = xpt.ptr[0];
          cy = xpt.ptr[1];
          break;
        case NSVG.Command.LineTo:
          addPoint(cx, cy);
          addPoint(xpt.ptr[0], xpt.ptr[1]);
          cx = xpt.ptr[0];
          cy = xpt.ptr[1];
          break;
        case NSVG.Command.QuadTo:
          memmove(xpt.ptr+2, xpt.ptr, 4); // make room for starting point
          xpt.ptr[0] = cx;
          xpt.ptr[1] = cy;
          float[4] curveBounds = void;
          nsvg__quadBounds(curveBounds.ptr, xpt.ptr);
          addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
          cx = xpt.ptr[4];
          cy = xpt.ptr[5];
          break;
        case NSVG.Command.BezierTo:
          memmove(xpt.ptr+2, xpt.ptr, 6); // make room for starting point
          xpt.ptr[0] = cx;
          xpt.ptr[1] = cy;
          float[4] curveBounds = void;
          nsvg__curveBounds(curveBounds.ptr, xpt.ptr);
          addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
          cx = xpt.ptr[6];
          cy = xpt.ptr[7];
          break;
      }
    });
    /*
    nsvg__xformPoint(&curve.ptr[0], &curve.ptr[1], path.pts[0], path.pts[1], xform);
    for (int i = 0; i < path.npts-1; i += 3) {
      nsvg__xformPoint(&curve.ptr[2], &curve.ptr[3], path.pts[(i+1)*2], path.pts[(i+1)*2+1], xform);
      nsvg__xformPoint(&curve.ptr[4], &curve.ptr[5], path.pts[(i+2)*2], path.pts[(i+2)*2+1], xform);
      nsvg__xformPoint(&curve.ptr[6], &curve.ptr[7], path.pts[(i+3)*2], path.pts[(i+3)*2+1], xform);
      nsvg__curveBounds(curveBounds.ptr, curve.ptr);
      if (first) {
        bounds[0] = curveBounds.ptr[0];
        bounds[1] = curveBounds.ptr[1];
        bounds[2] = curveBounds.ptr[2];
        bounds[3] = curveBounds.ptr[3];
        first = false;
      } else {
        bounds[0] = nsvg__minf(bounds[0], curveBounds.ptr[0]);
        bounds[1] = nsvg__minf(bounds[1], curveBounds.ptr[1]);
        bounds[2] = nsvg__maxf(bounds[2], curveBounds.ptr[2]);
        bounds[3] = nsvg__maxf(bounds[3], curveBounds.ptr[3]);
      }
      curve.ptr[0] = curve.ptr[6];
      curve.ptr[1] = curve.ptr[7];
    }
    */
  }
}

void nsvg__addShape (Parser* p) {
  Attrib* attr = nsvg__getAttr(p);
  float scale = 1.0f;
  NSVG.Shape* shape;
  NSVG.Path* path;
  int i;

  if (p.plist is null) return;

  shape = xalloc!(NSVG.Shape);
  if (shape is null) goto error;
  //memset(shape, 0, NSVG.Shape.sizeof);

  shape.id[] = attr.id[];
  scale = nsvg__getAverageScale(attr.xform.ptr);
  shape.strokeWidth = attr.strokeWidth*scale;
  shape.strokeDashOffset = attr.strokeDashOffset*scale;
  shape.strokeDashCount = cast(char)attr.strokeDashCount;
  for (i = 0; i < attr.strokeDashCount; i++) shape.strokeDashArray[i] = attr.strokeDashArray[i]*scale;
  shape.strokeLineJoin = attr.strokeLineJoin;
  shape.strokeLineCap = attr.strokeLineCap;
  shape.miterLimit = attr.miterLimit;
  shape.fillRule = attr.fillRule;
  shape.opacity = attr.opacity;

  shape.paths = p.plist;
  p.plist = null;

  // Calculate shape bounds
  shape.bounds.ptr[0] = shape.paths.bounds.ptr[0];
  shape.bounds.ptr[1] = shape.paths.bounds.ptr[1];
  shape.bounds.ptr[2] = shape.paths.bounds.ptr[2];
  shape.bounds.ptr[3] = shape.paths.bounds.ptr[3];
  for (path = shape.paths.next; path !is null; path = path.next) {
    shape.bounds.ptr[0] = nsvg__minf(shape.bounds.ptr[0], path.bounds[0]);
    shape.bounds.ptr[1] = nsvg__minf(shape.bounds.ptr[1], path.bounds[1]);
    shape.bounds.ptr[2] = nsvg__maxf(shape.bounds.ptr[2], path.bounds[2]);
    shape.bounds.ptr[3] = nsvg__maxf(shape.bounds.ptr[3], path.bounds[3]);
  }

  // Set fill
  if (attr.hasFill == 0) {
    shape.fill.type = NSVG.PaintType.None;
  } else if (attr.hasFill == 1) {
    shape.fill.type = NSVG.PaintType.Color;
    shape.fill.color = attr.fillColor;
    shape.fill.color |= cast(uint)(attr.fillOpacity*255)<<24;
  } else if (attr.hasFill == 2) {
    float[6] inv;
    float[4] localBounds;
    nsvg__xformInverse(inv.ptr, attr.xform.ptr);
    nsvg__getLocalBounds(localBounds.ptr, shape, inv.ptr);
    shape.fill.gradient = nsvg__createGradient(p, attr.fillGradient[], localBounds.ptr, &shape.fill.type);
    if (shape.fill.gradient is null) shape.fill.type = NSVG.PaintType.None;
  }

  // Set stroke
  if (attr.hasStroke == 0) {
    shape.stroke.type = NSVG.PaintType.None;
  } else if (attr.hasStroke == 1) {
    shape.stroke.type = NSVG.PaintType.Color;
    shape.stroke.color = attr.strokeColor;
    shape.stroke.color |= cast(uint)(attr.strokeOpacity*255)<<24;
  } else if (attr.hasStroke == 2) {
    float[6] inv;
    float[4] localBounds;
    nsvg__xformInverse(inv.ptr, attr.xform.ptr);
    nsvg__getLocalBounds(localBounds.ptr, shape, inv.ptr);
    shape.stroke.gradient = nsvg__createGradient(p, attr.strokeGradient[], localBounds.ptr, &shape.stroke.type);
    if (shape.stroke.gradient is null) shape.stroke.type = NSVG.PaintType.None;
  }

  // Set flags
  shape.flags = (attr.visible ? NSVG.Visible : 0x00);

  // Add to tail
  if (p.image.shapes is null)
    p.image.shapes = shape;
  else
    p.shapesTail.next = shape;

  p.shapesTail = shape;

  return;

error:
  if (shape) xfree(shape);
}

void nsvg__addPath (Parser* p, bool closed) {
  Attrib* attr = nsvg__getAttr(p);

  if (p.nsflts < 4) return;

  if (closed) {
    auto cmd = cast(NSVG.Command)p.stream[0];
    if (cmd != NSVG.Command.MoveTo) assert(0, "NanoVega.SVG: invalid path");
    nsvg__lineTo(p, p.stream[1], p.stream[2]);
  }

  float cx = 0, cy = 0;
  float[4] bounds = void;
  bool first = true;

  NSVG.Path* path = xalloc!(NSVG.Path);
  if (path is null) goto error;
  //memset(path, 0, NSVG.Path.sizeof);

  path.stream = xcalloc!float(p.nsflts);
  if (path.stream is null) goto error;
  path.closed = closed;
  path.nsflts = p.nsflts;

  // transform path and calculate bounds
  void addPoint (in float x, in float y) nothrow @trusted @nogc {
    if (!first) {
      bounds[0] = nsvg__minf(bounds[0], x);
      bounds[1] = nsvg__minf(bounds[1], y);
      bounds[2] = nsvg__maxf(bounds[2], x);
      bounds[3] = nsvg__maxf(bounds[3], y);
    } else {
      bounds[0] = bounds[2] = x;
      bounds[1] = bounds[3] = y;
      first = false;
    }
  }

  void addRect (in float x0, in float y0, in float x1, in float y1) nothrow @trusted @nogc {
    addPoint(x0, y0);
    addPoint(x1, y0);
    addPoint(x1, y1);
    addPoint(x0, y1);
  }

  version(none) {
    foreach (immutable idx, float f; p.stream[0..p.nsflts]) {
      import core.stdc.stdio;
      printf("idx=%u; f=%g\n", cast(uint)idx, cast(double)f);
    }
  }

  for (int i = 0; i+3 <= p.nsflts; ) {
    int argc = 0; // pair of coords
    NSVG.Command cmd = cast(NSVG.Command)p.stream[i];
    final switch (cmd) {
      case NSVG.Command.MoveTo: argc = 1; break;
      case NSVG.Command.LineTo: argc = 1; break;
      case NSVG.Command.QuadTo: argc = 2; break;
      case NSVG.Command.BezierTo: argc = 3; break;
    }
    // copy command
    path.stream[i] = p.stream[i];
    ++i;
    auto starti = i;
    // transform points
    while (argc-- > 0) {
      nsvg__xformPoint(&path.stream[i+0], &path.stream[i+1], p.stream[i+0], p.stream[i+1], attr.xform.ptr);
      i += 2;
    }
    // do bounds
    final switch (cmd) {
      case NSVG.Command.MoveTo:
        cx = path.stream[starti+0];
        cy = path.stream[starti+1];
        break;
      case NSVG.Command.LineTo:
        addPoint(cx, cy);
        cx = path.stream[starti+0];
        cy = path.stream[starti+1];
        addPoint(cx, cy);
        break;
      case NSVG.Command.QuadTo:
        float[6] curve = void;
        curve.ptr[0] = cx;
        curve.ptr[1] = cy;
        curve.ptr[2..6] = path.stream[starti+0..starti+4];
        cx = path.stream[starti+2];
        cy = path.stream[starti+3];
        float[4] curveBounds = void;
        nsvg__quadBounds(curveBounds.ptr, curve.ptr);
        addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
        break;
      case NSVG.Command.BezierTo:
        float[8] curve = void;
        curve.ptr[0] = cx;
        curve.ptr[1] = cy;
        curve.ptr[2..8] = path.stream[starti+0..starti+6];
        cx = path.stream[starti+4];
        cy = path.stream[starti+5];
        float[4] curveBounds = void;
        nsvg__curveBounds(curveBounds.ptr, curve.ptr);
        addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
        break;
    }
  }
  path.bounds[0..4] = bounds[0..4];

  path.next = p.plist;
  p.plist = path;

  return;

error:
  if (path !is null) {
    if (path.stream !is null) xfree(path.stream);
    xfree(path);
  }
}

// We roll our own string to float because the std library one uses locale and messes things up.
// special hack: stop at '\0' (actually, it stops on any non-digit, so no special code is required)
float nsvg__atof (const(char)[] s) nothrow @trusted @nogc {
  if (s.length == 0) return 0; // oops

  const(char)* cur = s.ptr;
  auto left = s.length;
  double res = 0.0, sign = 1.0;
  bool hasIntPart = false, hasFracPart = false;

  char peekChar () nothrow @trusted @nogc { pragma(inline, true); return (left > 0 ? *cur : '\x00'); }
  char getChar () nothrow @trusted @nogc { if (left > 0) { --left; return *cur++; } else return '\x00'; }

  // Parse optional sign
  switch (peekChar) {
    case '-': sign = -1; goto case;
    case '+': getChar(); break;
    default: break;
  }

  // Parse integer part
  if (nsvg__isdigit(peekChar)) {
    // Parse digit sequence
    hasIntPart = true;
    while (nsvg__isdigit(peekChar)) res = res*10.0+(getChar()-'0');
  }

  // Parse fractional part.
  if (peekChar == '.') {
    getChar(); // Skip '.'
    if (nsvg__isdigit(peekChar)) {
      // Parse digit sequence
      hasFracPart = true;
      int divisor = 1;
      long num = 0;
      while (nsvg__isdigit(peekChar)) {
        divisor *= 10;
        num = num*10+(getChar()-'0');
      }
      res += cast(double)num/divisor;
    }
  }

  // A valid number should have integer or fractional part.
  if (!hasIntPart && !hasFracPart) return 0;

  // Parse optional exponent
  if (peekChar == 'e' || peekChar == 'E') {
    getChar(); // skip 'E'
    // parse optional sign
    bool epositive = true;
    switch (peekChar) {
      case '-': epositive = false; goto case;
      case '+': getChar(); break;
      default: break;
    }
    int expPart = 0;
    while (nsvg__isdigit(peekChar)) expPart = expPart*10+(getChar()-'0');
    if (epositive) {
      foreach (immutable _; 0..expPart) res *= 10.0;
    } else {
      foreach (immutable _; 0..expPart) res /= 10.0;
    }
  }

  return cast(float)(res*sign);
}

// `it` should be big enough
// returns number of chars eaten
int nsvg__parseNumber (const(char)[] s, char[] it) {
  int i = 0;
  it[] = 0;

  const(char)[] os = s;

  // sign
  if (s.length && (s[0] == '-' || s[0] == '+')) {
    if (it.length-i > 1) it[i++] = s[0];
    s = s[1..$];
  }
  // integer part
  while (s.length && nsvg__isdigit(s[0])) {
    if (it.length-i > 1) it[i++] = s[0];
    s = s[1..$];
  }
  if (s.length && s[0] == '.') {
    // decimal point
    if (it.length-i > 1) it[i++] = s[0];
    s = s[1..$];
    // fraction part
    while (s.length && nsvg__isdigit(s[0])) {
      if (it.length-i > 1) it[i++] = s[0];
      s = s[1..$];
    }
  }
  // exponent
  if (s.length && (s[0] == 'e' || s[0] == 'E')) {
    if (it.length-i > 1) it[i++] = s[0];
    s = s[1..$];
    if (s.length && (s[0] == '-' || s[0] == '+')) {
      if (it.length-i > 1) it[i++] = s[0];
      s = s[1..$];
    }
    while (s.length && nsvg__isdigit(s[0])) {
      if (it.length-i > 1) it[i++] = s[0];
      s = s[1..$];
    }
  }

  return cast(int)(s.ptr-os.ptr);
}

// `it` should be big enough
int nsvg__getNextPathItem (const(char)[] s, char[] it) {
  int res = 0;
  it[] = '\0';
  // skip white spaces and commas
  while (res < s.length && (nsvg__isspace(s[res]) || s[res] == ',')) ++res;
  if (res >= s.length) return cast(int)s.length;
  if (s[res] == '-' || s[res] == '+' || s[res] == '.' || nsvg__isdigit(s[res])) {
    res += nsvg__parseNumber(s[res..$], it);
  } else if (s.length) {
    // Parse command
    it[0] = s[res++];
  }
  return res;
}

uint nsvg__parseColorHex (const(char)[] str) {
  char[12] tmp = 0;
  uint c = 0;
  ubyte r = 0, g = 0, b = 0;
  int n = 0;
  if (str.length) str = str[1..$]; // skip #
  // calculate number of characters
  while (n < str.length && !nsvg__isspace(str[n])) ++n;
  if (n == 3 || n == 6) {
    foreach (char ch; str[0..n]) {
      auto d0 = nsvg__hexdigit(ch);
      if (d0 < 0) break;
      c = c*16+d0;
    }
    if (n == 3) {
      c = (c&0xf)|((c&0xf0)<<4)|((c&0xf00)<<8);
      c |= c<<4;
    }
  }
  r = (c>>16)&0xff;
  g = (c>>8)&0xff;
  b = c&0xff;
  return NSVG.Paint.rgb(r, g, b);
}

uint nsvg__parseColorRGB (const(char)[] str) {
  int r = -1, g = -1, b = -1;
  const(char)[] s1, s2;
  assert(str.length > 4);
  xsscanf(str[4..$], "%d%[%%, \t]%d%[%%, \t]%d", r, s1, g, s2, b);
  if (s1[].xindexOf('%') >= 0) {
    return NSVG.Paint.rgb(cast(ubyte)((r*255)/100), cast(ubyte)((g*255)/100), cast(ubyte)((b*255)/100));
  } else {
    return NSVG.Paint.rgb(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
  }
}

struct NSVGNamedColor {
  string name;
  uint color;
}

static immutable NSVGNamedColor[147] nsvg__colors = [
  NSVGNamedColor("aliceblue", NSVG.Paint.rgb(240, 248, 255)),
  NSVGNamedColor("antiquewhite", NSVG.Paint.rgb(250, 235, 215)),
  NSVGNamedColor("aqua", NSVG.Paint.rgb( 0, 255, 255)),
  NSVGNamedColor("aquamarine", NSVG.Paint.rgb(127, 255, 212)),
  NSVGNamedColor("azure", NSVG.Paint.rgb(240, 255, 255)),
  NSVGNamedColor("beige", NSVG.Paint.rgb(245, 245, 220)),
  NSVGNamedColor("bisque", NSVG.Paint.rgb(255, 228, 196)),
  NSVGNamedColor("black", NSVG.Paint.rgb( 0, 0, 0)), // basic color
  NSVGNamedColor("blanchedalmond", NSVG.Paint.rgb(255, 235, 205)),
  NSVGNamedColor("blue", NSVG.Paint.rgb( 0, 0, 255)), // basic color
  NSVGNamedColor("blueviolet", NSVG.Paint.rgb(138, 43, 226)),
  NSVGNamedColor("brown", NSVG.Paint.rgb(165, 42, 42)),
  NSVGNamedColor("burlywood", NSVG.Paint.rgb(222, 184, 135)),
  NSVGNamedColor("cadetblue", NSVG.Paint.rgb( 95, 158, 160)),
  NSVGNamedColor("chartreuse", NSVG.Paint.rgb(127, 255, 0)),
  NSVGNamedColor("chocolate", NSVG.Paint.rgb(210, 105, 30)),
  NSVGNamedColor("coral", NSVG.Paint.rgb(255, 127, 80)),
  NSVGNamedColor("cornflowerblue", NSVG.Paint.rgb(100, 149, 237)),
  NSVGNamedColor("cornsilk", NSVG.Paint.rgb(255, 248, 220)),
  NSVGNamedColor("crimson", NSVG.Paint.rgb(220, 20, 60)),
  NSVGNamedColor("cyan", NSVG.Paint.rgb( 0, 255, 255)), // basic color
  NSVGNamedColor("darkblue", NSVG.Paint.rgb( 0, 0, 139)),
  NSVGNamedColor("darkcyan", NSVG.Paint.rgb( 0, 139, 139)),
  NSVGNamedColor("darkgoldenrod", NSVG.Paint.rgb(184, 134, 11)),
  NSVGNamedColor("darkgray", NSVG.Paint.rgb(169, 169, 169)),
  NSVGNamedColor("darkgreen", NSVG.Paint.rgb( 0, 100, 0)),
  NSVGNamedColor("darkgrey", NSVG.Paint.rgb(169, 169, 169)),
  NSVGNamedColor("darkkhaki", NSVG.Paint.rgb(189, 183, 107)),
  NSVGNamedColor("darkmagenta", NSVG.Paint.rgb(139, 0, 139)),
  NSVGNamedColor("darkolivegreen", NSVG.Paint.rgb( 85, 107, 47)),
  NSVGNamedColor("darkorange", NSVG.Paint.rgb(255, 140, 0)),
  NSVGNamedColor("darkorchid", NSVG.Paint.rgb(153, 50, 204)),
  NSVGNamedColor("darkred", NSVG.Paint.rgb(139, 0, 0)),
  NSVGNamedColor("darksalmon", NSVG.Paint.rgb(233, 150, 122)),
  NSVGNamedColor("darkseagreen", NSVG.Paint.rgb(143, 188, 143)),
  NSVGNamedColor("darkslateblue", NSVG.Paint.rgb( 72, 61, 139)),
  NSVGNamedColor("darkslategray", NSVG.Paint.rgb( 47, 79, 79)),
  NSVGNamedColor("darkslategrey", NSVG.Paint.rgb( 47, 79, 79)),
  NSVGNamedColor("darkturquoise", NSVG.Paint.rgb( 0, 206, 209)),
  NSVGNamedColor("darkviolet", NSVG.Paint.rgb(148, 0, 211)),
  NSVGNamedColor("deeppink", NSVG.Paint.rgb(255, 20, 147)),
  NSVGNamedColor("deepskyblue", NSVG.Paint.rgb( 0, 191, 255)),
  NSVGNamedColor("dimgray", NSVG.Paint.rgb(105, 105, 105)),
  NSVGNamedColor("dimgrey", NSVG.Paint.rgb(105, 105, 105)),
  NSVGNamedColor("dodgerblue", NSVG.Paint.rgb( 30, 144, 255)),
  NSVGNamedColor("firebrick", NSVG.Paint.rgb(178, 34, 34)),
  NSVGNamedColor("floralwhite", NSVG.Paint.rgb(255, 250, 240)),
  NSVGNamedColor("forestgreen", NSVG.Paint.rgb( 34, 139, 34)),
  NSVGNamedColor("fuchsia", NSVG.Paint.rgb(255, 0, 255)),
  NSVGNamedColor("gainsboro", NSVG.Paint.rgb(220, 220, 220)),
  NSVGNamedColor("ghostwhite", NSVG.Paint.rgb(248, 248, 255)),
  NSVGNamedColor("gold", NSVG.Paint.rgb(255, 215, 0)),
  NSVGNamedColor("goldenrod", NSVG.Paint.rgb(218, 165, 32)),
  NSVGNamedColor("gray", NSVG.Paint.rgb(128, 128, 128)), // basic color
  NSVGNamedColor("green", NSVG.Paint.rgb( 0, 128, 0)), // basic color
  NSVGNamedColor("greenyellow", NSVG.Paint.rgb(173, 255, 47)),
  NSVGNamedColor("grey", NSVG.Paint.rgb(128, 128, 128)), // basic color
  NSVGNamedColor("honeydew", NSVG.Paint.rgb(240, 255, 240)),
  NSVGNamedColor("hotpink", NSVG.Paint.rgb(255, 105, 180)),
  NSVGNamedColor("indianred", NSVG.Paint.rgb(205, 92, 92)),
  NSVGNamedColor("indigo", NSVG.Paint.rgb( 75, 0, 130)),
  NSVGNamedColor("ivory", NSVG.Paint.rgb(255, 255, 240)),
  NSVGNamedColor("khaki", NSVG.Paint.rgb(240, 230, 140)),
  NSVGNamedColor("lavender", NSVG.Paint.rgb(230, 230, 250)),
  NSVGNamedColor("lavenderblush", NSVG.Paint.rgb(255, 240, 245)),
  NSVGNamedColor("lawngreen", NSVG.Paint.rgb(124, 252, 0)),
  NSVGNamedColor("lemonchiffon", NSVG.Paint.rgb(255, 250, 205)),
  NSVGNamedColor("lightblue", NSVG.Paint.rgb(173, 216, 230)),
  NSVGNamedColor("lightcoral", NSVG.Paint.rgb(240, 128, 128)),
  NSVGNamedColor("lightcyan", NSVG.Paint.rgb(224, 255, 255)),
  NSVGNamedColor("lightgoldenrodyellow", NSVG.Paint.rgb(250, 250, 210)),
  NSVGNamedColor("lightgray", NSVG.Paint.rgb(211, 211, 211)),
  NSVGNamedColor("lightgreen", NSVG.Paint.rgb(144, 238, 144)),
  NSVGNamedColor("lightgrey", NSVG.Paint.rgb(211, 211, 211)),
  NSVGNamedColor("lightpink", NSVG.Paint.rgb(255, 182, 193)),
  NSVGNamedColor("lightsalmon", NSVG.Paint.rgb(255, 160, 122)),
  NSVGNamedColor("lightseagreen", NSVG.Paint.rgb( 32, 178, 170)),
  NSVGNamedColor("lightskyblue", NSVG.Paint.rgb(135, 206, 250)),
  NSVGNamedColor("lightslategray", NSVG.Paint.rgb(119, 136, 153)),
  NSVGNamedColor("lightslategrey", NSVG.Paint.rgb(119, 136, 153)),
  NSVGNamedColor("lightsteelblue", NSVG.Paint.rgb(176, 196, 222)),
  NSVGNamedColor("lightyellow", NSVG.Paint.rgb(255, 255, 224)),
  NSVGNamedColor("lime", NSVG.Paint.rgb( 0, 255, 0)),
  NSVGNamedColor("limegreen", NSVG.Paint.rgb( 50, 205, 50)),
  NSVGNamedColor("linen", NSVG.Paint.rgb(250, 240, 230)),
  NSVGNamedColor("magenta", NSVG.Paint.rgb(255, 0, 255)), // basic color
  NSVGNamedColor("maroon", NSVG.Paint.rgb(128, 0, 0)),
  NSVGNamedColor("mediumaquamarine", NSVG.Paint.rgb(102, 205, 170)),
  NSVGNamedColor("mediumblue", NSVG.Paint.rgb( 0, 0, 205)),
  NSVGNamedColor("mediumorchid", NSVG.Paint.rgb(186, 85, 211)),
  NSVGNamedColor("mediumpurple", NSVG.Paint.rgb(147, 112, 219)),
  NSVGNamedColor("mediumseagreen", NSVG.Paint.rgb( 60, 179, 113)),
  NSVGNamedColor("mediumslateblue", NSVG.Paint.rgb(123, 104, 238)),
  NSVGNamedColor("mediumspringgreen", NSVG.Paint.rgb( 0, 250, 154)),
  NSVGNamedColor("mediumturquoise", NSVG.Paint.rgb( 72, 209, 204)),
  NSVGNamedColor("mediumvioletred", NSVG.Paint.rgb(199, 21, 133)),
  NSVGNamedColor("midnightblue", NSVG.Paint.rgb( 25, 25, 112)),
  NSVGNamedColor("mintcream", NSVG.Paint.rgb(245, 255, 250)),
  NSVGNamedColor("mistyrose", NSVG.Paint.rgb(255, 228, 225)),
  NSVGNamedColor("moccasin", NSVG.Paint.rgb(255, 228, 181)),
  NSVGNamedColor("navajowhite", NSVG.Paint.rgb(255, 222, 173)),
  NSVGNamedColor("navy", NSVG.Paint.rgb( 0, 0, 128)),
  NSVGNamedColor("oldlace", NSVG.Paint.rgb(253, 245, 230)),
  NSVGNamedColor("olive", NSVG.Paint.rgb(128, 128, 0)),
  NSVGNamedColor("olivedrab", NSVG.Paint.rgb(107, 142, 35)),
  NSVGNamedColor("orange", NSVG.Paint.rgb(255, 165, 0)),
  NSVGNamedColor("orangered", NSVG.Paint.rgb(255, 69, 0)),
  NSVGNamedColor("orchid", NSVG.Paint.rgb(218, 112, 214)),
  NSVGNamedColor("palegoldenrod", NSVG.Paint.rgb(238, 232, 170)),
  NSVGNamedColor("palegreen", NSVG.Paint.rgb(152, 251, 152)),
  NSVGNamedColor("paleturquoise", NSVG.Paint.rgb(175, 238, 238)),
  NSVGNamedColor("palevioletred", NSVG.Paint.rgb(219, 112, 147)),
  NSVGNamedColor("papayawhip", NSVG.Paint.rgb(255, 239, 213)),
  NSVGNamedColor("peachpuff", NSVG.Paint.rgb(255, 218, 185)),
  NSVGNamedColor("peru", NSVG.Paint.rgb(205, 133, 63)),
  NSVGNamedColor("pink", NSVG.Paint.rgb(255, 192, 203)),
  NSVGNamedColor("plum", NSVG.Paint.rgb(221, 160, 221)),
  NSVGNamedColor("powderblue", NSVG.Paint.rgb(176, 224, 230)),
  NSVGNamedColor("purple", NSVG.Paint.rgb(128, 0, 128)),
  NSVGNamedColor("red", NSVG.Paint.rgb(255, 0, 0)), // basic color
  NSVGNamedColor("rosybrown", NSVG.Paint.rgb(188, 143, 143)),
  NSVGNamedColor("royalblue", NSVG.Paint.rgb( 65, 105, 225)),
  NSVGNamedColor("saddlebrown", NSVG.Paint.rgb(139, 69, 19)),
  NSVGNamedColor("salmon", NSVG.Paint.rgb(250, 128, 114)),
  NSVGNamedColor("sandybrown", NSVG.Paint.rgb(244, 164, 96)),
  NSVGNamedColor("seagreen", NSVG.Paint.rgb( 46, 139, 87)),
  NSVGNamedColor("seashell", NSVG.Paint.rgb(255, 245, 238)),
  NSVGNamedColor("sienna", NSVG.Paint.rgb(160, 82, 45)),
  NSVGNamedColor("silver", NSVG.Paint.rgb(192, 192, 192)),
  NSVGNamedColor("skyblue", NSVG.Paint.rgb(135, 206, 235)),
  NSVGNamedColor("slateblue", NSVG.Paint.rgb(106, 90, 205)),
  NSVGNamedColor("slategray", NSVG.Paint.rgb(112, 128, 144)),
  NSVGNamedColor("slategrey", NSVG.Paint.rgb(112, 128, 144)),
  NSVGNamedColor("snow", NSVG.Paint.rgb(255, 250, 250)),
  NSVGNamedColor("springgreen", NSVG.Paint.rgb( 0, 255, 127)),
  NSVGNamedColor("steelblue", NSVG.Paint.rgb( 70, 130, 180)),
  NSVGNamedColor("tan", NSVG.Paint.rgb(210, 180, 140)),
  NSVGNamedColor("teal", NSVG.Paint.rgb( 0, 128, 128)),
  NSVGNamedColor("thistle", NSVG.Paint.rgb(216, 191, 216)),
  NSVGNamedColor("tomato", NSVG.Paint.rgb(255, 99, 71)),
  NSVGNamedColor("turquoise", NSVG.Paint.rgb( 64, 224, 208)),
  NSVGNamedColor("violet", NSVG.Paint.rgb(238, 130, 238)),
  NSVGNamedColor("wheat", NSVG.Paint.rgb(245, 222, 179)),
  NSVGNamedColor("white", NSVG.Paint.rgb(255, 255, 255)), // basic color
  NSVGNamedColor("whitesmoke", NSVG.Paint.rgb(245, 245, 245)),
  NSVGNamedColor("yellow", NSVG.Paint.rgb(255, 255, 0)), // basic color
  NSVGNamedColor("yellowgreen", NSVG.Paint.rgb(154, 205, 50)),
];

enum nsvg__color_name_maxlen = () {
  int res = 0;
  foreach (const ref known; nsvg__colors) if (res < known.name.length) res = cast(int)known.name.length;
  return res;
}();


// `s0` and `s1` are never empty here
// `s0` is always lowercased
int xstrcmp (const(char)[] s0, const(char)[] s1) {
  /*
  const(char)* sp0 = s0.ptr;
  const(char)* sp1 = s1.ptr;
  foreach (; 0..(s0.length < s1.length ? s0.length : s1.length)) {
    int c1 = cast(int)(*sp1++);
    if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
    if (auto diff = cast(int)(*sp0++)-c1) return diff;
  }
  // equals so far
  if (s0.length < s1.length) return -1;
  if (s0.length > s1.length) return 1;
  return 0;
  */
  import core.stdc.string : memcmp;
  if (auto diff = memcmp(s0.ptr, s1.ptr, (s0.length < s1.length ? s0.length : s1.length))) return diff;
  // equals so far
  if (s0.length < s1.length) return -1;
  if (s0.length > s1.length) return 1;
  return 0;
}


uint nsvg__parseColorName (const(char)[] str) {
  if (str.length == 0 || str.length > nsvg__color_name_maxlen) return NSVG.Paint.rgb(128, 128, 128);
  // check if `str` contains only letters, and convert it to lowercase
  char[nsvg__color_name_maxlen] slow = void;
  foreach (immutable cidx, char ch; str) {
    if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
    if (ch < 'a' || ch > 'z') return NSVG.Paint.rgb(128, 128, 128); // alas
    slow.ptr[cidx] = ch;
  }
  int low = 0;
  int high = cast(int)nsvg__colors.length-1;
  while (low <= high) {
    int med = (low+high)/2;
    assert(med >= 0 && med < nsvg__colors.length);
    int res = xstrcmp(nsvg__colors.ptr[med].name, str);
         if (res < 0) low = med+1;
    else if (res > 0) high = med-1;
    else return nsvg__colors.ptr[med].color;
  }
  return NSVG.Paint.rgb(128, 128, 128);
}

uint nsvg__parseColor (const(char)[] str) {
  while (str.length && str[0] <= ' ') str = str[1..$];
  if (str.length >= 1 && str[0] == '#') return nsvg__parseColorHex(str);
  if (str.length >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') return nsvg__parseColorRGB(str);
  return nsvg__parseColorName(str);
}

float nsvg__parseOpacity (const(char)[] str) {
  float val = 0;
  xsscanf(str, "%f", val);
  if (val < 0.0f) val = 0.0f;
  if (val > 1.0f) val = 1.0f;
  return val;
}

float nsvg__parseMiterLimit (const(char)[] str) {
  float val = 0;
  xsscanf(str, "%f", val);
  if (val < 0.0f) val = 0.0f;
  return val;
}

Units nsvg__parseUnits (const(char)[] units) {
  if (units.length && units.ptr[0] == '%') return Units.percent;
  if (units.length == 2) {
    if (units.ptr[0] == 'p' && units.ptr[1] == 'x') return Units.px;
    if (units.ptr[0] == 'p' && units.ptr[1] == 't') return Units.pt;
    if (units.ptr[0] == 'p' && units.ptr[1] == 'c') return Units.pc;
    if (units.ptr[0] == 'm' && units.ptr[1] == 'm') return Units.mm;
    if (units.ptr[0] == 'c' && units.ptr[1] == 'm') return Units.cm;
    if (units.ptr[0] == 'i' && units.ptr[1] == 'n') return Units.in_;
    if (units.ptr[0] == 'e' && units.ptr[1] == 'm') return Units.em;
    if (units.ptr[0] == 'e' && units.ptr[1] == 'x') return Units.ex;
  }
  return Units.user;
}

Coordinate nsvg__parseCoordinateRaw (const(char)[] str) {
  Coordinate coord = Coordinate(0, Units.user);
  const(char)[] units;
  xsscanf(str, "%f%s", coord.value, units);
  coord.units = nsvg__parseUnits(units);
  return coord;
}

Coordinate nsvg__coord (float v, Units units) {
  Coordinate coord = Coordinate(v, units);
  return coord;
}

float nsvg__parseCoordinate (Parser* p, const(char)[] str, float orig, float length) {
  Coordinate coord = nsvg__parseCoordinateRaw(str);
  return nsvg__convertToPixels(p, coord, orig, length);
}

int nsvg__parseTransformArgs (const(char)[] str, float* args, int maxNa, int* na) {
  usize end, ptr;
  char[65] it = void;

  assert(str.length);
  *na = 0;

  ptr = 0;
  while (ptr < str.length && str[ptr] != '(') ++ptr;
  if (ptr >= str.length) return 1;

  end = ptr;
  while (end < str.length && str[end] != ')') ++end;
  if (end >= str.length) return 1;

  while (ptr < end) {
    if (str[ptr] == '-' || str[ptr] == '+' || str[ptr] == '.' || nsvg__isdigit(str[ptr])) {
      if (*na >= maxNa) return 0;
      ptr += nsvg__parseNumber(str[ptr..end], it[]);
      args[(*na)++] = nsvg__atof(it[]); // `it` is guaranteed to be asciiz, and `nsvg__atof()` will stop
    } else {
      ++ptr;
    }
  }
  return cast(int)end; // fuck off, 64bit
}


int nsvg__parseMatrix (float* xform, const(char)[] str) {
  float[6] t = void;
  int na = 0;
  int len = nsvg__parseTransformArgs(str, t.ptr, 6, &na);
  if (na != 6) return len;
  xform[0..6] = t[];
  return len;
}

int nsvg__parseTranslate (float* xform, const(char)[] str) {
  float[2] args = void;
  float[6] t = void;
  int na = 0;
  int len = nsvg__parseTransformArgs(str, args.ptr, 2, &na);
  if (na == 1) args[1] = 0.0;
  nsvg__xformSetTranslation(t.ptr, args.ptr[0], args.ptr[1]);
  xform[0..6] = t[];
  return len;
}

int nsvg__parseScale (float* xform, const(char)[] str) {
  float[2] args = void;
  int na = 0;
  float[6] t = void;
  int len = nsvg__parseTransformArgs(str, args.ptr, 2, &na);
  if (na == 1) args.ptr[1] = args.ptr[0];
  nsvg__xformSetScale(t.ptr, args.ptr[0], args.ptr[1]);
  xform[0..6] = t[];
  return len;
}

int nsvg__parseSkewX (float* xform, const(char)[] str) {
  float[1] args = void;
  int na = 0;
  float[6] t = void;
  int len = nsvg__parseTransformArgs(str, args.ptr, 1, &na);
  nsvg__xformSetSkewX(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
  xform[0..6] = t[];
  return len;
}

int nsvg__parseSkewY (float* xform, const(char)[] str) {
  float[1] args = void;
  int na = 0;
  float[6] t = void;
  int len = nsvg__parseTransformArgs(str, args.ptr, 1, &na);
  nsvg__xformSetSkewY(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
  xform[0..6] = t[];
  return len;
}

int nsvg__parseRotate (float* xform, const(char)[] str) {
  float[3] args = void;
  int na = 0;
  float[6] m = void;
  float[6] t = void;
  int len = nsvg__parseTransformArgs(str, args.ptr, 3, &na);
  if (na == 1) args.ptr[1] = args.ptr[2] = 0.0f;
  nsvg__xformIdentity(m.ptr);

  if (na > 1) {
    nsvg__xformSetTranslation(t.ptr, -args.ptr[1], -args.ptr[2]);
    nsvg__xformMultiply(m.ptr, t.ptr);
  }

  nsvg__xformSetRotation(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
  nsvg__xformMultiply(m.ptr, t.ptr);

  if (na > 1) {
    nsvg__xformSetTranslation(t.ptr, args.ptr[1], args.ptr[2]);
    nsvg__xformMultiply(m.ptr, t.ptr);
  }

  xform[0..6] = m[];

  return len;
}

bool startsWith (const(char)[] str, const(char)[] sw) {
  pragma(inline, true);
  return (sw.length <= str.length && str[0..sw.length] == sw[]);
}

void nsvg__parseTransform (float* xform, const(char)[] str) {
  float[6] t = void;
  nsvg__xformIdentity(xform);
  while (str.length) {
    int len;
         if (startsWith(str, "matrix")) len = nsvg__parseMatrix(t.ptr, str);
    else if (startsWith(str, "translate")) len = nsvg__parseTranslate(t.ptr, str);
    else if (startsWith(str, "scale")) len = nsvg__parseScale(t.ptr, str);
    else if (startsWith(str, "rotate")) len = nsvg__parseRotate(t.ptr, str);
    else if (startsWith(str, "skewX")) len = nsvg__parseSkewX(t.ptr, str);
    else if (startsWith(str, "skewY")) len = nsvg__parseSkewY(t.ptr, str);
    else { str = str[1..$]; continue; }
    str = str[len..$];
    nsvg__xformPremultiply(xform, t.ptr);
  }
}

// `id` should be prealloced
void nsvg__parseUrl (char[] id, const(char)[] str) {
  int i = 0;
  if (str.length >= 4) {
    str = str[4..$]; // "url(";
    if (str.length && str[0] == '#') str = str[1..$];
    while (str.length && str[0] != ')') {
      if (id.length-i > 1) id[i++] = str[0];
      str = str[1..$];
    }
  }
  if (id.length-i > 0) id[i] = '\0';
}

NSVG.LineCap nsvg__parseLineCap (const(char)[] str) {
  if (str == "butt") return NSVG.LineCap.Butt;
  if (str == "round") return NSVG.LineCap.Round;
  if (str == "square") return NSVG.LineCap.Square;
  // TODO: handle inherit.
  return NSVG.LineCap.Butt;
}

NSVG.LineJoin nsvg__parseLineJoin (const(char)[] str) {
  if (str == "miter") return NSVG.LineJoin.Miter;
  if (str == "round") return NSVG.LineJoin.Round;
  if (str == "bevel") return NSVG.LineJoin.Bevel;
  // TODO: handle inherit.
  return NSVG.LineJoin.Miter;
}

NSVG.FillRule nsvg__parseFillRule (const(char)[] str) {
  if (str == "nonzero") return NSVG.FillRule.NonZero;
  if (str == "evenodd") return NSVG.FillRule.EvenOdd;
  // TODO: handle inherit.
  return NSVG.FillRule.EvenOdd;
}


int nsvg__parseStrokeDashArray (Parser* p, const(char)[] str, float* strokeDashArray) {
  char[65] item = 0;
  int count = 0;
  float sum = 0.0f;

  int nsvg__getNextDashItem () {
    int n = 0;
    item[] = '\0';
    // skip white spaces and commas
    while (str.length && (nsvg__isspace(str[0]) || str[0] == ',')) str = str[1..$];
    // advance until whitespace, comma or end
    while (str.length && (!nsvg__isspace(str[0]) && str[0] != ',')) {
      if (item.length-n > 1) item[n++] = str[0];
      str = str[1..$];
    }
    return n;
  }

  // Handle "none"
  if (!str.length || str[0] == 'n') return 0;

  // Parse dashes
  while (str.length) {
    auto len = nsvg__getNextDashItem();
    if (len < 1) break;
    if (count < NSVG_MAX_DASHES) strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item[0..len], 0.0f, nsvg__actualLength(p)));
  }

  foreach (int i; 0..count) sum += strokeDashArray[i];
  if (sum <= 1e-6f) count = 0;

  return count;
}

const(char)[] trimLeft (const(char)[] s, char ech=0) {
  usize pos = 0;
  while (pos < s.length) {
    if (s.ptr[pos] <= ' ') { ++pos; continue; }
    if (ech && s.ptr[pos] == ech) { ++pos; continue; }
    if (s.ptr[pos] == '/' && s.length-pos > 1 && s.ptr[pos+1] == '*') {
      pos += 2;
      while (s.length-pos > 1 && !(s.ptr[pos] == '*' && s.ptr[pos+1] == '/')) ++pos;
      if ((pos += 2) > s.length) pos = s.length;
      continue;
    }
    break;
  }
  return s[pos..$];
}

static const(char)[] trimRight (const(char)[] s, char ech=0) {
  usize pos = 0;
  while (pos < s.length) {
    if (s.ptr[pos] <= ' ' || (ech && s.ptr[pos] == ech)) {
      if (s[pos..$].trimLeft(ech).length == 0) return s[0..pos];
    } else if (s.ptr[pos] == '/' && s.length-pos > 1 && s.ptr[pos+1] == '*') {
      if (s[pos..$].trimLeft(ech).length == 0) return s[0..pos];
    }
    ++pos;
  }
  return s;
}

version(nanosvg_crappy_stylesheet_parser) {
Style* findStyle (Parser* p, char fch, const(char)[] name) {
  if (name.length == 0) return null;
  foreach (ref st; p.styles[0..p.styleCount]) {
    if (st.name.length < 2 || st.name.ptr[0] != fch) continue;
    if (st.name[1..$] == name) return &st;
  }
  return null;
}

void nsvg__parseClassOrId (Parser* p, char lch, const(char)[] str) {
  while (str.length) {
    while (str.length && str.ptr[0] <= ' ') str = str[1..$];
    if (str.length == 0) break;
    usize pos = 1;
    while (pos < str.length && str.ptr[pos] > ' ') ++pos;
    version(nanosvg_debug_styles) { import std.stdio; writeln("class to find: ", lch, str[0..pos].quote); }
    if (auto st = p.findStyle(lch, str[0..pos])) {
      version(nanosvg_debug_styles) { import std.stdio; writeln("class: [", str[0..pos], "]; value: ", st.value.quote); }
      nsvg__parseStyle(p, st.value);
    }
    str = str[pos..$];
  }
}
}

bool nsvg__parseAttr (Parser* p, const(char)[] name, const(char)[] value) {
  float[6] xform = void;
  Attrib* attr = nsvg__getAttr(p);
  if (attr is null) return false; //???

  if (name == "style") {
    nsvg__parseStyle(p, value);
  } else if (name == "display") {
    if (value == "none") attr.visible = 0;
    // Don't reset .visible on display:inline, one display:none hides the whole subtree
  } else if (name == "fill") {
    if (value == "none") {
      attr.hasFill = 0;
    } else if (startsWith(value, "url(")) {
      attr.hasFill = 2;
      nsvg__parseUrl(attr.fillGradient[], value);
    } else {
      attr.hasFill = 1;
      attr.fillColor = nsvg__parseColor(value);
    }
  } else if (name == "opacity") {
    attr.opacity = nsvg__parseOpacity(value);
  } else if (name == "fill-opacity") {
    attr.fillOpacity = nsvg__parseOpacity(value);
  } else if (name == "stroke") {
    if (value == "none") {
      attr.hasStroke = 0;
    } else if (startsWith(value, "url(")) {
      attr.hasStroke = 2;
      nsvg__parseUrl(attr.strokeGradient[], value);
    } else {
      attr.hasStroke = 1;
      attr.strokeColor = nsvg__parseColor(value);
    }
  } else if (name == "stroke-width") {
    attr.strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
  } else if (name == "stroke-dasharray") {
    attr.strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr.strokeDashArray.ptr);
  } else if (name == "stroke-dashoffset") {
    attr.strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
  } else if (name == "stroke-opacity") {
    attr.strokeOpacity = nsvg__parseOpacity(value);
  } else if (name == "stroke-linecap") {
    attr.strokeLineCap = nsvg__parseLineCap(value);
  } else if (name == "stroke-linejoin") {
    attr.strokeLineJoin = nsvg__parseLineJoin(value);
  } else if (name == "stroke-miterlimit") {
    attr.miterLimit = nsvg__parseMiterLimit(value);
  } else if (name == "fill-rule") {
    attr.fillRule = nsvg__parseFillRule(value);
  } else if (name == "font-size") {
    attr.fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
  } else if (name == "transform") {
    nsvg__parseTransform(xform.ptr, value);
    nsvg__xformPremultiply(attr.xform.ptr, xform.ptr);
  } else if (name == "stop-color") {
    attr.stopColor = nsvg__parseColor(value);
  } else if (name == "stop-opacity") {
    attr.stopOpacity = nsvg__parseOpacity(value);
  } else if (name == "offset") {
    attr.stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
  } else if (name == "class") {
    version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '.', value);
  } else if (name == "id") {
    // apply classes here too
    version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '#', value);
    attr.id[] = 0;
    if (value.length > attr.id.length-1) value = value[0..attr.id.length-1];
    attr.id[0..value.length] = value[];
  } else {
    return false;
  }
  return true;
}

bool nsvg__parseNameValue (Parser* p, const(char)[] str) {
  const(char)[] name;

  str = str.trimLeft;
  usize pos = 0;
  while (pos < str.length && str.ptr[pos] != ':') {
    if (str.length-pos > 1 && str.ptr[pos] == '/' && str.ptr[pos+1] == '*') {
      pos += 2;
      while (str.length-pos > 1 && !(str.ptr[pos] == '*' && str.ptr[pos+1] == '/')) ++pos;
      if ((pos += 2) > str.length) pos = str.length;
    } else {
      ++pos;
    }
  }

  name = str[0..pos].trimLeft.trimRight;
  if (name.length == 0) return false;

  str = str[pos+(pos < str.length ? 1 : 0)..$].trimLeft.trimRight(';');

  version(nanosvg_debug_styles) { import std.stdio; writeln("** name=", name.quote, "; value=", str.quote); }

  return nsvg__parseAttr(p, name, str);
}

void nsvg__parseStyle (Parser* p, const(char)[] str) {
  while (str.length) {
    str = str.trimLeft;
    usize pos = 0;
    while (pos < str.length && str[pos] != ';') {
      if (str.length-pos > 1 && str.ptr[pos] == '/' && str.ptr[pos+1] == '*') {
        pos += 2;
        while (str.length-pos > 1 && !(str.ptr[pos] == '*' && str.ptr[pos+1] == '/')) ++pos;
        if ((pos += 2) > str.length) pos = str.length;
      } else {
        ++pos;
      }
    }
    const(char)[] val = trimRight(str[0..pos]);
    version(nanosvg_debug_styles) { import std.stdio; writeln("style: ", val.quote); }
    str = str[pos+(pos < str.length ? 1 : 0)..$];
    if (val.length > 0) nsvg__parseNameValue(p, val);
  }
}

void nsvg__parseAttribs (Parser* p, AttrList attr) {
  for (usize i = 0; attr.length-i >= 2; i += 2) {
         if (attr[i] == "style") nsvg__parseStyle(p, attr[i+1]);
    else if (attr[i] == "class") { version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '.', attr[i+1]); }
    else nsvg__parseAttr(p, attr[i], attr[i+1]);
  }
}

int nsvg__getArgsPerElement (char cmd) {
  switch (cmd) {
    case 'v': case 'V':
    case 'h': case 'H':
      return 1;
    case 'm': case 'M':
    case 'l': case 'L':
    case 't': case 'T':
      return 2;
    case 'q': case 'Q':
    case 's': case 'S':
      return 4;
    case 'c': case 'C':
      return 6;
    case 'a': case 'A':
      return 7;
    default:
  }
  return 0;
}

void nsvg__pathMoveTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathMoveTo: args=", args[0..2]); }
  if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; }
  nsvg__moveTo(p, *cpx, *cpy);
}

void nsvg__pathLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathLineTo: args=", args[0..2]); }
  if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; }
  nsvg__lineTo(p, *cpx, *cpy);
}

void nsvg__pathHLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathHLineTo: args=", args[0..1]); }
  if (rel) *cpx += args[0]; else *cpx = args[0];
  nsvg__lineTo(p, *cpx, *cpy);
}

void nsvg__pathVLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathVLineTo: args=", args[0..1]); }
  if (rel) *cpy += args[0]; else *cpy = args[0];
  nsvg__lineTo(p, *cpx, *cpy);
}

void nsvg__pathCubicBezTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathCubicBezTo: args=", args[0..6]); }
  float cx1 = args[0];
  float cy1 = args[1];
  float cx2 = args[2];
  float cy2 = args[3];
  float x2 = args[4];
  float y2 = args[5];

  if (rel) {
    cx1 += *cpx;
    cy1 += *cpy;
    cx2 += *cpx;
    cy2 += *cpy;
    x2 += *cpx;
    y2 += *cpy;
  }

  nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);

  *cpx2 = cx2;
  *cpy2 = cy2;
  *cpx = x2;
  *cpy = y2;
}

void nsvg__pathCubicBezShortTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathCubicBezShortTo: args=", args[0..4]); }

  float cx2 = args[0];
  float cy2 = args[1];
  float x2 = args[2];
  float y2 = args[3];
  immutable float x1 = *cpx;
  immutable float y1 = *cpy;

  if (rel) {
    cx2 += *cpx;
    cy2 += *cpy;
    x2 += *cpx;
    y2 += *cpy;
  }

  immutable float cx1 = 2*x1-*cpx2;
  immutable float cy1 = 2*y1-*cpy2;

  nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);

  *cpx2 = cx2;
  *cpy2 = cy2;
  *cpx = x2;
  *cpy = y2;
}

void nsvg__pathQuadBezTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathQuadBezTo: args=", args[0..4]); }

  float cx = args[0];
  float cy = args[1];
  float x2 = args[2];
  float y2 = args[3];
  immutable float x1 = *cpx;
  immutable float y1 = *cpy;

  if (rel) {
    cx += *cpx;
    cy += *cpy;
    x2 += *cpx;
    y2 += *cpy;
  }

  version(nanosvg_only_cubic_beziers) {
    // convert to cubic bezier
    immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
    immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
    immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
    immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
  } else {
    nsvg__quadBezTo(p, cx, cy, x2, y2);
  }

  *cpx2 = cx;
  *cpy2 = cy;
  *cpx = x2;
  *cpy = y2;
}

void nsvg__pathQuadBezShortTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
  debug(nanosvg) { import std.stdio; writeln("nsvg__pathQuadBezShortTo: args=", args[0..2]); }

  float x2 = args[0];
  float y2 = args[1];
  immutable float x1 = *cpx;
  immutable float y1 = *cpy;

  if (rel) {
    x2 += *cpx;
    y2 += *cpy;
  }

  immutable float cx = 2*x1-*cpx2;
  immutable float cy = 2*y1-*cpy2;

  version(nanosvg_only_cubic_beziers) {
    // convert to cubic bezier
    immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
    immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
    immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
    immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
  } else {
    nsvg__quadBezTo(p, cx, cy, x2, y2);
  }

  *cpx2 = cx;
  *cpy2 = cy;
  *cpx = x2;
  *cpy = y2;
}

float nsvg__sqr (in float x) pure nothrow @safe @nogc { pragma(inline, true); return x*x; }
float nsvg__vmag (in float x, float y) nothrow @safe @nogc { pragma(inline, true); return sqrtf(x*x+y*y); }

float nsvg__vecrat (float ux, float uy, float vx, float vy) nothrow @safe @nogc {
  pragma(inline, true);
  return (ux*vx+uy*vy)/(nsvg__vmag(ux, uy)*nsvg__vmag(vx, vy));
}

float nsvg__vecang (float ux, float uy, float vx, float vy) nothrow @safe @nogc {
  float r = nsvg__vecrat(ux, uy, vx, vy);
  if (r < -1.0f) r = -1.0f;
  if (r > 1.0f) r = 1.0f;
  return (ux*vy < uy*vx ? -1.0f : 1.0f)*acosf(r);
}

void nsvg__pathArcTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
  // ported from canvg (https://code.google.com/p/canvg/)
  float rx = fabsf(args[0]); // y radius
  float ry = fabsf(args[1]); // x radius
  immutable float rotx = args[2]/180.0f*NSVG_PI; // x rotation engle
  immutable float fa = (fabsf(args[3]) > 1e-6 ? 1 : 0); // large arc
  immutable float fs = (fabsf(args[4]) > 1e-6 ? 1 : 0); // sweep direction
  immutable float x1 = *cpx; // start point
  immutable float y1 = *cpy;

  // end point
  float x2 = args[5];
  float y2 = args[6];

  if (rel) { x2 += *cpx; y2 += *cpy; }

  float dx = x1-x2;
  float dy = y1-y2;
  immutable float d0 = sqrtf(dx*dx+dy*dy);
  if (d0 < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
    // the arc degenerates to a line
    nsvg__lineTo(p, x2, y2);
    *cpx = x2;
    *cpy = y2;
    return;
  }

  immutable float sinrx = sinf(rotx);
  immutable float cosrx = cosf(rotx);

  // convert to center point parameterization
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  // 1) Compute x1', y1'
  immutable float x1p = cosrx*dx/2.0f+sinrx*dy/2.0f;
  immutable float y1p = -sinrx*dx/2.0f+cosrx*dy/2.0f;
  immutable float d1 = nsvg__sqr(x1p)/nsvg__sqr(rx)+nsvg__sqr(y1p)/nsvg__sqr(ry);
  if (d1 > 1) {
    immutable float d2 = sqrtf(d1);
    rx *= d2;
    ry *= d2;
  }
  // 2) Compute cx', cy'
  float s = 0.0f;
  float sa = nsvg__sqr(rx)*nsvg__sqr(ry)-nsvg__sqr(rx)*nsvg__sqr(y1p)-nsvg__sqr(ry)*nsvg__sqr(x1p);
  immutable float sb = nsvg__sqr(rx)*nsvg__sqr(y1p)+nsvg__sqr(ry)*nsvg__sqr(x1p);
  if (sa < 0.0f) sa = 0.0f;
  if (sb > 0.0f) s = sqrtf(sa/sb);
  if (fa == fs) s = -s;
  immutable float cxp = s*rx*y1p/ry;
  immutable float cyp = s*-ry*x1p/rx;

  // 3) Compute cx,cy from cx',cy'
  immutable float cx = (x1+x2)/2.0f+cosrx*cxp-sinrx*cyp;
  immutable float cy = (y1+y2)/2.0f+sinrx*cxp+cosrx*cyp;

  // 4) Calculate theta1, and delta theta.
  immutable float ux = (x1p-cxp)/rx;
  immutable float uy = (y1p-cyp)/ry;
  immutable float vx = (-x1p-cxp)/rx;
  immutable float vy = (-y1p-cyp)/ry;
  immutable float a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle
  float da = nsvg__vecang(ux, uy, vx, vy); // Delta angle

       if (fs == 0 && da > 0) da -= 2*NSVG_PI;
  else if (fs == 1 && da < 0) da += 2*NSVG_PI;

  float[6] t = void;
  // approximate the arc using cubic spline segments
  t.ptr[0] = cosrx; t.ptr[1] = sinrx;
  t.ptr[2] = -sinrx; t.ptr[3] = cosrx;
  t.ptr[4] = cx; t.ptr[5] = cy;

  // split arc into max 90 degree segments
  // the loop assumes an iteration per end point (including start and end), this +1
  immutable ndivs = cast(int)(fabsf(da)/(NSVG_PI*0.5f)+1.0f);
  immutable float hda = (da/cast(float)ndivs)/2.0f;
  float kappa = fabsf(4.0f/3.0f*(1.0f-cosf(hda))/sinf(hda));
  if (da < 0.0f) kappa = -kappa;

  immutable float ndivsf = cast(float)ndivs;
  float px = 0, py = 0, ptanx = 0, ptany = 0;
  foreach (int i; 0..ndivs+1) {
    float x = void, y = void, tanx = void, tany = void;
    immutable float a = a1+da*(i/ndivsf);
    immutable float loopdx = cosf(a);
    immutable float loopdy = sinf(a);
    nsvg__xformPoint(&x, &y, loopdx*rx, loopdy*ry, t.ptr); // position
    nsvg__xformVec(&tanx, &tany, -loopdy*rx*kappa, loopdx*ry*kappa, t.ptr); // tangent
    if (i > 0) nsvg__cubicBezTo(p, px+ptanx, py+ptany, x-tanx, y-tany, x, y);
    px = x;
    py = y;
    ptanx = tanx;
    ptany = tany;
  }

  *cpx = x2;
  *cpy = y2;
}

void nsvg__parsePath (Parser* p, AttrList attr) {
  const(char)[] s = null;
  char cmd = '\0';
  float[10] args = void;
  int nargs;
  int rargs = 0;
  float cpx = void, cpy = void, cpx2 = void, cpy2 = void;
  bool closedFlag = false;
  char[65] item = void;

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (attr[i] == "d") {
      s = attr[i+1];
    } else {
      const(char)[][2] tmp;
      tmp[0] = attr[i];
      tmp[1] = attr[i+1];
      nsvg__parseAttribs(p, tmp[]);
    }
  }

  if (s.length) {
    nsvg__resetPath(p);
    cpx = 0;
    cpy = 0;
    cpx2 = 0;
    cpy2 = 0;
    closedFlag = false;
    nargs = 0;

    while (s.length) {
      auto skl = nsvg__getNextPathItem(s, item[]);
      if (skl < s.length) s = s[skl..$]; else s = s[$..$];
      debug(nanosvg) { import std.stdio; writeln(":: ", item.fromAsciiz.quote, " : ", s.quote); }
      if (!item[0]) break;
      if (nsvg__isnum(item[0])) {
        if (nargs < 10) {
          args[nargs++] = nsvg__atof(item[]);
        }
        if (nargs >= rargs) {
          switch (cmd) {
            case 'm': case 'M': // move to
              nsvg__pathMoveTo(p, &cpx, &cpy, args.ptr, (cmd == 'm' ? 1 : 0));
              // Moveto can be followed by multiple coordinate pairs,
              // which should be treated as linetos.
              cmd = (cmd == 'm' ? 'l' : 'L');
              rargs = nsvg__getArgsPerElement(cmd);
              cpx2 = cpx; cpy2 = cpy;
              break;
            case 'l': case 'L': // line to
              nsvg__pathLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'l' ? 1 : 0));
              cpx2 = cpx; cpy2 = cpy;
              break;
            case 'H': case 'h': // horizontal line to
              nsvg__pathHLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'h' ? 1 : 0));
              cpx2 = cpx; cpy2 = cpy;
              break;
            case 'V': case 'v': // vertical line to
              nsvg__pathVLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'v' ? 1 : 0));
              cpx2 = cpx; cpy2 = cpy;
              break;
            case 'C': case 'c': // cubic bezier
              nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 'c' ? 1 : 0));
              break;
            case 'S': case 's': // "short" cubic bezier
              nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 's' ? 1 : 0));
              break;
            case 'Q': case 'q': // quadratic bezier
              nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 'q' ? 1 : 0));
              break;
            case 'T': case 't': // "short" quadratic bezier
              nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, cmd == 't' ? 1 : 0);
              break;
            case 'A': case 'a': // arc
              nsvg__pathArcTo(p, &cpx, &cpy, args.ptr, cmd == 'a' ? 1 : 0);
              cpx2 = cpx; cpy2 = cpy;
              break;
            default:
              if (nargs >= 2) {
                cpx = args[nargs-2];
                cpy = args[nargs-1];
                cpx2 = cpx;
                cpy2 = cpy;
              }
              break;
          }
          nargs = 0;
        }
      } else {
        cmd = item[0];
        rargs = nsvg__getArgsPerElement(cmd);
        if (cmd == 'M' || cmd == 'm') {
          // commit path
          if (p.nsflts > 0) nsvg__addPath(p, closedFlag);
          // start new subpath
          nsvg__resetPath(p);
          closedFlag = false;
          nargs = 0;
        } else if (cmd == 'Z' || cmd == 'z') {
          closedFlag = true;
          // commit path
          if (p.nsflts > 0) {
            // move current point to first point
            if ((cast(NSVG.Command)p.stream[0]) != NSVG.Command.MoveTo) assert(0, "NanoVega.SVG: invalid path");
            cpx = p.stream[1];
            cpy = p.stream[2];
            cpx2 = cpx;
            cpy2 = cpy;
            nsvg__addPath(p, closedFlag);
          }
          // start new subpath
          nsvg__resetPath(p);
          nsvg__moveTo(p, cpx, cpy);
          closedFlag = false;
          nargs = 0;
        }
      }
    }
    // commit path
    if (p.nsflts) nsvg__addPath(p, closedFlag);
  }

  nsvg__addShape(p);
}

void nsvg__parseRect (Parser* p, AttrList attr) {
  float x = 0.0f;
  float y = 0.0f;
  float w = 0.0f;
  float h = 0.0f;
  float rx = -1.0f; // marks not set
  float ry = -1.0f;

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
           if (attr[i] == "x") x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
      else if (attr[i] == "y") y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
      else if (attr[i] == "width") w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p));
      else if (attr[i] == "height") h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p));
      else if (attr[i] == "rx") rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
      else if (attr[i] == "ry") ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
    }
  }

  if (rx < 0.0f && ry > 0.0f) rx = ry;
  if (ry < 0.0f && rx > 0.0f) ry = rx;
  if (rx < 0.0f) rx = 0.0f;
  if (ry < 0.0f) ry = 0.0f;
  if (rx > w/2.0f) rx = w/2.0f;
  if (ry > h/2.0f) ry = h/2.0f;

  if (w != 0.0f && h != 0.0f) {
    nsvg__resetPath(p);

    if (rx < 0.00001f || ry < 0.0001f) {
      nsvg__moveTo(p, x, y);
      nsvg__lineTo(p, x+w, y);
      nsvg__lineTo(p, x+w, y+h);
      nsvg__lineTo(p, x, y+h);
    } else {
      // Rounded rectangle
      nsvg__moveTo(p, x+rx, y);
      nsvg__lineTo(p, x+w-rx, y);
      nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry);
      nsvg__lineTo(p, x+w, y+h-ry);
      nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h);
      nsvg__lineTo(p, x+rx, y+h);
      nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry);
      nsvg__lineTo(p, x, y+ry);
      nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y);
    }

    nsvg__addPath(p, 1);

    nsvg__addShape(p);
  }
}

void nsvg__parseCircle (Parser* p, AttrList attr) {
  float cx = 0.0f;
  float cy = 0.0f;
  float r = 0.0f;

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
           if (attr[i] == "cx") cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
      else if (attr[i] == "cy") cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
      else if (attr[i] == "r") r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p)));
    }
  }

  if (r > 0.0f) {
    nsvg__resetPath(p);

    nsvg__moveTo(p, cx+r, cy);
    nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r);
    nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy);
    nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r);
    nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy);

    nsvg__addPath(p, 1);

    nsvg__addShape(p);
  }
}

void nsvg__parseEllipse (Parser* p, AttrList attr) {
  float cx = 0.0f;
  float cy = 0.0f;
  float rx = 0.0f;
  float ry = 0.0f;

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
           if (attr[i] == "cx") cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
      else if (attr[i] == "cy") cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
      else if (attr[i] == "rx") rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
      else if (attr[i] == "ry") ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
    }
  }

  if (rx > 0.0f && ry > 0.0f) {
    nsvg__resetPath(p);

    nsvg__moveTo(p, cx+rx, cy);
    nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry);
    nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy);
    nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry);
    nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy);

    nsvg__addPath(p, 1);

    nsvg__addShape(p);
  }
}

void nsvg__parseLine (Parser* p, AttrList attr) {
  float x1 = 0.0;
  float y1 = 0.0;
  float x2 = 0.0;
  float y2 = 0.0;

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
           if (attr[i] == "x1") x1 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
      else if (attr[i] == "y1") y1 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
      else if (attr[i] == "x2") x2 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
      else if (attr[i] == "y2") y2 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
    }
  }

  nsvg__resetPath(p);

  nsvg__moveTo(p, x1, y1);
  nsvg__lineTo(p, x2, y2);

  nsvg__addPath(p, 0);

  nsvg__addShape(p);
}

void nsvg__parsePoly (Parser* p, AttrList attr, bool closeFlag) {
  float[2] args = void;
  int nargs, npts = 0;
  char[65] item = 0;

  nsvg__resetPath(p);

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
      if (attr[i] == "points") {
        const(char)[]s = attr[i+1];
        nargs = 0;
        while (s.length) {
          auto skl = nsvg__getNextPathItem(s, item[]);
          if (skl < s.length) s = s[skl..$]; else s = s[$..$];
          args[nargs++] = nsvg__atof(item[]);
          if (nargs >= 2) {
            if (npts == 0) nsvg__moveTo(p, args[0], args[1]); else nsvg__lineTo(p, args[0], args[1]);
            nargs = 0;
            ++npts;
          }
        }
      }
    }
  }

  nsvg__addPath(p, closeFlag);

  nsvg__addShape(p);
}

void nsvg__parseSVG (Parser* p, AttrList attr) {
  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
      if (attr[i] == "width") {
        p.image.width = nsvg__parseCoordinate(p, attr[i+1], 0.0f, p.canvaswdt);
        //{ import core.stdc.stdio; printf("(%d) w=%d [%.*s]\n", p.canvaswdt, cast(int)p.image.width, cast(uint)attr[i+1].length, attr[i+1].ptr); }
      } else if (attr[i] == "height") {
        p.image.height = nsvg__parseCoordinate(p, attr[i+1], 0.0f, p.canvashgt);
      } else if (attr[i] == "viewBox") {
        xsscanf(attr[i+1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", p.viewMinx, p.viewMiny, p.viewWidth, p.viewHeight);
      } else if (attr[i] == "preserveAspectRatio") {
        if (attr[i+1].xindexOf("none") >= 0) {
          // No uniform scaling
          p.alignType = NSVG_ALIGN_NONE;
        } else {
          // Parse X align
               if (attr[i+1].xindexOf("xMin") >= 0) p.alignX = NSVG_ALIGN_MIN;
          else if (attr[i+1].xindexOf("xMid") >= 0) p.alignX = NSVG_ALIGN_MID;
          else if (attr[i+1].xindexOf("xMax") >= 0) p.alignX = NSVG_ALIGN_MAX;
          // Parse X align
               if (attr[i+1].xindexOf("yMin") >= 0) p.alignY = NSVG_ALIGN_MIN;
          else if (attr[i+1].xindexOf("yMid") >= 0) p.alignY = NSVG_ALIGN_MID;
          else if (attr[i+1].xindexOf("yMax") >= 0) p.alignY = NSVG_ALIGN_MAX;
          // Parse meet/slice
          p.alignType = NSVG_ALIGN_MEET;
          if (attr[i+1].xindexOf("slice") >= 0) p.alignType = NSVG_ALIGN_SLICE;
        }
      }
    }
  }
}

void nsvg__parseGradient (Parser* p, AttrList attr, NSVG.PaintType type) {
  GradientData* grad = xalloc!GradientData;
  if (grad is null) return;
  //memset(grad, 0, GradientData.sizeof);
  grad.units = GradientUnits.Object;
  grad.type = type;
  if (grad.type == NSVG.PaintType.LinearGradient) {
    grad.linear.x1 = nsvg__coord(0.0f, Units.percent);
    grad.linear.y1 = nsvg__coord(0.0f, Units.percent);
    grad.linear.x2 = nsvg__coord(100.0f, Units.percent);
    grad.linear.y2 = nsvg__coord(0.0f, Units.percent);
  } else if (grad.type == NSVG.PaintType.RadialGradient) {
    grad.radial.cx = nsvg__coord(50.0f, Units.percent);
    grad.radial.cy = nsvg__coord(50.0f, Units.percent);
    grad.radial.r = nsvg__coord(50.0f, Units.percent);
  }

  nsvg__xformIdentity(grad.xform.ptr);

  for (usize i = 0; attr.length-i >= 2; i += 2) {
    if (attr[i] == "id") {
      grad.id[] = 0;
      const(char)[] s = attr[i+1];
      if (s.length > grad.id.length-1) s = s[0..grad.id.length-1];
      grad.id[0..s.length] = s[];
    } else if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
           if (attr[i] == "gradientUnits") { if (attr[i+1] == "objectBoundingBox") grad.units = GradientUnits.Object; else grad.units = GradientUnits.User; }
      else if (attr[i] == "gradientTransform") { nsvg__parseTransform(grad.xform.ptr, attr[i+1]); }
      else if (attr[i] == "cx") { grad.radial.cx = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "cy") { grad.radial.cy = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "r") { grad.radial.r = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "fx") { grad.radial.fx = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "fy") { grad.radial.fy = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "x1") { grad.linear.x1 = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "y1") { grad.linear.y1 = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "x2") { grad.linear.x2 = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "y2") { grad.linear.y2 = nsvg__parseCoordinateRaw(attr[i+1]); }
      else if (attr[i] == "spreadMethod") {
             if (attr[i+1] == "pad") grad.spread = NSVG.SpreadType.Pad;
        else if (attr[i+1] == "reflect") grad.spread = NSVG.SpreadType.Reflect;
        else if (attr[i+1] == "repeat") grad.spread = NSVG.SpreadType.Repeat;
      } else if (attr[i] == "xlink:href") {
        grad.ref_[] = 0;
        const(char)[] s = attr[i+1];
        if (s.length > 0 && s.ptr[0] == '#') s = s[1..$]; // remove '#'
        if (s.length > grad.ref_.length-1) s = s[0..grad.ref_.length-1];
        grad.ref_[0..s.length] = s[];
      }
    }
  }

  grad.next = p.gradients;
  p.gradients = grad;
}

void nsvg__parseGradientStop (Parser* p, AttrList attr) {
  import core.stdc.stdlib : realloc;

  Attrib* curAttr = nsvg__getAttr(p);
  GradientData* grad;
  NSVG.GradientStop* stop;
  int idx;

  curAttr.stopOffset = 0;
  curAttr.stopColor = 0;
  curAttr.stopOpacity = 1.0f;

  for (usize i = 0; attr.length-i >= 2; i += 2) nsvg__parseAttr(p, attr[i], attr[i+1]);

  // Add stop to the last gradient.
  grad = p.gradients;
  if (grad is null) return;

  ++grad.nstops;
  grad.stops = cast(NSVG.GradientStop*)realloc(grad.stops, NSVG.GradientStop.sizeof*grad.nstops+256);
  if (grad.stops is null) assert(0, "nanosvg: out of memory");

  // Insert
  idx = grad.nstops-1;
  foreach (int i; 0..grad.nstops-1) {
    if (curAttr.stopOffset < grad.stops[i].offset) {
      idx = i;
      break;
    }
  }
  if (idx != grad.nstops-1) {
    for (int i = grad.nstops-1; i > idx; --i) grad.stops[i] = grad.stops[i-1];
  }

  stop = grad.stops+idx;
  stop.color = curAttr.stopColor;
  stop.color |= cast(uint)(curAttr.stopOpacity*255)<<24;
  stop.offset = curAttr.stopOffset;
}

void nsvg__startElement (void* ud, const(char)[] el, AttrList attr) {
  Parser* p = cast(Parser*)ud;

  version(nanosvg_debug_styles) { import std.stdio; writeln("tagB: ", el.quote); }
  version(nanosvg_crappy_stylesheet_parser) { p.inStyle = (el == "style"); }

  if (p.defsFlag) {
    // Skip everything but gradients in defs
    if (el == "linearGradient") {
      nsvg__parseGradient(p, attr, NSVG.PaintType.LinearGradient);
    } else if (el == "radialGradient") {
      nsvg__parseGradient(p, attr, NSVG.PaintType.RadialGradient);
    } else if (el == "stop") {
      nsvg__parseGradientStop(p, attr);
    }
    return;
  }

  if (el == "g") {
    nsvg__pushAttr(p);
    nsvg__parseAttribs(p, attr);
  } else if (el == "path") {
    if (p.pathFlag) return; // do not allow nested paths
    p.pathFlag = true;
    nsvg__pushAttr(p);
    nsvg__parsePath(p, attr);
    nsvg__popAttr(p);
  } else if (el == "rect") {
    nsvg__pushAttr(p);
    nsvg__parseRect(p, attr);
    nsvg__popAttr(p);
  } else if (el == "circle") {
    nsvg__pushAttr(p);
    nsvg__parseCircle(p, attr);
    nsvg__popAttr(p);
  } else if (el == "ellipse") {
    nsvg__pushAttr(p);
    nsvg__parseEllipse(p, attr);
    nsvg__popAttr(p);
  } else if (el == "line")  {
    nsvg__pushAttr(p);
    nsvg__parseLine(p, attr);
    nsvg__popAttr(p);
  } else if (el == "polyline")  {
    nsvg__pushAttr(p);
    nsvg__parsePoly(p, attr, 0);
    nsvg__popAttr(p);
  } else if (el == "polygon")  {
    nsvg__pushAttr(p);
    nsvg__parsePoly(p, attr, 1);
    nsvg__popAttr(p);
  } else  if (el == "linearGradient") {
    nsvg__parseGradient(p, attr, NSVG.PaintType.LinearGradient);
  } else if (el == "radialGradient") {
    nsvg__parseGradient(p, attr, NSVG.PaintType.RadialGradient);
  } else if (el == "stop") {
    nsvg__parseGradientStop(p, attr);
  } else if (el == "defs") {
    p.defsFlag = true;
  } else if (el == "svg") {
    nsvg__parseSVG(p, attr);
  }
}

void nsvg__endElement (void* ud, const(char)[] el) {
  version(nanosvg_debug_styles) { import std.stdio; writeln("tagE: ", el.quote); }
  Parser* p = cast(Parser*)ud;
       if (el == "g") nsvg__popAttr(p);
  else if (el == "path") p.pathFlag = false;
  else if (el == "defs") p.defsFlag = false;
  else if (el == "style") { version(nanosvg_crappy_stylesheet_parser) p.inStyle = false; }
}

void nsvg__content (void* ud, const(char)[] s) {
  version(nanosvg_crappy_stylesheet_parser) {
    Parser* p = cast(Parser*)ud;
    if (!p.inStyle) {
      return;
    }
    // cheap hack
    for (;;) {
      while (s.length && s.ptr[0] <= ' ') s = s[1..$];
      if (!s.startsWith("<![CDATA[")) break;
      s = s[9..$];
    }
    for (;;) {
      while (s.length && (s[$-1] <= ' ' || s[$-1] == '>')) s = s[0..$-1];
      if (s.length > 1 && s[$-2..$] == "]]") s = s[0..$-2]; else break;
    }
    version(nanosvg_debug_styles) { import std.stdio; writeln("ctx: ", s.quote); }
    uint tokensAdded = 0;
    while (s.length) {
      if (s.length > 1 && s.ptr[0] == '/' && s.ptr[1] == '*') {
        // comment
        s = s[2..$];
        while (s.length > 1 && !(s.ptr[0] == '*' && s.ptr[1] == '/')) s = s[1..$];
        if (s.length <= 2) break;
        s = s[2..$];
        continue;
      } else if (s.ptr[0] <= ' ') {
        while (s.length && s.ptr[0] <= ' ') s = s[1..$];
        continue;
      }
      //version(nanosvg_debug_styles) { import std.stdio; writeln("::: ", s.quote); }
      if (s.ptr[0] == '{') {
        usize pos = 1;
        while (pos < s.length && s.ptr[pos] != '}') {
          if (s.length-pos > 1 && s.ptr[pos] == '/' && s.ptr[pos+1] == '*') {
            // skip comment
            pos += 2;
            while (s.length-pos > 1 && !(s.ptr[pos] == '*' && s.ptr[pos+1] == '/')) ++pos;
            if (s.length-pos <= 2) { pos = cast(uint)s.length; break; }
            pos += 2;
          } else {
            ++pos;
          }
        }
        version(nanosvg_debug_styles) { import std.stdio; writeln("*** style: ", s[1..pos].quote); }
        if (tokensAdded > 0) {
          foreach (immutable idx; p.styleCount-tokensAdded..p.styleCount) p.styles[idx].value = s[1..pos];
        }
        tokensAdded = 0;
        if (s.length-pos < 1) break;
        s = s[pos+1..$];
      } else {
        usize pos = 0;
        while (pos < s.length && s.ptr[pos] > ' ' && s.ptr[pos] != '{' && s.ptr[pos] != '/') ++pos;
        const(char)[] tk = s[0..pos];
        version(nanosvg_debug_styles) { import std.stdio; writeln("token: ", tk.quote); }
        s = s[pos..$];
        {
          import core.stdc.stdlib : realloc;
          import core.stdc.string : memset;
          p.styles = cast(typeof(p.styles))realloc(p.styles, p.styles[0].sizeof*(p.styleCount+1));
          memset(p.styles+p.styleCount, 0, p.styles[0].sizeof);
          ++p.styleCount;
        }
        p.styles[p.styleCount-1].name = tk;
        ++tokensAdded;
      }
    }
    version(nanosvg_debug_styles) foreach (const ref st; p.styles[0..p.styleCount]) { import std.stdio; writeln("name: ", st.name.quote, "; value: ", st.value.quote); }
  }
}

void nsvg__imageBounds (Parser* p, float* bounds) {
  NSVG.Shape* shape;
  shape = p.image.shapes;
  if (shape is null) {
    bounds[0..4] = 0.0;
    return;
  }
  bounds[0] = shape.bounds.ptr[0];
  bounds[1] = shape.bounds.ptr[1];
  bounds[2] = shape.bounds.ptr[2];
  bounds[3] = shape.bounds.ptr[3];
  for (shape = shape.next; shape !is null; shape = shape.next) {
    bounds[0] = nsvg__minf(bounds[0], shape.bounds.ptr[0]);
    bounds[1] = nsvg__minf(bounds[1], shape.bounds.ptr[1]);
    bounds[2] = nsvg__maxf(bounds[2], shape.bounds.ptr[2]);
    bounds[3] = nsvg__maxf(bounds[3], shape.bounds.ptr[3]);
  }
}

float nsvg__viewAlign (float content, float container, int type) {
  if (type == NSVG_ALIGN_MIN) return 0;
  if (type == NSVG_ALIGN_MAX) return container-content;
  // mid
  return (container-content)*0.5f;
}

void nsvg__scaleGradient (NSVG.Gradient* grad, float tx, float ty, float sx, float sy) {
  float[6] t = void;
  nsvg__xformSetTranslation(t.ptr, tx, ty);
  nsvg__xformMultiply(grad.xform.ptr, t.ptr);

  nsvg__xformSetScale(t.ptr, sx, sy);
  nsvg__xformMultiply(grad.xform.ptr, t.ptr);
}

void nsvg__scaleToViewbox (Parser* p, const(char)[] units) {
  NSVG.Shape* shape;
  NSVG.Path* path;
  float tx = void, ty = void, sx = void, sy = void, us = void, avgs = void;
  float[4] bounds = void;
  float[6] t = void;
  float* pt;

  // Guess image size if not set completely.
  nsvg__imageBounds(p, bounds.ptr);

  if (p.viewWidth == 0) {
    if (p.image.width > 0) {
      p.viewWidth = p.image.width;
    } else {
      p.viewMinx = bounds[0];
      p.viewWidth = bounds[2]-bounds[0];
    }
  }
  if (p.viewHeight == 0) {
    if (p.image.height > 0) {
      p.viewHeight = p.image.height;
    } else {
      p.viewMiny = bounds[1];
      p.viewHeight = bounds[3]-bounds[1];
    }
  }
  if (p.image.width == 0)
    p.image.width = p.viewWidth;
  if (p.image.height == 0)
    p.image.height = p.viewHeight;

  tx = -p.viewMinx;
  ty = -p.viewMiny;
  sx = p.viewWidth > 0 ? p.image.width/p.viewWidth : 0;
  sy = p.viewHeight > 0 ? p.image.height/p.viewHeight : 0;
  // Unit scaling
  us = 1.0f/nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);

  // Fix aspect ratio
  if (p.alignType == NSVG_ALIGN_MEET) {
    // fit whole image into viewbox
    sx = sy = nsvg__minf(sx, sy);
    tx += nsvg__viewAlign(p.viewWidth*sx, p.image.width, p.alignX)/sx;
    ty += nsvg__viewAlign(p.viewHeight*sy, p.image.height, p.alignY)/sy;
  } else if (p.alignType == NSVG_ALIGN_SLICE) {
    // fill whole viewbox with image
    sx = sy = nsvg__maxf(sx, sy);
    tx += nsvg__viewAlign(p.viewWidth*sx, p.image.width, p.alignX)/sx;
    ty += nsvg__viewAlign(p.viewHeight*sy, p.image.height, p.alignY)/sy;
  }

  // Transform
  sx *= us;
  sy *= us;
  avgs = (sx+sy)/2.0f;
  for (shape = p.image.shapes; shape !is null; shape = shape.next) {
    shape.bounds.ptr[0] = (shape.bounds.ptr[0]+tx)*sx;
    shape.bounds.ptr[1] = (shape.bounds.ptr[1]+ty)*sy;
    shape.bounds.ptr[2] = (shape.bounds.ptr[2]+tx)*sx;
    shape.bounds.ptr[3] = (shape.bounds.ptr[3]+ty)*sy;
    for (path = shape.paths; path !is null; path = path.next) {
      path.bounds[0] = (path.bounds[0]+tx)*sx;
      path.bounds[1] = (path.bounds[1]+ty)*sy;
      path.bounds[2] = (path.bounds[2]+tx)*sx;
      path.bounds[3] = (path.bounds[3]+ty)*sy;
      for (int i = 0; i+3 <= path.nsflts; ) {
        int argc = 0; // pair of coords
        NSVG.Command cmd = cast(NSVG.Command)path.stream[i++];
        final switch (cmd) {
          case NSVG.Command.MoveTo: argc = 1; break;
          case NSVG.Command.LineTo: argc = 1; break;
          case NSVG.Command.QuadTo: argc = 2; break;
          case NSVG.Command.BezierTo: argc = 3; break;
        }
        // scale points
        while (argc-- > 0) {
          path.stream[i+0] = (path.stream[i+0]+tx)*sx;
          path.stream[i+1] = (path.stream[i+1]+ty)*sy;
          i += 2;
        }
      }
    }

    if (shape.fill.type == NSVG.PaintType.LinearGradient || shape.fill.type == NSVG.PaintType.RadialGradient) {
      nsvg__scaleGradient(shape.fill.gradient, tx, ty, sx, sy);
      //memcpy(t.ptr, shape.fill.gradient.xform.ptr, float.sizeof*6);
      t.ptr[0..6] = shape.fill.gradient.xform[0..6];
      nsvg__xformInverse(shape.fill.gradient.xform.ptr, t.ptr);
    }
    if (shape.stroke.type == NSVG.PaintType.LinearGradient || shape.stroke.type == NSVG.PaintType.RadialGradient) {
      nsvg__scaleGradient(shape.stroke.gradient, tx, ty, sx, sy);
      //memcpy(t.ptr, shape.stroke.gradient.xform.ptr, float.sizeof*6);
      t.ptr[0..6] = shape.stroke.gradient.xform[0..6];
      nsvg__xformInverse(shape.stroke.gradient.xform.ptr, t.ptr);
    }

    shape.strokeWidth *= avgs;
    shape.strokeDashOffset *= avgs;
    foreach (immutable int i; 0..shape.strokeDashCount) shape.strokeDashArray[i] *= avgs;
  }
}

///
public NSVG* nsvgParse (const(char)[] input, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) {
  Parser* p;
  NSVG* ret = null;

  /*
  static if (NanoSVGHasVFS) {
    if (input.length > 4 && input[0..5] == "NSVG\x00" && units == "px" && dpi == 96) {
      return nsvgUnserialize(wrapStream(MemoryStreamRO(input)));
    }
  }
  */

  p = nsvg__createParser();
  if (p is null) return null;
  p.dpi = dpi;
  p.canvaswdt = (canvaswdt < 1 ? NSVGDefaults.CanvasWidth : canvaswdt);
  p.canvashgt = (canvashgt < 1 ? NSVGDefaults.CanvasHeight : canvashgt);

  nsvg__parseXML(input, &nsvg__startElement, &nsvg__endElement, &nsvg__content, p);

  // Scale to viewBox
  nsvg__scaleToViewbox(p, units);

  ret = p.image;
  p.image = null;

  nsvg__deleteParser(p);

  return ret;
}

///
public void kill (NSVG* image) {
  import core.stdc.string : memset;
  NSVG.Shape* snext, shape;
  if (image is null) return;
  shape = image.shapes;
  while (shape !is null) {
    snext = shape.next;
    nsvg__deletePaths(shape.paths);
    nsvg__deletePaint(&shape.fill);
    nsvg__deletePaint(&shape.stroke);
    xfree(shape);
    shape = snext;
  }
  memset(image, 0, (*image).sizeof);
  xfree(image);
}

} // nothrow @trusted @nogc


///
public NSVG* nsvgParseFromFile (const(char)[] filename, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) nothrow {
  import core.stdc.stdlib : malloc, free;
  enum AddedBytes = 8;

  char* data = null;
  scope(exit) if (data !is null) free(data);

  if (filename.length == 0) return null;

  try {
    static if (NanoSVGHasIVVFS) {
      auto fl = VFile(filename);
      auto size = fl.size;
      if (size > int.max/8 || size < 1) return null;
      data = cast(char*)malloc(cast(uint)size+AddedBytes);
      if (data is null) return null;
      data[0..cast(uint)size+AddedBytes] = 0;
      fl.rawReadExact(data[0..cast(uint)size]);
      fl.close();
    } else {
      import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
      import std.internal.cstring : tempCString;
      auto fl = fopen(filename.tempCString, "rb");
      if (fl is null) return null;
      scope(exit) fclose(fl);
      if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return null;
      auto size = ftell(fl);
      if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return null;
      if (size < 16 || size > int.max/32) return null;
      data = cast(char*)malloc(cast(uint)size+AddedBytes);
      if (data is null) assert(0, "out of memory in NanoVega fontstash");
      data[0..cast(uint)size+AddedBytes] = 0;
      char* dptr = data;
      auto left = cast(uint)size;
      while (left > 0) {
        auto rd = fread(dptr, 1, left, fl);
        if (rd == 0) return null; // unexpected EOF or reading error, it doesn't matter
        dptr += rd;
        left -= rd;
      }
    }
    return nsvgParse(data[0..cast(uint)size], units, dpi, canvaswdt, canvashgt);
  } catch (Exception e) {
    return null;
  }
}


static if (NanoSVGHasIVVFS) {
///
public NSVG* nsvgParseFromFile(ST) (auto ref ST fi, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) nothrow
if (isReadableStream!ST && isSeekableStream!ST && streamHasSize!ST)
{
  import core.stdc.stdlib : malloc, free;

  enum AddedBytes = 8;
  usize size;
  char* data = null;
  scope(exit) if (data is null) free(data);

  try {
    auto sz = fi.size;
    auto pp = fi.tell;
    if (pp >= sz) return null;
    sz -= pp;
    if (sz > 0x3ff_ffff) return null;
    size = cast(usize)sz;
    data = cast(char*)malloc(size+AddedBytes);
    if (data is null) return null;
    scope(exit) free(data);
    data[0..size+AddedBytes] = 0;
    fi.rawReadExact(data[0..size]);
    return nsvgParse(data[0..size], units, dpi, canvaswdt, canvashgt);
  } catch (Exception e) {
    return null;
  }
}
}


// ////////////////////////////////////////////////////////////////////////// //
// rasterizer
private:
nothrow @trusted @nogc {

enum NSVG__SUBSAMPLES = 5;
enum NSVG__FIXSHIFT = 10;
enum NSVG__FIX = 1<<NSVG__FIXSHIFT;
enum NSVG__FIXMASK = NSVG__FIX-1;
enum NSVG__MEMPAGE_SIZE = 1024;

struct NSVGedge {
  float x0 = 0, y0 = 0, x1 = 0, y1 = 0;
  int dir = 0;
  NSVGedge* next;
}

struct NSVGpoint {
  float x = 0, y = 0;
  float dx = 0, dy = 0;
  float len = 0;
  float dmx = 0, dmy = 0;
  ubyte flags = 0;
}

struct NSVGactiveEdge {
  int x = 0, dx = 0;
  float ey = 0;
  int dir = 0;
  NSVGactiveEdge *next;
}

struct NSVGmemPage {
  ubyte[NSVG__MEMPAGE_SIZE] mem;
  int size;
  NSVGmemPage* next;
}

struct NSVGcachedPaint {
  char type;
  char spread;
  float[6] xform = 0;
  uint[256] colors;
}

struct NSVGrasterizerS {
  float px = 0, py = 0;

  float tessTol = 0;
  float distTol = 0;

  NSVGedge* edges;
  int nedges;
  int cedges;

  NSVGpoint* points;
  int npoints;
  int cpoints;

  NSVGpoint* points2;
  int npoints2;
  int cpoints2;

  NSVGactiveEdge* freelist;
  NSVGmemPage* pages;
  NSVGmemPage* curpage;

  ubyte* scanline;
  int cscanline;

  ubyte* bitmap;
  int width, height, stride;
}


///
public NSVGrasterizer nsvgCreateRasterizer () {
  NSVGrasterizer r = xalloc!NSVGrasterizerS;
  if (r is null) goto error;

  r.tessTol = 0.25f;
  r.distTol = 0.01f;

  return r;

error:
  r.kill();
  return null;
}

///
public void kill (NSVGrasterizer r) {
  NSVGmemPage* p;

  if (r is null) return;

  p = r.pages;
  while (p !is null) {
    NSVGmemPage* next = p.next;
    xfree(p);
    p = next;
  }

  if (r.edges) xfree(r.edges);
  if (r.points) xfree(r.points);
  if (r.points2) xfree(r.points2);
  if (r.scanline) xfree(r.scanline);

  xfree(r);
}

NSVGmemPage* nsvg__nextPage (NSVGrasterizer r, NSVGmemPage* cur) {
  NSVGmemPage *newp;

  // If using existing chain, return the next page in chain
  if (cur !is null && cur.next !is null) return cur.next;

  // Alloc new page
  newp = xalloc!NSVGmemPage;
  if (newp is null) return null;

  // Add to linked list
  if (cur !is null)
    cur.next = newp;
  else
    r.pages = newp;

  return newp;
}

void nsvg__resetPool (NSVGrasterizer r) {
  NSVGmemPage* p = r.pages;
  while (p !is null) {
    p.size = 0;
    p = p.next;
  }
  r.curpage = r.pages;
}

ubyte* nsvg__alloc (NSVGrasterizer r, int size) {
  ubyte* buf;
  if (size > NSVG__MEMPAGE_SIZE) return null;
  if (r.curpage is null || r.curpage.size+size > NSVG__MEMPAGE_SIZE) {
    r.curpage = nsvg__nextPage(r, r.curpage);
  }
  buf = &r.curpage.mem[r.curpage.size];
  r.curpage.size += size;
  return buf;
}

int nsvg__ptEquals (float x1, float y1, float x2, float y2, float tol) {
  immutable float dx = x2-x1;
  immutable float dy = y2-y1;
  return dx*dx+dy*dy < tol*tol;
}

void nsvg__addPathPoint (NSVGrasterizer r, float x, float y, int flags) {
  import core.stdc.stdlib : realloc;

  NSVGpoint* pt;

  if (r.npoints > 0) {
    pt = r.points+(r.npoints-1);
    if (nsvg__ptEquals(pt.x, pt.y, x, y, r.distTol)) {
      pt.flags |= flags;
      return;
    }
  }

  if (r.npoints+1 > r.cpoints) {
    r.cpoints = (r.cpoints > 0 ? r.cpoints*2 : 64);
    r.points = cast(NSVGpoint*)realloc(r.points, NSVGpoint.sizeof*r.cpoints+256);
    if (r.points is null) assert(0, "nanosvg: out of memory");
  }

  pt = r.points+r.npoints;
  pt.x = x;
  pt.y = y;
  pt.flags = cast(ubyte)flags;
  ++r.npoints;
}

void nsvg__appendPathPoint (NSVGrasterizer r, NSVGpoint pt) {
  import core.stdc.stdlib : realloc;
  if (r.npoints+1 > r.cpoints) {
    r.cpoints = (r.cpoints > 0 ? r.cpoints*2 : 64);
    r.points = cast(NSVGpoint*)realloc(r.points, NSVGpoint.sizeof*r.cpoints+256);
    if (r.points is null) assert(0, "nanosvg: out of memory");
  }
  r.points[r.npoints] = pt;
  ++r.npoints;
}

void nsvg__duplicatePoints (NSVGrasterizer r) {
  import core.stdc.stdlib : realloc;
  import core.stdc.string : memmove;
  if (r.npoints > r.cpoints2) {
    r.cpoints2 = r.npoints;
    r.points2 = cast(NSVGpoint*)realloc(r.points2, NSVGpoint.sizeof*r.cpoints2+256);
    if (r.points2 is null) assert(0, "nanosvg: out of memory");
  }
  memmove(r.points2, r.points, NSVGpoint.sizeof*r.npoints);
  r.npoints2 = r.npoints;
}

void nsvg__addEdge (NSVGrasterizer r, float x0, float y0, float x1, float y1) {
  NSVGedge* e;

  // Skip horizontal edges
  if (y0 == y1) return;

  if (r.nedges+1 > r.cedges) {
    import core.stdc.stdlib : realloc;
    r.cedges = (r.cedges > 0 ? r.cedges*2 : 64);
    r.edges = cast(NSVGedge*)realloc(r.edges, NSVGedge.sizeof*r.cedges+256);
    if (r.edges is null) assert(0, "nanosvg: out of memory");
  }

  e = &r.edges[r.nedges];
  ++r.nedges;

  if (y0 < y1) {
    e.x0 = x0;
    e.y0 = y0;
    e.x1 = x1;
    e.y1 = y1;
    e.dir = 1;
  } else {
    e.x0 = x1;
    e.y0 = y1;
    e.x1 = x0;
    e.y1 = y0;
    e.dir = -1;
  }
}

float nsvg__normalize (float *x, float* y) {
  immutable float d = sqrtf((*x)*(*x)+(*y)*(*y));
  if (d > 1e-6f) {
    float id = 1.0f/d;
    *x *= id;
    *y *= id;
  }
  return d;
}

void nsvg__flattenCubicBez (NSVGrasterizer r, in float x1, in float y1, in float x2, in float y2, in float x3, in float y3, in float x4, in float y4, in int level, in int type) {
  if (level > 10) return;

  // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
  version(none) {
    if (level == 0) {
      static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
        immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
        return (fabsf(cz*cz) <= 0.01f);
      }
      if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
        //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
        nsvg__flattenCubicBezAFD(r, x1, y1, x2, y2, x3, y3, x4, y4, type);
        return;
      }
    }
  }

  immutable x12 = (x1+x2)*0.5f;
  immutable y12 = (y1+y2)*0.5f;
  immutable x23 = (x2+x3)*0.5f;
  immutable y23 = (y2+y3)*0.5f;
  immutable x34 = (x3+x4)*0.5f;
  immutable y34 = (y3+y4)*0.5f;
  immutable x123 = (x12+x23)*0.5f;
  immutable y123 = (y12+y23)*0.5f;

  immutable dx = x4-x1;
  immutable dy = y4-y1;
  immutable d2 = fabsf(((x2-x4)*dy-(y2-y4)*dx));
  immutable d3 = fabsf(((x3-x4)*dy-(y3-y4)*dx));

  if ((d2+d3)*(d2+d3) < r.tessTol*(dx*dx+dy*dy)) {
    nsvg__addPathPoint(r, x4, y4, type);
    return;
  }

  immutable x234 = (x23+x34)*0.5f;
  immutable y234 = (y23+y34)*0.5f;
  immutable x1234 = (x123+x234)*0.5f;
  immutable y1234 = (y123+y234)*0.5f;

  // "taxicab" / "manhattan" check for flat curves
  if (fabsf(x1+x3-x2-x2)+fabsf(y1+y3-y2-y2)+fabsf(x2+x4-x3-x3)+fabsf(y2+y4-y3-y3) < r.tessTol/4) {
    nsvg__addPathPoint(r, x1234, y1234, type);
    return;
  }

  nsvg__flattenCubicBez(r, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
  nsvg__flattenCubicBez(r, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
}

// Adaptive forward differencing for bezier tesselation.
// See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
// "Adaptive forward differencing for rendering curves and surfaces."
// ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
// original code by Taylor Holliday <taylor@audulus.com>
void nsvg__flattenCubicBezAFD (NSVGrasterizer r, in float x1, in float y1, in float x2, in float y2, in float x3, in float y3, in float x4, in float y4, in int type) nothrow @trusted @nogc {
  enum AFD_ONE = (1<<10);

  // power basis
  immutable float ax = -x1+3*x2-3*x3+x4;
  immutable float ay = -y1+3*y2-3*y3+y4;
  immutable float bx = 3*x1-6*x2+3*x3;
  immutable float by = 3*y1-6*y2+3*y3;
  immutable float cx = -3*x1+3*x2;
  immutable float cy = -3*y1+3*y2;

  // Transform to forward difference basis (stepsize 1)
  float px = x1;
  float py = y1;
  float dx = ax+bx+cx;
  float dy = ay+by+cy;
  float ddx = 6*ax+2*bx;
  float ddy = 6*ay+2*by;
  float dddx = 6*ax;
  float dddy = 6*ay;

  //printf("dx: %f, dy: %f\n", dx, dy);
  //printf("ddx: %f, ddy: %f\n", ddx, ddy);
  //printf("dddx: %f, dddy: %f\n", dddx, dddy);

  int t = 0;
  int dt = AFD_ONE;

  immutable float tol = r.tessTol*4;

  while (t < AFD_ONE) {
    // Flatness measure.
    float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;

    // printf("d: %f, th: %f\n", d, th);

    // Go to higher resolution if we're moving a lot or overshooting the end.
    while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
      // printf("up\n");

      // Apply L to the curve. Increase curve resolution.
      dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
      dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
      ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
      ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
      dddx = (1.0f/8.0f)*dddx;
      dddy = (1.0f/8.0f)*dddy;

      // Half the stepsize.
      dt >>= 1;

      // Recompute d
      d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
    }

    // Go to lower resolution if we're really flat
    // and we aren't going to overshoot the end.
    // XXX: tol/32 is just a guess for when we are too flat.
    while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
      // printf("down\n");

      // Apply L^(-1) to the curve. Decrease curve resolution.
      dx = 2*dx+ddx;
      dy = 2*dy+ddy;
      ddx = 4*ddx+4*dddx;
      ddy = 4*ddy+4*dddy;
      dddx = 8*dddx;
      dddy = 8*dddy;

      // Double the stepsize.
      dt <<= 1;

      // Recompute d
      d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
    }

    // Forward differencing.
    px += dx;
    py += dy;
    dx += ddx;
    dy += ddy;
    ddx += dddx;
    ddy += dddy;

    // Output a point.
    nsvg__addPathPoint(r, px, py, (t > 0 ? type : 0));

    // Advance along the curve.
    t += dt;

    // Ensure we don't overshoot.
    assert(t <= AFD_ONE);
  }
}

void nsvg__flattenShape (NSVGrasterizer r, const(NSVG.Shape)* shape, float scale) {
  for (const(NSVG.Path)* path = shape.paths; path !is null; path = path.next) {
    r.npoints = 0;
    if (path.empty) continue;
    // first point
    float x0, y0;
    path.startPoint(&x0, &y0);
    nsvg__addPathPoint(r, x0*scale, y0*scale, 0);
    // cubic beziers
    path.asCubics(delegate (const(float)[] cubic) {
      assert(cubic.length >= 8);
      nsvg__flattenCubicBez(r,
        cubic.ptr[0]*scale, cubic.ptr[1]*scale,
        cubic.ptr[2]*scale, cubic.ptr[3]*scale,
        cubic.ptr[4]*scale, cubic.ptr[5]*scale,
        cubic.ptr[6]*scale, cubic.ptr[7]*scale,
        0, 0);
    });
    // close path
    nsvg__addPathPoint(r, x0*scale, y0*scale, 0);
    // Flatten path
    /+
    nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, 0);
    for (int i = 0; i < path.npts-1; i += 3) {
      const(float)* p = path.pts+(i*2);
      nsvg__flattenCubicBez(r, p[0]*scale, p[1]*scale, p[2]*scale, p[3]*scale, p[4]*scale, p[5]*scale, p[6]*scale, p[7]*scale, 0, 0);
    }
    // Close path
    nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, 0);
    +/
    // Build edges
    for (int i = 0, j = r.npoints-1; i < r.npoints; j = i++) {
      nsvg__addEdge(r, r.points[j].x, r.points[j].y, r.points[i].x, r.points[i].y);
    }
  }
}

alias PtFlags = ubyte;
enum : ubyte {
  PtFlagsCorner = 0x01,
  PtFlagsBevel = 0x02,
  PtFlagsLeft = 0x04,
}

void nsvg__initClosed (NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
  immutable float w = lineWidth*0.5f;
  float dx = p1.x-p0.x;
  float dy = p1.y-p0.y;
  immutable float len = nsvg__normalize(&dx, &dy);
  immutable float px = p0.x+dx*len*0.5f, py = p0.y+dy*len*0.5f;
  immutable float dlx = dy, dly = -dx;
  immutable float lx = px-dlx*w, ly = py-dly*w;
  immutable float rx = px+dlx*w, ry = py+dly*w;
  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

void nsvg__buttCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int connect) {
  immutable float w = lineWidth*0.5f;
  immutable float px = p.x, py = p.y;
  immutable float dlx = dy, dly = -dx;
  immutable float lx = px-dlx*w, ly = py-dly*w;
  immutable float rx = px+dlx*w, ry = py+dly*w;

  nsvg__addEdge(r, lx, ly, rx, ry);

  if (connect) {
    nsvg__addEdge(r, left.x, left.y, lx, ly);
    nsvg__addEdge(r, rx, ry, right.x, right.y);
  }
  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

void nsvg__squareCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int connect) {
  immutable float w = lineWidth*0.5f;
  immutable float px = p.x-dx*w, py = p.y-dy*w;
  immutable float dlx = dy, dly = -dx;
  immutable float lx = px-dlx*w, ly = py-dly*w;
  immutable float rx = px+dlx*w, ry = py+dly*w;

  nsvg__addEdge(r, lx, ly, rx, ry);

  if (connect) {
    nsvg__addEdge(r, left.x, left.y, lx, ly);
    nsvg__addEdge(r, rx, ry, right.x, right.y);
  }
  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

void nsvg__roundCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int ncap, int connect) {
  immutable float w = lineWidth*0.5f;
  immutable float px = p.x, py = p.y;
  immutable float dlx = dy, dly = -dx;
  float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0;

  foreach (int i; 0..ncap) {
    immutable float a = i/cast(float)(ncap-1)*NSVG_PI;
    immutable float ax = cosf(a)*w, ay = sinf(a)*w;
    immutable float x = px-dlx*ax-dx*ay;
    immutable float y = py-dly*ax-dy*ay;

    if (i > 0) nsvg__addEdge(r, prevx, prevy, x, y);

    prevx = x;
    prevy = y;

    if (i == 0) {
      lx = x;
      ly = y;
    } else if (i == ncap-1) {
      rx = x;
      ry = y;
    }
  }

  if (connect) {
    nsvg__addEdge(r, left.x, left.y, lx, ly);
    nsvg__addEdge(r, rx, ry, right.x, right.y);
  }

  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

void nsvg__bevelJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
  immutable float w = lineWidth*0.5f;
  immutable float dlx0 = p0.dy, dly0 = -p0.dx;
  immutable float dlx1 = p1.dy, dly1 = -p1.dx;
  immutable float lx0 = p1.x-(dlx0*w), ly0 = p1.y-(dly0*w);
  immutable float rx0 = p1.x+(dlx0*w), ry0 = p1.y+(dly0*w);
  immutable float lx1 = p1.x-(dlx1*w), ly1 = p1.y-(dly1*w);
  immutable float rx1 = p1.x+(dlx1*w), ry1 = p1.y+(dly1*w);

  nsvg__addEdge(r, lx0, ly0, left.x, left.y);
  nsvg__addEdge(r, lx1, ly1, lx0, ly0);

  nsvg__addEdge(r, right.x, right.y, rx0, ry0);
  nsvg__addEdge(r, rx0, ry0, rx1, ry1);

  left.x = lx1; left.y = ly1;
  right.x = rx1; right.y = ry1;
}

void nsvg__miterJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
  immutable float w = lineWidth*0.5f;
  immutable float dlx0 = p0.dy, dly0 = -p0.dx;
  immutable float dlx1 = p1.dy, dly1 = -p1.dx;
  float lx0 = void, rx0 = void, lx1 = void, rx1 = void;
  float ly0 = void, ry0 = void, ly1 = void, ry1 = void;

  if (p1.flags&PtFlagsLeft) {
    lx0 = lx1 = p1.x-p1.dmx*w;
    ly0 = ly1 = p1.y-p1.dmy*w;
    nsvg__addEdge(r, lx1, ly1, left.x, left.y);

    rx0 = p1.x+(dlx0*w);
    ry0 = p1.y+(dly0*w);
    rx1 = p1.x+(dlx1*w);
    ry1 = p1.y+(dly1*w);
    nsvg__addEdge(r, right.x, right.y, rx0, ry0);
    nsvg__addEdge(r, rx0, ry0, rx1, ry1);
  } else {
    lx0 = p1.x-(dlx0*w);
    ly0 = p1.y-(dly0*w);
    lx1 = p1.x-(dlx1*w);
    ly1 = p1.y-(dly1*w);
    nsvg__addEdge(r, lx0, ly0, left.x, left.y);
    nsvg__addEdge(r, lx1, ly1, lx0, ly0);

    rx0 = rx1 = p1.x+p1.dmx*w;
    ry0 = ry1 = p1.y+p1.dmy*w;
    nsvg__addEdge(r, right.x, right.y, rx1, ry1);
  }

  left.x = lx1; left.y = ly1;
  right.x = rx1; right.y = ry1;
}

void nsvg__roundJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth, int ncap) {
  int i, n;
  float w = lineWidth*0.5f;
  float dlx0 = p0.dy, dly0 = -p0.dx;
  float dlx1 = p1.dy, dly1 = -p1.dx;
  float a0 = atan2f(dly0, dlx0);
  float a1 = atan2f(dly1, dlx1);
  float da = a1-a0;
  float lx, ly, rx, ry;

  if (da < NSVG_PI) da += NSVG_PI*2;
  if (da > NSVG_PI) da -= NSVG_PI*2;

  n = cast(int)ceilf((fabsf(da)/NSVG_PI)*ncap);
  if (n < 2) n = 2;
  if (n > ncap) n = ncap;

  lx = left.x;
  ly = left.y;
  rx = right.x;
  ry = right.y;

  for (i = 0; i < n; i++) {
    float u = i/cast(float)(n-1);
    float a = a0+u*da;
    float ax = cosf(a)*w, ay = sinf(a)*w;
    float lx1 = p1.x-ax, ly1 = p1.y-ay;
    float rx1 = p1.x+ax, ry1 = p1.y+ay;

    nsvg__addEdge(r, lx1, ly1, lx, ly);
    nsvg__addEdge(r, rx, ry, rx1, ry1);

    lx = lx1; ly = ly1;
    rx = rx1; ry = ry1;
  }

  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

void nsvg__straightJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p1, float lineWidth) {
  float w = lineWidth*0.5f;
  float lx = p1.x-(p1.dmx*w), ly = p1.y-(p1.dmy*w);
  float rx = p1.x+(p1.dmx*w), ry = p1.y+(p1.dmy*w);

  nsvg__addEdge(r, lx, ly, left.x, left.y);
  nsvg__addEdge(r, right.x, right.y, rx, ry);

  left.x = lx; left.y = ly;
  right.x = rx; right.y = ry;
}

int nsvg__curveDivs (float r, float arc, float tol) {
  float da = acosf(r/(r+tol))*2.0f;
  int divs = cast(int)ceilf(arc/da);
  if (divs < 2) divs = 2;
  return divs;
}

void nsvg__expandStroke (NSVGrasterizer r, const(NSVGpoint)* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) {
  int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r.tessTol);  // Calculate divisions per half circle.
  //NSVGpoint left = {0, 0, 0, 0, 0, 0, 0, 0}, right = {0, 0, 0, 0, 0, 0, 0, 0}, firstLeft = {0, 0, 0, 0, 0, 0, 0, 0}, firstRight = {0, 0, 0, 0, 0, 0, 0, 0};
  NSVGpoint left, right, firstLeft, firstRight;
  const(NSVGpoint)* p0, p1;
  int j, s, e;

  // Build stroke edges
  if (closed) {
    // Looping
    p0 = &points[npoints-1];
    p1 = &points[0];
    s = 0;
    e = npoints;
  } else {
    // Add cap
    p0 = &points[0];
    p1 = &points[1];
    s = 1;
    e = npoints-1;
  }

  if (closed) {
    nsvg__initClosed(&left, &right, p0, p1, lineWidth);
    firstLeft = left;
    firstRight = right;
  } else {
    // Add cap
    float dx = p1.x-p0.x;
    float dy = p1.y-p0.y;
    nsvg__normalize(&dx, &dy);
    if (lineCap == NSVG.LineCap.Butt)
      nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
    else if (lineCap == NSVG.LineCap.Square)
      nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
    else if (lineCap == NSVG.LineCap.Round)
      nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);
  }

  for (j = s; j < e; ++j) {
    if (p1.flags&PtFlagsCorner) {
      if (lineJoin == NSVG.LineJoin.Round)
        nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);
      else if (lineJoin == NSVG.LineJoin.Bevel || (p1.flags&PtFlagsBevel))
        nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);
      else
        nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);
    } else {
      nsvg__straightJoin(r, &left, &right, p1, lineWidth);
    }
    p0 = p1++;
  }

  if (closed) {
    // Loop it
    nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);
    nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);
  } else {
    // Add cap
    float dx = p1.x-p0.x;
    float dy = p1.y-p0.y;
    nsvg__normalize(&dx, &dy);
    if (lineCap == NSVG.LineCap.Butt)
      nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
    else if (lineCap == NSVG.LineCap.Square)
      nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
    else if (lineCap == NSVG.LineCap.Round)
      nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);
  }
}

void nsvg__prepareStroke (NSVGrasterizer r, float miterLimit, int lineJoin) {
  int i, j;
  NSVGpoint* p0, p1;

  p0 = r.points+(r.npoints-1);
  p1 = r.points;
  for (i = 0; i < r.npoints; i++) {
    // Calculate segment direction and length
    p0.dx = p1.x-p0.x;
    p0.dy = p1.y-p0.y;
    p0.len = nsvg__normalize(&p0.dx, &p0.dy);
    // Advance
    p0 = p1++;
  }

  // calculate joins
  p0 = r.points+(r.npoints-1);
  p1 = r.points;
  for (j = 0; j < r.npoints; j++) {
    float dlx0, dly0, dlx1, dly1, dmr2, cross;
    dlx0 = p0.dy;
    dly0 = -p0.dx;
    dlx1 = p1.dy;
    dly1 = -p1.dx;
    // Calculate extrusions
    p1.dmx = (dlx0+dlx1)*0.5f;
    p1.dmy = (dly0+dly1)*0.5f;
    dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
    if (dmr2 > 0.000001f) {
      float s2 = 1.0f/dmr2;
      if (s2 > 600.0f) {
        s2 = 600.0f;
      }
      p1.dmx *= s2;
      p1.dmy *= s2;
    }

    // Clear flags, but keep the corner.
    p1.flags = (p1.flags&PtFlagsCorner) ? PtFlagsCorner : 0;

    // Keep track of left turns.
    cross = p1.dx*p0.dy-p0.dx*p1.dy;
    if (cross > 0.0f)
      p1.flags |= PtFlagsLeft;

    // Check to see if the corner needs to be beveled.
    if (p1.flags&PtFlagsCorner) {
      if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NSVG.LineJoin.Bevel || lineJoin == NSVG.LineJoin.Round) {
        p1.flags |= PtFlagsBevel;
      }
    }

    p0 = p1++;
  }
}

void nsvg__flattenShapeStroke (NSVGrasterizer r, const(NSVG.Shape)* shape, float scale) {
  int i, j, closed;
  const(NSVG.Path)* path;
  const(NSVGpoint)* p0, p1;
  float miterLimit = shape.miterLimit;
  int lineJoin = shape.strokeLineJoin;
  int lineCap = shape.strokeLineCap;
  float lineWidth = shape.strokeWidth*scale;

  for (path = shape.paths; path !is null; path = path.next) {
    // Flatten path
    r.npoints = 0;
    if (!path.empty) {
      // first point
      {
        float x0, y0;
        path.startPoint(&x0, &y0);
        nsvg__addPathPoint(r, x0*scale, y0*scale, PtFlagsCorner);
      }
      // cubic beziers
      path.asCubics(delegate (const(float)[] cubic) {
        assert(cubic.length >= 8);
        nsvg__flattenCubicBez(r,
          cubic.ptr[0]*scale, cubic.ptr[1]*scale,
          cubic.ptr[2]*scale, cubic.ptr[3]*scale,
          cubic.ptr[4]*scale, cubic.ptr[5]*scale,
          cubic.ptr[6]*scale, cubic.ptr[7]*scale,
          0, PtFlagsCorner);
      });
    }
    /+
    nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, PtFlagsCorner);
    for (i = 0; i < path.npts-1; i += 3) {
      const(float)* p = &path.pts[i*2];
      nsvg__flattenCubicBez(r, p[0]*scale, p[1]*scale, p[2]*scale, p[3]*scale, p[4]*scale, p[5]*scale, p[6]*scale, p[7]*scale, 0, PtFlagsCorner);
    }
    +/
    if (r.npoints < 2) continue;

    closed = path.closed;

    // If the first and last points are the same, remove the last, mark as closed path.
    p0 = &r.points[r.npoints-1];
    p1 = &r.points[0];
    if (nsvg__ptEquals(p0.x, p0.y, p1.x, p1.y, r.distTol)) {
      r.npoints--;
      p0 = &r.points[r.npoints-1];
      closed = 1;
    }

    if (shape.strokeDashCount > 0) {
      int idash = 0, dashState = 1;
      float totalDist = 0, dashLen, allDashLen, dashOffset;
      NSVGpoint cur;

      if (closed) nsvg__appendPathPoint(r, r.points[0]);

      // Duplicate points . points2.
      nsvg__duplicatePoints(r);

      r.npoints = 0;
      cur = r.points2[0];
      nsvg__appendPathPoint(r, cur);

      // Figure out dash offset.
      allDashLen = 0;
      for (j = 0; j < shape.strokeDashCount; j++) allDashLen += shape.strokeDashArray[j];
      if (shape.strokeDashCount&1) allDashLen *= 2.0f;
      // Find location inside pattern
      dashOffset = fmodf(shape.strokeDashOffset, allDashLen);
      if (dashOffset < 0.0f) dashOffset += allDashLen;

      while (dashOffset > shape.strokeDashArray[idash]) {
        dashOffset -= shape.strokeDashArray[idash];
        idash = (idash+1)%shape.strokeDashCount;
      }
      dashLen = (shape.strokeDashArray[idash]-dashOffset)*scale;

      for (j = 1; j < r.npoints2; ) {
        float dx = r.points2[j].x-cur.x;
        float dy = r.points2[j].y-cur.y;
        float dist = sqrtf(dx*dx+dy*dy);

        if (totalDist+dist > dashLen) {
          // Calculate intermediate point
          float d = (dashLen-totalDist)/dist;
          float x = cur.x+dx*d;
          float y = cur.y+dy*d;
          nsvg__addPathPoint(r, x, y, PtFlagsCorner);

          // Stroke
          if (r.npoints > 1 && dashState) {
            nsvg__prepareStroke(r, miterLimit, lineJoin);
            nsvg__expandStroke(r, r.points, r.npoints, 0, lineJoin, lineCap, lineWidth);
          }
          // Advance dash pattern
          dashState = !dashState;
          idash = (idash+1)%shape.strokeDashCount;
          dashLen = shape.strokeDashArray[idash]*scale;
          // Restart
          cur.x = x;
          cur.y = y;
          cur.flags = PtFlagsCorner;
          totalDist = 0.0f;
          r.npoints = 0;
          nsvg__appendPathPoint(r, cur);
        } else {
          totalDist += dist;
          cur = r.points2[j];
          nsvg__appendPathPoint(r, cur);
          j++;
        }
      }
      // Stroke any leftover path
      if (r.npoints > 1 && dashState) nsvg__expandStroke(r, r.points, r.npoints, 0, lineJoin, lineCap, lineWidth);
    } else {
      nsvg__prepareStroke(r, miterLimit, lineJoin);
      nsvg__expandStroke(r, r.points, r.npoints, closed, lineJoin, lineCap, lineWidth);
    }
  }
}

extern(C) int nsvg__cmpEdge (in void *p, in void *q) nothrow @trusted @nogc {
  NSVGedge* a = cast(NSVGedge*)p;
  NSVGedge* b = cast(NSVGedge*)q;
  if (a.y0 < b.y0) return -1;
  if (a.y0 > b.y0) return  1;
  return 0;
}


static NSVGactiveEdge* nsvg__addActive (NSVGrasterizer r, const(NSVGedge)* e, float startPoint) {
  NSVGactiveEdge* z;

  if (r.freelist !is null) {
    // Restore from freelist.
    z = r.freelist;
    r.freelist = z.next;
  } else {
    // Alloc new edge.
    z = cast(NSVGactiveEdge*)nsvg__alloc(r, NSVGactiveEdge.sizeof);
    if (z is null) return null;
  }

  immutable float dxdy = (e.x1-e.x0)/(e.y1-e.y0);
  //STBTT_assert(e.y0 <= start_point);
  // round dx down to avoid going too far
  if (dxdy < 0)
    z.dx = cast(int)(-floorf(NSVG__FIX*-dxdy));
  else
    z.dx = cast(int)floorf(NSVG__FIX*dxdy);
  z.x = cast(int)floorf(NSVG__FIX*(e.x0+dxdy*(startPoint-e.y0)));
  //z.x -= off_x*FIX;
  z.ey = e.y1;
  z.next = null;
  z.dir = e.dir;

  return z;
}

void nsvg__freeActive (NSVGrasterizer r, NSVGactiveEdge* z) {
  z.next = r.freelist;
  r.freelist = z;
}

void nsvg__fillScanline (ubyte* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) {
  int i = x0>>NSVG__FIXSHIFT;
  int j = x1>>NSVG__FIXSHIFT;
  if (i < *xmin) *xmin = i;
  if (j > *xmax) *xmax = j;
  if (i < len && j >= 0) {
    if (i == j) {
      // x0, x1 are the same pixel, so compute combined coverage
      scanline[i] += cast(ubyte)((x1-x0)*maxWeight>>NSVG__FIXSHIFT);
    } else {
      if (i >= 0) // add antialiasing for x0
        scanline[i] += cast(ubyte)(((NSVG__FIX-(x0&NSVG__FIXMASK))*maxWeight)>>NSVG__FIXSHIFT);
      else
        i = -1; // clip

      if (j < len) // add antialiasing for x1
        scanline[j] += cast(ubyte)(((x1&NSVG__FIXMASK)*maxWeight)>>NSVG__FIXSHIFT);
      else
        j = len; // clip

      for (++i; i < j; ++i) // fill pixels between x0 and x1
        scanline[i] += cast(ubyte)maxWeight;
    }
  }
}

// note: this routine clips fills that extend off the edges... ideally this
// wouldn't happen, but it could happen if the truetype glyph bounding boxes
// are wrong, or if the user supplies a too-small bitmap
void nsvg__fillActiveEdges (ubyte* scanline, int len, const(NSVGactiveEdge)* e, int maxWeight, int* xmin, int* xmax, char fillRule) {
  // non-zero winding fill
  int x0 = 0, w = 0;
  if (fillRule == NSVG.FillRule.NonZero) {
    // Non-zero
    while (e !is null) {
      if (w == 0) {
        // if we're currently at zero, we need to record the edge start point
        x0 = e.x; w += e.dir;
      } else {
        int x1 = e.x; w += e.dir;
        // if we went to zero, we need to draw
        if (w == 0) nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
      }
      e = e.next;
    }
  } else if (fillRule == NSVG.FillRule.EvenOdd) {
    // Even-odd
    while (e !is null) {
      if (w == 0) {
        // if we're currently at zero, we need to record the edge start point
        x0 = e.x; w = 1;
      } else {
        int x1 = e.x; w = 0;
        nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
      }
      e = e.next;
    }
  }
}

float nsvg__clampf() (float a, float mn, float mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }

uint nsvg__RGBA() (ubyte r, ubyte g, ubyte b, ubyte a) { pragma(inline, true); return (r)|(g<<8)|(b<<16)|(a<<24); }

uint nsvg__lerpRGBA (uint c0, uint c1, float u) {
  int iu = cast(int)(nsvg__clampf(u, 0.0f, 1.0f)*256.0f);
  int r = (((c0)&0xff)*(256-iu)+(((c1)&0xff)*iu))>>8;
  int g = (((c0>>8)&0xff)*(256-iu)+(((c1>>8)&0xff)*iu))>>8;
  int b = (((c0>>16)&0xff)*(256-iu)+(((c1>>16)&0xff)*iu))>>8;
  int a = (((c0>>24)&0xff)*(256-iu)+(((c1>>24)&0xff)*iu))>>8;
  return nsvg__RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
}

uint nsvg__applyOpacity (uint c, float u) {
  int iu = cast(int)(nsvg__clampf(u, 0.0f, 1.0f)*256.0f);
  int r = (c)&0xff;
  int g = (c>>8)&0xff;
  int b = (c>>16)&0xff;
  int a = (((c>>24)&0xff)*iu)>>8;
  return nsvg__RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
}

int nsvg__div255() (int x) { pragma(inline, true); return ((x+1)*257)>>16; }

void nsvg__scanlineSolid (ubyte* dst, int count, ubyte* cover, int x, int y, float tx, float ty, float scale, const(NSVGcachedPaint)* cache) {
  if (cache.type == NSVG.PaintType.Color) {
    int cr = cache.colors[0]&0xff;
    int cg = (cache.colors[0]>>8)&0xff;
    int cb = (cache.colors[0]>>16)&0xff;
    int ca = (cache.colors[0]>>24)&0xff;

    foreach (int i; 0..count) {
      int r, g, b;
      int a = nsvg__div255(cast(int)cover[0]*ca);
      int ia = 255-a;
      // Premultiply
      r = nsvg__div255(cr*a);
      g = nsvg__div255(cg*a);
      b = nsvg__div255(cb*a);

      // Blend over
      r += nsvg__div255(ia*cast(int)dst[0]);
      g += nsvg__div255(ia*cast(int)dst[1]);
      b += nsvg__div255(ia*cast(int)dst[2]);
      a += nsvg__div255(ia*cast(int)dst[3]);

      dst[0] = cast(ubyte)r;
      dst[1] = cast(ubyte)g;
      dst[2] = cast(ubyte)b;
      dst[3] = cast(ubyte)a;

      ++cover;
      dst += 4;
    }
  } else if (cache.type == NSVG.PaintType.LinearGradient) {
    // TODO: spread modes.
    // TODO: plenty of opportunities to optimize.
    const(float)* t = cache.xform.ptr;
    //int i, cr, cg, cb, ca;
    //uint c;

    float fx = (x-tx)/scale;
    float fy = (y-ty)/scale;
    float dx = 1.0f/scale;

    foreach (int i; 0..count) {
      //int r, g, b, a, ia;
      float gy = fx*t[1]+fy*t[3]+t[5];
      uint c = cache.colors[cast(int)nsvg__clampf(gy*255.0f, 0, 255.0f)];
      int cr = (c)&0xff;
      int cg = (c>>8)&0xff;
      int cb = (c>>16)&0xff;
      int ca = (c>>24)&0xff;

      int a = nsvg__div255(cast(int)cover[0]*ca);
      int ia = 255-a;

      // Premultiply
      int r = nsvg__div255(cr*a);
      int g = nsvg__div255(cg*a);
      int b = nsvg__div255(cb*a);

      // Blend over
      r += nsvg__div255(ia*cast(int)dst[0]);
      g += nsvg__div255(ia*cast(int)dst[1]);
      b += nsvg__div255(ia*cast(int)dst[2]);
      a += nsvg__div255(ia*cast(int)dst[3]);

      dst[0] = cast(ubyte)r;
      dst[1] = cast(ubyte)g;
      dst[2] = cast(ubyte)b;
      dst[3] = cast(ubyte)a;

      ++cover;
      dst += 4;
      fx += dx;
    }
  } else if (cache.type == NSVG.PaintType.RadialGradient) {
    // TODO: spread modes.
    // TODO: plenty of opportunities to optimize.
    // TODO: focus (fx, fy)
    //float fx, fy, dx, gx, gy, gd;
    const(float)* t = cache.xform.ptr;
    //int i, cr, cg, cb, ca;
    //uint c;

    float fx = (x-tx)/scale;
    float fy = (y-ty)/scale;
    float dx = 1.0f/scale;

    foreach (int i; 0..count) {
      //int r, g, b, a, ia;
      float gx = fx*t[0]+fy*t[2]+t[4];
      float gy = fx*t[1]+fy*t[3]+t[5];
      float gd = sqrtf(gx*gx+gy*gy);
      uint c = cache.colors[cast(int)nsvg__clampf(gd*255.0f, 0, 255.0f)];
      int cr = (c)&0xff;
      int cg = (c>>8)&0xff;
      int cb = (c>>16)&0xff;
      int ca = (c>>24)&0xff;

      int a = nsvg__div255(cast(int)cover[0]*ca);
      int ia = 255-a;

      // Premultiply
      int r = nsvg__div255(cr*a);
      int g = nsvg__div255(cg*a);
      int b = nsvg__div255(cb*a);

      // Blend over
      r += nsvg__div255(ia*cast(int)dst[0]);
      g += nsvg__div255(ia*cast(int)dst[1]);
      b += nsvg__div255(ia*cast(int)dst[2]);
      a += nsvg__div255(ia*cast(int)dst[3]);

      dst[0] = cast(ubyte)r;
      dst[1] = cast(ubyte)g;
      dst[2] = cast(ubyte)b;
      dst[3] = cast(ubyte)a;

      ++cover;
      dst += 4;
      fx += dx;
    }
  }
}

void nsvg__rasterizeSortedEdges (NSVGrasterizer r, float tx, float ty, float scale, const(NSVGcachedPaint)* cache, char fillRule) {
  NSVGactiveEdge* active = null;
  int s;
  int e = 0;
  int maxWeight = (255/NSVG__SUBSAMPLES);  // weight per vertical scanline
  int xmin, xmax;

  foreach (int y; 0..r.height) {
    import core.stdc.string : memset;
    memset(r.scanline, 0, r.width);
    xmin = r.width;
    xmax = 0;
    for (s = 0; s < NSVG__SUBSAMPLES; ++s) {
      // find center of pixel for this scanline
      float scany = y*NSVG__SUBSAMPLES+s+0.5f;
      NSVGactiveEdge** step = &active;

      // update all active edges;
      // remove all active edges that terminate before the center of this scanline
      while (*step) {
        NSVGactiveEdge* z = *step;
        if (z.ey <= scany) {
          *step = z.next; // delete from list
          //NSVG__assert(z.valid);
          nsvg__freeActive(r, z);
        } else {
          z.x += z.dx; // advance to position for current scanline
          step = &((*step).next); // advance through list
        }
      }

      // resort the list if needed
      for (;;) {
        int changed = 0;
        step = &active;
        while (*step && (*step).next) {
          if ((*step).x > (*step).next.x) {
            NSVGactiveEdge* t = *step;
            NSVGactiveEdge* q = t.next;
            t.next = q.next;
            q.next = t;
            *step = q;
            changed = 1;
          }
          step = &(*step).next;
        }
        if (!changed) break;
      }

      // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
      while (e < r.nedges && r.edges[e].y0 <= scany) {
        if (r.edges[e].y1 > scany) {
          NSVGactiveEdge* z = nsvg__addActive(r, &r.edges[e], scany);
          if (z is null) break;
          // find insertion point
          if (active is null) {
            active = z;
          } else if (z.x < active.x) {
            // insert at front
            z.next = active;
            active = z;
          } else {
            // find thing to insert AFTER
            NSVGactiveEdge* p = active;
            while (p.next && p.next.x < z.x)
              p = p.next;
            // at this point, p.next.x is NOT < z.x
            z.next = p.next;
            p.next = z;
          }
        }
        e++;
      }

      // now process all active edges in non-zero fashion
      if (active !is null)
        nsvg__fillActiveEdges(r.scanline, r.width, active, maxWeight, &xmin, &xmax, fillRule);
    }
    // Blit
    if (xmin < 0) xmin = 0;
    if (xmax > r.width-1) xmax = r.width-1;
    if (xmin <= xmax) {
      nsvg__scanlineSolid(&r.bitmap[y*r.stride]+xmin*4, xmax-xmin+1, &r.scanline[xmin], xmin, y, tx, ty, scale, cache);
    }
  }

}

void nsvg__unpremultiplyAlpha (ubyte* image, int w, int h, int stride) {
  // Unpremultiply
  foreach (int y; 0..h) {
    ubyte *row = &image[y*stride];
    foreach (int x; 0..w) {
      int r = row[0], g = row[1], b = row[2], a = row[3];
      if (a != 0) {
        row[0] = cast(ubyte)(r*255/a);
        row[1] = cast(ubyte)(g*255/a);
        row[2] = cast(ubyte)(b*255/a);
      }
      row += 4;
    }
  }

  // Defringe
  foreach (int y; 0..h) {
    ubyte *row = &image[y*stride];
    foreach (int x; 0..w) {
      int r = 0, g = 0, b = 0, a = row[3], n = 0;
      if (a == 0) {
        if (x-1 > 0 && row[-1] != 0) {
          r += row[-4];
          g += row[-3];
          b += row[-2];
          n++;
        }
        if (x+1 < w && row[7] != 0) {
          r += row[4];
          g += row[5];
          b += row[6];
          n++;
        }
        if (y-1 > 0 && row[-stride+3] != 0) {
          r += row[-stride];
          g += row[-stride+1];
          b += row[-stride+2];
          n++;
        }
        if (y+1 < h && row[stride+3] != 0) {
          r += row[stride];
          g += row[stride+1];
          b += row[stride+2];
          n++;
        }
        if (n > 0) {
          row[0] = cast(ubyte)(r/n);
          row[1] = cast(ubyte)(g/n);
          row[2] = cast(ubyte)(b/n);
        }
      }
      row += 4;
    }
  }
}


void nsvg__initPaint (NSVGcachedPaint* cache, const(NSVG.Paint)* paint, float opacity) {
  const(NSVG.Gradient)* grad;

  cache.type = paint.type;

  if (paint.type == NSVG.PaintType.Color) {
    cache.colors[0] = nsvg__applyOpacity(paint.color, opacity);
    return;
  }

  grad = paint.gradient;

  cache.spread = grad.spread;
  //memcpy(cache.xform.ptr, grad.xform.ptr, float.sizeof*6);
  cache.xform[0..6] = grad.xform[0..6];

  if (grad.nstops == 0) {
    //for (i = 0; i < 256; i++) cache.colors[i] = 0;
    cache.colors[0..256] = 0;
  } if (grad.nstops == 1) {
    foreach (int i; 0..256) cache.colors[i] = nsvg__applyOpacity(grad.stops.ptr[i].color, opacity);
  } else {
    uint cb = 0;
    //float ua, ub, du, u;
    int ia, ib, count;

    uint ca = nsvg__applyOpacity(grad.stops.ptr[0].color, opacity);
    float ua = nsvg__clampf(grad.stops.ptr[0].offset, 0, 1);
    float ub = nsvg__clampf(grad.stops.ptr[grad.nstops-1].offset, ua, 1);
    ia = cast(int)(ua*255.0f);
    ib = cast(int)(ub*255.0f);
    //for (i = 0; i < ia; i++) cache.colors[i] = ca;
    cache.colors[0..ia] = ca;

    foreach (int i; 0..grad.nstops-1) {
      ca = nsvg__applyOpacity(grad.stops.ptr[i].color, opacity);
      cb = nsvg__applyOpacity(grad.stops.ptr[i+1].color, opacity);
      ua = nsvg__clampf(grad.stops.ptr[i].offset, 0, 1);
      ub = nsvg__clampf(grad.stops.ptr[i+1].offset, 0, 1);
      ia = cast(int)(ua*255.0f);
      ib = cast(int)(ub*255.0f);
      count = ib-ia;
      if (count <= 0) continue;
      float u = 0;
      immutable float du = 1.0f/cast(float)count;
      foreach (int j; 0..count) {
        cache.colors[ia+j] = nsvg__lerpRGBA(ca, cb, u);
        u += du;
      }
    }

    //for (i = ib; i < 256; i++) cache.colors[i] = cb;
    cache.colors[ib..256] = cb;
  }

}

extern(C) {
  private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
  private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
}

/**
 * Rasterizes SVG image, returns RGBA image (non-premultiplied alpha).
 *
 * Params:
 *   r = pointer to rasterizer context
 *   image = pointer to SVG image to rasterize
 *   tx, ty = image offset (applied after scaling)
 *   scale = image scale
 *   dst = pointer to destination image data, 4 bytes per pixel (RGBA)
 *   w = width of the image to render
 *   h = height of the image to render
 *   stride = number of bytes per scaleline in the destination buffer
 */
public void rasterize (NSVGrasterizer r, const(NSVG)* image, float tx, float ty, float scale, ubyte* dst, int w, int h, int stride=-1) {
  const(NSVG.Shape)* shape = null;
  NSVGedge* e = null;
  NSVGcachedPaint cache;
  int i;

  if (stride <= 0) stride = w*4;
  r.bitmap = dst;
  r.width = w;
  r.height = h;
  r.stride = stride;

  if (w > r.cscanline) {
    import core.stdc.stdlib : realloc;
    r.cscanline = w;
    r.scanline = cast(ubyte*)realloc(r.scanline, w+256);
    if (r.scanline is null) assert(0, "nanosvg: out of memory");
  }

  for (i = 0; i < h; i++) {
    import core.stdc.string : memset;
    memset(&dst[i*stride], 0, w*4);
  }

  for (shape = image.shapes; shape !is null; shape = shape.next) {
    if (!(shape.flags&NSVG.Visible)) continue;

    if (shape.fill.type != NSVG.PaintType.None) {
      //import core.stdc.stdlib : qsort; // not @nogc

      nsvg__resetPool(r);
      r.freelist = null;
      r.nedges = 0;

      nsvg__flattenShape(r, shape, scale);

      // Scale and translate edges
      for (i = 0; i < r.nedges; i++) {
        e = &r.edges[i];
        e.x0 = tx+e.x0;
        e.y0 = (ty+e.y0)*NSVG__SUBSAMPLES;
        e.x1 = tx+e.x1;
        e.y1 = (ty+e.y1)*NSVG__SUBSAMPLES;
      }

      // Rasterize edges
      qsort(r.edges, r.nedges, NSVGedge.sizeof, &nsvg__cmpEdge);

      // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
      nsvg__initPaint(&cache, &shape.fill, shape.opacity);

      nsvg__rasterizeSortedEdges(r, tx, ty, scale, &cache, shape.fillRule);
    }
    if (shape.stroke.type != NSVG.PaintType.None && (shape.strokeWidth*scale) > 0.01f) {
      //import core.stdc.stdlib : qsort; // not @nogc

      nsvg__resetPool(r);
      r.freelist = null;
      r.nedges = 0;

      nsvg__flattenShapeStroke(r, shape, scale);

      //dumpEdges(r, "edge.svg");

      // Scale and translate edges
      for (i = 0; i < r.nedges; i++) {
        e = &r.edges[i];
        e.x0 = tx+e.x0;
        e.y0 = (ty+e.y0)*NSVG__SUBSAMPLES;
        e.x1 = tx+e.x1;
        e.y1 = (ty+e.y1)*NSVG__SUBSAMPLES;
      }

      // Rasterize edges
      qsort(r.edges, r.nedges, NSVGedge.sizeof, &nsvg__cmpEdge);

      // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
      nsvg__initPaint(&cache, &shape.stroke, shape.opacity);

      nsvg__rasterizeSortedEdges(r, tx, ty, scale, &cache, NSVG.FillRule.NonZero);
    }
  }

  nsvg__unpremultiplyAlpha(dst, w, h, stride);

  r.bitmap = null;
  r.width = 0;
  r.height = 0;
  r.stride = 0;
}

} // nothrow @trusted @nogc


// ////////////////////////////////////////////////////////////////////////// //
ptrdiff_t xindexOf (const(void)[] hay, const(void)[] need, usize stIdx=0) pure @trusted nothrow @nogc {
  if (hay.length <= stIdx || need.length == 0 || need.length > hay.length-stIdx) {
    return -1;
  } else {
    //import iv.strex : memmem;
    auto res = memmem(hay.ptr+stIdx, hay.length-stIdx, need.ptr, need.length);
    return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
  }
}

ptrdiff_t xindexOf (const(void)[] hay, ubyte ch, usize stIdx=0) pure @trusted nothrow @nogc {
  return xindexOf(hay, (&ch)[0..1], stIdx);
}

pure nothrow @trusted @nogc:
version(linux) {
  extern(C) inout(void)* memmem (inout(void)* haystack, usize haystacklen, inout(void)* needle, usize needlelen);
} else {
  inout(void)* memmem (inout(void)* haystack, usize haystacklen, inout(void)* needle, usize needlelen) {
    auto h = cast(const(ubyte)*)haystack;
    auto n = cast(const(ubyte)*)needle;
    // usize is unsigned
    if (needlelen > haystacklen) return null;
    foreach (immutable i; 0..haystacklen-needlelen+1) {
      import core.stdc.string : memcmp;
      if (memcmp(h+i, n, needlelen) == 0) return cast(void*)(h+i);
    }
    return null;
  }
}
