1 /*
2  * Copyright (c) 2013-14 Mikko Mononen memon@inside.org
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  * claim that you wrote the original software. If you use this software
14  * in a product, an acknowledgment in the product documentation would be
15  * appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  * misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  *
20  * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
21  * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
22  *
23  * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
24  *
25  * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
26  *
27  * Fork developement, feature integration and new bugs:
28  * Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
29  * Contains code from various contributors.
30  */
31 /**
32   NanoVega.SVG is a simple stupid SVG parser. The output of the parser is a list of drawing commands.
33 
34   The library suits well for anything from rendering scalable icons in your editor application to prototyping a game.
35 
36   NanoVega.SVG supports a wide range of SVG features, but something may be missing. Your's Captain Obvious.
37 
38 
39   The shapes in the SVG images are transformed by the viewBox and converted to specified units.
40   That is, you should get the same looking data as your designed in your favorite app.
41 
42   NanoVega.SVG can return the paths in few different units. For example if you want to render an image, you may choose
43   to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters.
44 
45   The units passed to NanoVega.SVG should be one of: 'px', 'pt', 'pc', 'mm', 'cm', 'in'.
46   DPI (dots-per-inch) controls how the unit conversion is done.
47 
48   If you don't know or care about the units stuff, "px" and 96 should get you going.
49 
50   Example Usage:
51 
52   ---
53     // Load
54     NSVG* image = nsvgParseFromFile("test.svg", "px", 96);
55     printf("size: %f x %f\n", image.width, image.height);
56     // Use...
57     image.forEachShape((in ref NSVG.Shape shape) {
58       if (!shape.visible) return;
59       shape.forEachPath((in ref NSVG.Path path) {
60         // this will issue final `LineTo` for closed pathes
61         path.forEachCommand!true(delegate (NSVG.Command cmd, const(float)[] args) nothrow @trusted @nogc {
62           final switch (cmd) {
63             case NSVG.Command.MoveTo: nvg.moveTo(args); break;
64             case NSVG.Command.LineTo: nvg.lineTo(args); break;
65             case NSVG.Command.QuadTo: nvg.quadTo(args); break;
66             case NSVG.Command.BezierTo: nvg.bezierTo(args); break;
67           }
68         });
69       });
70     });
71 
72     NSVGrasterizer rast = nsvgCreateRasterizer();
73     // Allocate memory for image
74     ubyte* img = malloc(w*h*4);
75     // Rasterize
76     nsvgRasterize(rast, image, 0, 0, 1, img, w, h, w*4);
77 
78     // Delete
79     image.kill();
80   ---
81  */
82 module arsd.svg;
83 
84 private import core.stdc.math : fabs, fabsf, atan2f, acosf, cosf, sinf, tanf, sqrt, sqrtf, floorf, ceilf, fmodf;
85 //private import iv.vfs;
86 
87 version(nanosvg_disable_vfs) {
88   enum NanoSVGHasIVVFS = false;
89 } else {
90   static if (is(typeof((){import iv.vfs;}))) {
91     enum NanoSVGHasIVVFS = true;
92     import iv.vfs;
93   } else {
94     enum NanoSVGHasIVVFS = false;
95   }
96 }
97 
98 version(aliced) {} else {
99   private alias usize = size_t;
100 }
101 
102 version = nanosvg_crappy_stylesheet_parser;
103 //version = nanosvg_debug_styles;
104 //version(rdmd) import iv.strex;
105 
106 //version = nanosvg_use_beziers; // convert everything to beziers
107 //version = nanosvg_only_cubic_beziers; // convert everything to cubic beziers
108 
109 ///
110 public enum NSVGDefaults {
111   CanvasWidth = 800,
112   CanvasHeight = 600,
113 }
114 
115 
116 // ////////////////////////////////////////////////////////////////////////// //
117 public alias NSVGrasterizer = NSVGrasterizerS*; ///
118 public alias NSVGRasterizer = NSVGrasterizer; ///
119 
120 ///
121 struct NSVG {
122   @disable this (this);
123 
124   ///
125   enum Command : int {
126     MoveTo, ///
127     LineTo, ///
128     QuadTo, ///
129     BezierTo, /// cubic bezier
130   }
131 
132   ///
133   enum PaintType : ubyte {
134     None, ///
135     Color, ///
136     LinearGradient, ///
137     RadialGradient, ///
138   }
139 
140   ///
141   enum SpreadType : ubyte {
142     Pad, ///
143     Reflect, ///
144     Repeat, ///
145   }
146 
147   ///
148   enum LineJoin : ubyte {
149     Miter, ///
150     Round, ///
151     Bevel, ///
152   }
153 
154   ///
155   enum LineCap : ubyte {
156     Butt, ///
157     Round, ///
158     Square, ///
159   }
160 
161   ///
162   enum FillRule : ubyte {
163     NonZero, ///
164     EvenOdd, ///
165   }
166 
167   alias Flags = ubyte; ///
168   enum : ubyte {
169     Visible = 0x01, ///
170   }
171 
172   ///
173   static struct GradientStop {
174     uint color; ///
175     float offset; ///
176   }
177 
178   ///
179   static struct Gradient {
180     float[6] xform; ///
181     SpreadType spread; ///
182     float fx, fy; ///
183     int nstops; ///
184     GradientStop[0] stops; ///
185   }
186 
187   ///
188   static struct Paint {
189   pure nothrow @safe @nogc:
190     @disable this (this);
191     PaintType type; ///
192     union {
193       uint color; ///
194       Gradient* gradient; ///
195     }
196     static uint rgb (ubyte r, ubyte g, ubyte b) { pragma(inline, true); return (r|(g<<8)|(b<<16)); } ///
197     @property const {
198       bool isNone () { pragma(inline, true); return (type == PaintType.None); } ///
199       bool isColor () { pragma(inline, true); return (type == PaintType.Color); } ///
200       // gradient types
201       bool isLinear () { pragma(inline, true); return (type == PaintType.LinearGradient); } ///
202       bool isRadial () { pragma(inline, true); return (type == PaintType.RadialGradient); } ///
203       // color
204       ubyte r () { pragma(inline, true); return color&0xff; } ///
205       ubyte g () { pragma(inline, true); return (color>>8)&0xff; } ///
206       ubyte b () { pragma(inline, true); return (color>>16)&0xff; } ///
207       ubyte a () { pragma(inline, true); return (color>>24)&0xff; } ///
208     }
209   }
210 
211   ///
212   static struct Path {
213     @disable this (this);
214     float* stream;   /// Command, args...; Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
215     int nsflts;      /// Total number of floats in stream.
216     bool closed;     /// Flag indicating if shapes should be treated as closed.
217     float[4] bounds; /// Tight bounding box of the shape [minx,miny,maxx,maxy].
218     NSVG.Path* next; /// Pointer to next path, or null if last element.
219 
220     ///
221     @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (nsflts == 0); }
222 
223     ///
224     float startX () const nothrow @trusted @nogc {
225       pragma(inline, true);
226       return (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo ? stream[1] : float.nan);
227     }
228 
229     ///
230     float startY () const nothrow @trusted @nogc {
231       pragma(inline, true);
232       return (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo ? stream[2] : float.nan);
233     }
234 
235     ///
236     bool startPoint (float* dx, float* dy) const nothrow @trusted @nogc {
237       if (nsflts >= 3 && cast(Command)stream[0] == Command.MoveTo) {
238         if (dx !is null) *dx = stream[1];
239         if (dy !is null) *dy = stream[2];
240         return true;
241       } else {
242         if (dx !is null) *dx = 0;
243         if (dy !is null) *dy = 0;
244         return false;
245       }
246     }
247 
248     ///
249     int countCubics () const nothrow @trusted @nogc {
250       if (nsflts < 3) return 0;
251       int res = 0, argc;
252       for (int pidx = 0; pidx+3 <= nsflts; ) {
253         final switch (cast(Command)stream[pidx++]) {
254           case Command.MoveTo: argc = 2; break;
255           case Command.LineTo: argc = 2; ++res; break;
256           case Command.QuadTo: argc = 4; ++res; break;
257           case Command.BezierTo: argc = 6; ++res; break;
258         }
259         if (pidx+argc > nsflts) break; // just in case
260         pidx += argc;
261       }
262       return res;
263     }
264 
265     ///
266     int countCommands(bool synthesizeCloseCommand=true) () const nothrow @trusted @nogc {
267       if (nsflts < 3) return 0;
268       int res = 0, argc;
269       for (int pidx = 0; pidx+3 <= nsflts; ) {
270         ++res;
271         final switch (cast(Command)stream[pidx++]) {
272           case Command.MoveTo: argc = 2; break;
273           case Command.LineTo: argc = 2; break;
274           case Command.QuadTo: argc = 4; break;
275           case Command.BezierTo: argc = 6; break;
276         }
277         if (pidx+argc > nsflts) break; // just in case
278         pidx += argc;
279       }
280       static if (synthesizeCloseCommand) { if (closed) ++res; }
281       return res;
282     }
283 
284     /// emits cubic beziers.
285     /// if `withMoveTo` is `false`, issue 8-arg commands for cubic beziers (i.e. include starting point).
286     /// if `withMoveTo` is `true`, issue 2-arg command for `moveTo`, and 6-arg command for cubic beziers.
287     void asCubics(bool withMoveTo=false, DG) (scope DG dg) inout if (__traits(compiles, (){ DG xdg; float[] f; xdg(f); })) {
288       if (dg is null) return;
289       if (nsflts < 3) return;
290       enum HasRes = __traits(compiles, (){ DG xdg; float[] f; bool res = xdg(f); });
291       float cx = 0, cy = 0;
292       float[8] cubic = void;
293 
294       void synthLine (in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
295         immutable float dx = x-cx;
296         immutable float dy = y-cy;
297         cubic.ptr[0] = cx;
298         cubic.ptr[1] = cy;
299         cubic.ptr[2] = cx+dx/3.0f;
300         cubic.ptr[3] = cy+dy/3.0f;
301         cubic.ptr[4] = x-dx/3.0f;
302         cubic.ptr[5] = y-dy/3.0f;
303         cubic.ptr[6] = x;
304         cubic.ptr[7] = y;
305       }
306 
307       void synthQuad (in float cx, in float cy, in float x1, in float y1, in float x2, in float y2) nothrow @trusted @nogc {
308         immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
309         immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
310         immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
311         immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
312         cubic.ptr[0] = cx;
313         cubic.ptr[1] = cy;
314         cubic.ptr[2] = cx1;
315         cubic.ptr[3] = cy2;
316         cubic.ptr[4] = cx2;
317         cubic.ptr[5] = cy2;
318         cubic.ptr[6] = x2;
319         cubic.ptr[7] = y2;
320       }
321 
322       for (int pidx = 0; pidx+3 <= nsflts; ) {
323         final switch (cast(Command)stream[pidx++]) {
324           case Command.MoveTo:
325             static if (withMoveTo) {
326               static if (HasRes) { if (dg(stream[pidx+0..pidx+2])) return; } else { dg(stream[pidx+0..pidx+2]); }
327             }
328             cx = stream[pidx++];
329             cy = stream[pidx++];
330             continue;
331           case Command.LineTo:
332             synthLine(cx, cy, stream[pidx+0], stream[pidx+1]);
333             pidx += 2;
334             break;
335           case Command.QuadTo:
336             synthQuad(cx, cy, stream[pidx+0], stream[pidx+1], stream[pidx+2], stream[pidx+3]);
337             pidx += 4;
338             break;
339           case Command.BezierTo:
340             cubic.ptr[0] = cx;
341             cubic.ptr[1] = cy;
342             cubic.ptr[2..8] = stream[pidx..pidx+6];
343             pidx += 6;
344             break;
345         }
346         cx = cubic.ptr[6];
347         cy = cubic.ptr[7];
348         static if (withMoveTo) {
349           static if (HasRes) { if (dg(cubic[2..8])) return; } else { dg(cubic[2..8]); }
350         } else {
351           static if (HasRes) { if (dg(cubic[])) return; } else { dg(cubic[]); }
352         }
353       }
354     }
355 
356     /// if `synthesizeCloseCommand` is true, and the path is closed, this emits line to the first point.
357     void forEachCommand(bool synthesizeCloseCommand=true, DG) (scope DG dg) inout
358     if (__traits(compiles, (){ DG xdg; Command c; const(float)[] f; xdg(c, f); }))
359     {
360       if (dg is null) return;
361       if (nsflts < 3) return;
362       enum HasRes = __traits(compiles, (){ DG xdg; Command c; const(float)[] f; bool res = xdg(c, f); });
363       int argc;
364       Command cmd;
365       for (int pidx = 0; pidx+3 <= nsflts; ) {
366         cmd = cast(Command)stream[pidx++];
367         final switch (cmd) {
368           case Command.MoveTo: argc = 2; break;
369           case Command.LineTo: argc = 2; break;
370           case Command.QuadTo: argc = 4; break;
371           case Command.BezierTo: argc = 6; break;
372         }
373         if (pidx+argc > nsflts) break; // just in case
374         static if (HasRes) { if (dg(cmd, stream[pidx..pidx+argc])) return; } else { dg(cmd, stream[pidx..pidx+argc]); }
375         pidx += argc;
376       }
377       static if (synthesizeCloseCommand) {
378         if (closed && cast(Command)stream[0] == Command.MoveTo) {
379           static if (HasRes) { if (dg(Command.LineTo, stream[1..3])) return; } else { dg(Command.LineTo, stream[1..3]); }
380         }
381       }
382     }
383   }
384 
385   ///
386   static struct Shape {
387     @disable this (this);
388     char[64] id = 0;          /// Optional 'id' attr of the shape or its group
389     NSVG.Paint fill;          /// Fill paint
390     NSVG.Paint stroke;        /// Stroke paint
391     float opacity;            /// Opacity of the shape.
392     float strokeWidth;        /// Stroke width (scaled).
393     float strokeDashOffset;   /// Stroke dash offset (scaled).
394     float[8] strokeDashArray; /// Stroke dash array (scaled).
395     byte strokeDashCount;     /// Number of dash values in dash array.
396     LineJoin strokeLineJoin;  /// Stroke join type.
397     LineCap strokeLineCap;    /// Stroke cap type.
398     float miterLimit;         /// Miter limit
399     FillRule fillRule;        /// Fill rule, see FillRule.
400     /*Flags*/ubyte flags;     /// Logical or of NSVG_FLAGS_* flags
401     float[4] bounds;          /// Tight bounding box of the shape [minx,miny,maxx,maxy].
402     NSVG.Path* paths;         /// Linked list of paths in the image.
403     NSVG.Shape* next;         /// Pointer to next shape, or null if last element.
404 
405     @property bool visible () const pure nothrow @safe @nogc { pragma(inline, true); return ((flags&Visible) != 0); } ///
406 
407     /// delegate can accept:
408     ///   NSVG.Path*
409     ///   const(NSVG.Path)*
410     ///   ref NSVG.Path
411     ///   in ref NSVG.Path
412     /// delegate can return:
413     ///   void
414     ///   bool (true means `stop`)
415     void forEachPath(DG) (scope DG dg) inout
416     if (__traits(compiles, (){ DG xdg; NSVG.Path s; xdg(&s); }) ||
417         __traits(compiles, (){ DG xdg; NSVG.Path s; xdg(s); }))
418     {
419       if (dg is null) return;
420       enum WantPtr = __traits(compiles, (){ DG xdg; NSVG.Path s; xdg(&s); });
421       static if (WantPtr) {
422         enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Path s; bool res = xdg(&s); });
423       } else {
424         enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Path s; bool res = xdg(s); });
425       }
426       static if (__traits(compiles, (){ NSVG.Path* s = this.paths; })) {
427         alias TP = NSVG.Path*;
428       } else {
429         alias TP = const(NSVG.Path)*;
430       }
431       for (TP path = paths; path !is null; path = path.next) {
432         static if (HasRes) {
433           static if (WantPtr) {
434             if (dg(path)) return;
435           } else {
436             if (dg(*path)) return;
437           }
438         } else {
439           static if (WantPtr) dg(path); else dg(*path);
440         }
441       }
442     }
443   }
444 
445   float width;        /// Width of the image.
446   float height;       /// Height of the image.
447   NSVG.Shape* shapes; /// Linked list of shapes in the image.
448 
449   /// delegate can accept:
450   ///   NSVG.Shape*
451   ///   const(NSVG.Shape)*
452   ///   ref NSVG.Shape
453   ///   in ref NSVG.Shape
454   /// delegate can return:
455   ///   void
456   ///   bool (true means `stop`)
457   void forEachShape(DG) (scope DG dg) inout
458   if (__traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(&s); }) ||
459       __traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(s); }))
460   {
461     if (dg is null) return;
462     enum WantPtr = __traits(compiles, (){ DG xdg; NSVG.Shape s; xdg(&s); });
463     static if (WantPtr) {
464       enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Shape s; bool res = xdg(&s); });
465     } else {
466       enum HasRes = __traits(compiles, (){ DG xdg; NSVG.Shape s; bool res = xdg(s); });
467     }
468     static if (__traits(compiles, (){ NSVG.Shape* s = this.shapes; })) {
469       alias TP = NSVG.Shape*;
470     } else {
471       alias TP = const(NSVG.Shape)*;
472     }
473     for (TP shape = shapes; shape !is null; shape = shape.next) {
474       static if (HasRes) {
475         static if (WantPtr) {
476           if (dg(shape)) return;
477         } else {
478           if (dg(*shape)) return;
479         }
480       } else {
481         static if (WantPtr) dg(shape); else dg(*shape);
482       }
483     }
484   }
485 }
486 
487 
488 // ////////////////////////////////////////////////////////////////////////// //
489 private:
490 nothrow @trusted @nogc {
491 
492 // ////////////////////////////////////////////////////////////////////////// //
493 // sscanf replacement: just enough to replace all our cases
494 int xsscanf(A...) (const(char)[] str, const(char)[] fmt, ref A args) {
495   int spos;
496   while (spos < str.length && str.ptr[spos] <= ' ') ++spos;
497 
498   static int hexdigit() (char c) {
499     pragma(inline, true);
500     return
501       (c >= '0' && c <= '9' ? c-'0' :
502        c >= 'A' && c <= 'F' ? c-'A'+10 :
503        c >= 'a' && c <= 'f' ? c-'a'+10 :
504        -1);
505   }
506 
507   bool parseInt(T : ulong) (ref T res) {
508     res = 0;
509     debug(xsscanf_int) { import std.stdio; writeln("parseInt00: str=", str[spos..$].quote); }
510     bool neg = false;
511          if (spos < str.length && str.ptr[spos] == '+') ++spos;
512     else if (spos < str.length && str.ptr[spos] == '-') { neg = true; ++spos; }
513     if (spos >= str.length || str.ptr[spos] < '0' || str.ptr[spos] > '9') return false;
514     while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') res = res*10+str.ptr[spos++]-'0';
515     debug(xsscanf_int) { import std.stdio; writeln("parseInt10: str=", str[spos..$].quote); }
516     if (neg) res = -res;
517     return true;
518   }
519 
520   bool parseHex(T : ulong) (ref T res) {
521     res = 0;
522     debug(xsscanf_int) { import std.stdio; writeln("parseHex00: str=", str[spos..$].quote); }
523     if (spos >= str.length || hexdigit(str.ptr[spos]) < 0) return false;
524     while (spos < str.length) {
525       auto d = hexdigit(str.ptr[spos]);
526       if (d < 0) break;
527       res = res*16+d;
528       ++spos;
529     }
530     debug(xsscanf_int) { import std.stdio; writeln("parseHex10: str=", str[spos..$].quote); }
531     return true;
532   }
533 
534   bool parseFloat(T : real) (ref T res) {
535     res = 0.0;
536     debug(xsscanf_float) { import std.stdio; writeln("parseFloat00: str=", str[spos..$].quote); }
537     bool neg = false;
538          if (spos < str.length && str.ptr[spos] == '+') ++spos;
539     else if (spos < str.length && str.ptr[spos] == '-') { neg = true; ++spos; }
540     bool wasChar = false;
541     // integer part
542     debug(xsscanf_float) { import std.stdio; writeln("parseFloat01: str=", str[spos..$].quote); }
543     if (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') wasChar = true;
544     while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') res = res*10+str.ptr[spos++]-'0';
545     // fractional part
546     if (spos < str.length && str.ptr[spos] == '.') {
547       debug(xsscanf_float) { import std.stdio; writeln("parseFloat02: str=", str[spos..$].quote); }
548       T div = 1.0/10;
549       ++spos;
550       if (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') wasChar = true;
551       debug(xsscanf_float) { import std.stdio; writeln("parseFloat03: str=", str[spos..$].quote); }
552       while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') {
553         res += div*(str.ptr[spos++]-'0');
554         div /= 10.0;
555       }
556       debug(xsscanf_float) { import std.stdio; writeln("parseFloat04: str=", str[spos..$].quote); }
557       debug(xsscanf_float) { import std.stdio; writeln("div=", div, "; res=", res, "; str=", str[spos..$].quote); }
558     }
559     // '[Ee][+-]num' part
560     if (wasChar && spos < str.length && (str.ptr[spos] == 'E' || str.ptr[spos] == 'e')) {
561       debug(xsscanf_float) { import std.stdio; writeln("parseFloat05: str=", str[spos..$].quote); }
562       ++spos;
563       bool xneg = false;
564            if (spos < str.length && str.ptr[spos] == '+') ++spos;
565       else if (spos < str.length && str.ptr[spos] == '-') { xneg = true; ++spos; }
566       int n = 0;
567       if (spos >= str.length || str.ptr[spos] < '0' || str.ptr[spos] > '9') return false; // number expected
568       debug(xsscanf_float) { import std.stdio; writeln("parseFloat06: str=", str[spos..$].quote); }
569       while (spos < str.length && str.ptr[spos] >= '0' && str.ptr[spos] <= '9') n = n*10+str.ptr[spos++]-'0';
570       if (xneg) {
571         while (n-- > 0) res /= 10;
572       } else {
573         while (n-- > 0) res *= 10;
574       }
575       debug(xsscanf_float) { import std.stdio; writeln("parseFloat07: str=", str[spos..$].quote); }
576     }
577     if (!wasChar) return false;
578     debug(xsscanf_float) { import std.stdio; writeln("parseFloat10: str=", str[spos..$].quote); }
579     if (neg) res = -res;
580     return true;
581   }
582 
583   int fpos;
584 
585   void skipXSpaces () {
586     if (fpos < fmt.length && fmt.ptr[fpos] <= ' ') {
587       while (fpos < fmt.length && fmt.ptr[fpos] <= ' ') ++fpos;
588       while (spos < str.length && str.ptr[spos] <= ' ') ++spos;
589     }
590   }
591 
592   bool parseImpl(T/*, usize dummy*/) (ref T res) {
593     while (fpos < fmt.length) {
594       //{ import std.stdio; writeln("spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote); }
595       if (fmt.ptr[fpos] <= ' ') {
596         skipXSpaces();
597         continue;
598       }
599       if (fmt.ptr[fpos] != '%') {
600         if (spos >= str.length || str.ptr[spos] != fmt.ptr[spos]) return false;
601         ++spos;
602         ++fpos;
603         continue;
604       }
605       if (fmt.length-fpos < 2) return false; // stray percent
606       fpos += 2;
607       bool skipAss = false;
608       if (fmt.ptr[fpos-1] == '*') {
609         ++fpos;
610         if (fpos >= fmt.length) return false; // stray star
611         skipAss = true;
612       }
613       switch (fmt.ptr[fpos-1]) {
614         case '%':
615           if (spos >= str.length || str.ptr[spos] != '%') return false;
616           ++spos;
617           break;
618         case 'd':
619           static if (is(T : ulong)) {
620             if (skipAss) {
621               long v;
622               if (!parseInt!long(v)) return false;
623             } else {
624               return parseInt!T(res);
625             }
626           } else {
627             if (!skipAss) assert(0, "invalid type");
628             long v;
629             if (!parseInt!long(v)) return false;
630           }
631           break;
632         case 'x':
633           static if (is(T : ulong)) {
634             if (skipAss) {
635               long v;
636               if (!parseHex!long(v)) return false;
637             } else {
638               return parseHex!T(res);
639             }
640           } else {
641             if (!skipAss) assert(0, "invalid type");
642             ulong v;
643             if (!parseHex!ulong(v)) return false;
644           }
645           break;
646         case 'f':
647           static if (is(T == float) || is(T == double) || is(T == real)) {
648             if (skipAss) {
649               double v;
650               if (!parseFloat!double(v)) return false;
651             } else {
652               return parseFloat!T(res);
653             }
654           } else {
655             if (!skipAss) assert(0, "invalid type");
656             double v;
657             if (!parseFloat!double(v)) return false;
658           }
659           break;
660         case '[':
661           if (fmt.length-fpos < 1) return false;
662           auto stp = spos;
663           while (spos < str.length) {
664             bool ok = false;
665             foreach (immutable cidx, char c; fmt[fpos..$]) {
666               if (cidx != 0) {
667                 if (c == '-') assert(0, "not yet");
668                 if (c == ']') break;
669               }
670               if (c == ' ') {
671                 if (str.ptr[spos] <= ' ') { ok = true; break; }
672               } else {
673                 if (str.ptr[spos] == c) { ok = true; break; }
674               }
675             }
676             //{ import std.stdio; writeln("** spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote, "\nok: ", ok); }
677             if (!ok) break; // not a match
678             ++spos; // skip match
679           }
680           ++fpos;
681           while (fpos < fmt.length && fmt[fpos] != ']') ++fpos;
682           if (fpos < fmt.length) ++fpos;
683           static if (is(T == const(char)[])) {
684             if (!skipAss) {
685               res = str[stp..spos];
686               return true;
687             }
688           } else {
689             if (!skipAss) assert(0, "invalid type");
690           }
691           break;
692         case 's':
693           auto stp = spos;
694           while (spos < str.length && str.ptr[spos] > ' ') ++spos;
695           static if (is(T == const(char)[])) {
696             if (!skipAss) {
697               res = str[stp..spos];
698               return true;
699             }
700           } else {
701             // skip non-spaces
702             if (!skipAss) assert(0, "invalid type");
703           }
704           break;
705         default: assert(0, "unknown format specifier");
706       }
707     }
708     return false;
709   }
710 
711   foreach (usize aidx, immutable T; A) {
712     //pragma(msg, "aidx=", aidx, "; T=", T);
713     if (!parseImpl!(T)(args[aidx])) return -(spos+1);
714     //{ import std.stdio; writeln("@@@ aidx=", aidx+3, "; spos=", spos, "; fpos=", fpos, "\nfmt=", fmt[fpos..$].quote, "\nstr=", str[spos..$].quote); }
715   }
716   skipXSpaces();
717   return (fpos < fmt.length ? -(spos+1) : spos);
718 }
719 
720 
721 // ////////////////////////////////////////////////////////////////////////// //
722 T* xalloc(T) (usize addmem=0) if (!is(T == class)) {
723   import core.stdc.stdlib : malloc;
724   if (T.sizeof == 0 && addmem == 0) addmem = 1;
725   auto res = cast(ubyte*)malloc(T.sizeof+addmem+256);
726   if (res is null) assert(0, "NanoVega.SVG: out of memory");
727   res[0..T.sizeof+addmem] = 0;
728   return cast(T*)res;
729 }
730 
731 T* xcalloc(T) (usize count) if (!is(T == class) && !is(T == struct)) {
732   import core.stdc.stdlib : malloc;
733   usize sz = T.sizeof*count;
734   if (sz == 0) sz = 1;
735   auto res = cast(ubyte*)malloc(sz+256);
736   if (res is null) assert(0, "NanoVega.SVG: out of memory");
737   res[0..sz] = 0;
738   return cast(T*)res;
739 }
740 
741 void xfree(T) (ref T* p) {
742   if (p !is null) {
743     import core.stdc.stdlib : free;
744     free(p);
745     p = null;
746   }
747 }
748 
749 
750 alias AttrList = const(const(char)[])[];
751 
752 public enum NSVG_PI = 3.14159265358979323846264338327f; ///
753 enum NSVG_KAPPA90 = 0.5522847493f; // Lenght proportional to radius of a cubic bezier handle for 90deg arcs.
754 
755 enum NSVG_ALIGN_MIN = 0;
756 enum NSVG_ALIGN_MID = 1;
757 enum NSVG_ALIGN_MAX = 2;
758 enum NSVG_ALIGN_NONE = 0;
759 enum NSVG_ALIGN_MEET = 1;
760 enum NSVG_ALIGN_SLICE = 2;
761 
762 
763 int nsvg__isspace() (char c) { pragma(inline, true); return (c && c <= ' '); } // because
764 int nsvg__isdigit() (char c) { pragma(inline, true); return (c >= '0' && c <= '9'); }
765 int nsvg__isnum() (char c) { pragma(inline, true); return ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E'); }
766 
767 int nsvg__hexdigit() (char c) {
768   pragma(inline, true);
769   return
770     (c >= '0' && c <= '9' ? c-'0' :
771      c >= 'A' && c <= 'F' ? c-'A'+10 :
772      c >= 'a' && c <= 'f' ? c-'a'+10 :
773      -1);
774 }
775 
776 float nsvg__minf() (float a, float b) { pragma(inline, true); return (a < b ? a : b); }
777 float nsvg__maxf() (float a, float b) { pragma(inline, true); return (a > b ? a : b); }
778 
779 
780 // Simple XML parser
781 enum NSVG_XML_TAG = 1;
782 enum NSVG_XML_CONTENT = 2;
783 enum NSVG_XML_MAX_ATTRIBS = 256;
784 
785 void nsvg__parseContent (const(char)[] s, scope void function (void* ud, const(char)[] s) nothrow @nogc contentCb, void* ud) {
786   // Trim start white spaces
787   while (s.length && nsvg__isspace(s[0])) s = s[1..$];
788   if (s.length == 0) return;
789   //{ import std.stdio; writeln("s=", s.quote); }
790   if (contentCb !is null) contentCb(ud, s);
791 }
792 
793 static void nsvg__parseElement (const(char)[] s,
794                  scope void function (void* ud, const(char)[] el, AttrList attr) nothrow @nogc startelCb,
795                  scope void function (void* ud, const(char)[] el) nothrow @nogc endelCb,
796                  void* ud)
797 {
798   const(char)[][NSVG_XML_MAX_ATTRIBS] attr;
799   int nattr = 0;
800   const(char)[] name;
801   int start = 0;
802   int end = 0;
803   char quote;
804 
805   // Skip white space after the '<'
806   while (s.length && nsvg__isspace(s[0])) s = s[1..$];
807 
808   // Check if the tag is end tag
809   if (s.length && s[0] == '/') {
810     s = s[1..$];
811     end = 1;
812   } else {
813     start = 1;
814   }
815 
816   // Skip comments, data and preprocessor stuff.
817   if (s.length == 0 || s[0] == '?' || s[0] == '!') return;
818 
819   // Get tag name
820   //{ import std.stdio; writeln("bs=", s.quote); }
821   {
822     usize pos = 0;
823     while (pos < s.length && !nsvg__isspace(s[pos])) ++pos;
824     name = s[0..pos];
825     s = s[pos..$];
826   }
827   //{ import std.stdio; writeln("name=", name.quote); }
828   //{ import std.stdio; writeln("as=", s.quote); }
829 
830   // Get attribs
831   while (!end && s.length && attr.length-nattr >= 2) {
832     // skip white space before the attrib name
833     while (s.length && nsvg__isspace(s[0])) s = s[1..$];
834     if (s.length == 0) break;
835     if (s[0] == '/') { end = 1; break; }
836     // find end of the attrib name
837     {
838       usize pos = 0;
839       while (pos < s.length && !nsvg__isspace(s[pos]) && s[pos] != '=') ++pos;
840       attr[nattr++] = s[0..pos];
841       s = s[pos..$];
842     }
843     // skip until the beginning of the value
844     while (s.length && s[0] != '\"' && s[0] != '\'') s = s[1..$];
845     if (s.length == 0) break;
846     // store value and find the end of it
847     quote = s[0];
848     s = s[1..$];
849     {
850       usize pos = 0;
851       while (pos < s.length && s[pos] != quote) ++pos;
852       attr[nattr++] = s[0..pos];
853       s = s[pos+(pos < s.length ? 1 : 0)..$];
854     }
855     //{ import std.stdio; writeln("n=", attr[nattr-2].quote, "\nv=", attr[nattr-1].quote, "\n"); }
856   }
857 
858   debug(nanosvg) {
859     import std.stdio;
860     writeln("===========================");
861     foreach (immutable idx, const(char)[] v; attr[0..nattr]) writeln("  #", idx, ": ", v.quote);
862   }
863 
864   // Call callbacks.
865   if (start && startelCb !is null) startelCb(ud, name, attr[0..nattr]);
866   if (end && endelCb !is null) endelCb(ud, name);
867 }
868 
869 void nsvg__parseXML (const(char)[] input,
870                      scope void function (void* ud, const(char)[] el, AttrList attr) nothrow @nogc startelCb,
871                      scope void function (void* ud, const(char)[] el) nothrow @nogc endelCb,
872                      scope void function (void* ud, const(char)[] s) nothrow @nogc contentCb,
873                      void* ud)
874 {
875   usize cpos = 0;
876   int state = NSVG_XML_CONTENT;
877   while (cpos < input.length) {
878     if (state == NSVG_XML_CONTENT && input[cpos] == '<') {
879       if (input.length-cpos >= 9 && input[cpos..cpos+9] == "<![CDATA[") {
880         cpos += 9;
881         while (cpos < input.length) {
882           if (input.length-cpos > 1 && input.ptr[cpos] == ']' && input.ptr[cpos+1] == ']') {
883             cpos += 2;
884             while (cpos < input.length && input.ptr[cpos] <= ' ') ++cpos;
885             if (cpos < input.length && input.ptr[cpos] == '>') { ++cpos; break; }
886           } else {
887             ++cpos;
888           }
889         }
890         continue;
891       }
892       // start of a tag
893       //{ import std.stdio; writeln("ctx: ", input[0..cpos].quote); }
894       ////version(nanosvg_debug_styles) { import std.stdio; writeln("ctx: ", input[0..cpos].quote); }
895       nsvg__parseContent(input[0..cpos], contentCb, ud);
896       input = input[cpos+1..$];
897       if (input.length > 2 && input.ptr[0] == '!' && input.ptr[1] == '-' && input.ptr[2] == '-') {
898         //{ import std.stdio; writeln("ctx0: ", input.quote); }
899         // skip comments
900         cpos = 3;
901         while (cpos < input.length) {
902           if (input.length-cpos > 2 && input.ptr[cpos] == '-' && input.ptr[cpos+1] == '-' && input.ptr[cpos+2] == '>') {
903             cpos += 3;
904             break;
905           }
906           ++cpos;
907         }
908         input = input[cpos..$];
909         //{ import std.stdio; writeln("ctx1: ", input.quote); }
910       } else {
911         state = NSVG_XML_TAG;
912       }
913       cpos = 0;
914     } else if (state == NSVG_XML_TAG && input[cpos] == '>') {
915       // start of a content or new tag
916       //{ import std.stdio; writeln("tag: ", input[0..cpos].quote); }
917       nsvg__parseElement(input[0..cpos], startelCb, endelCb, ud);
918       input = input[cpos+1..$];
919       cpos = 0;
920       state = NSVG_XML_CONTENT;
921     } else {
922       ++cpos;
923     }
924   }
925 }
926 
927 
928 /* Simple SVG parser. */
929 
930 enum NSVG_MAX_ATTR = 128;
931 
932 enum GradientUnits : ubyte {
933   User,
934   Object,
935 }
936 
937 enum NSVG_MAX_DASHES = 8;
938 
939 enum Units : ubyte {
940   user,
941   px,
942   pt,
943   pc,
944   mm,
945   cm,
946   in_,
947   percent,
948   em,
949   ex,
950 }
951 
952 struct Coordinate {
953   float value;
954   Units units;
955 }
956 
957 struct LinearData {
958   Coordinate x1, y1, x2, y2;
959 }
960 
961 struct RadialData {
962   Coordinate cx, cy, r, fx, fy;
963 }
964 
965 struct GradientData {
966   char[64] id = 0;
967   char[64] ref_ = 0;
968   NSVG.PaintType type;
969   union {
970     LinearData linear;
971     RadialData radial;
972   }
973   NSVG.SpreadType spread;
974   GradientUnits units;
975   float[6] xform;
976   int nstops;
977   NSVG.GradientStop* stops;
978   GradientData* next;
979 }
980 
981 struct Attrib {
982   char[64] id = 0;
983   float[6] xform;
984   uint fillColor;
985   uint strokeColor;
986   float opacity;
987   float fillOpacity;
988   float strokeOpacity;
989   char[64] fillGradient = 0;
990   char[64] strokeGradient = 0;
991   float strokeWidth;
992   float strokeDashOffset;
993   float[NSVG_MAX_DASHES] strokeDashArray;
994   int strokeDashCount;
995   NSVG.LineJoin strokeLineJoin;
996   NSVG.LineCap strokeLineCap;
997   float miterLimit;
998   NSVG.FillRule fillRule;
999   float fontSize;
1000   uint stopColor;
1001   float stopOpacity;
1002   float stopOffset;
1003   ubyte hasFill;
1004   ubyte hasStroke;
1005   ubyte visible;
1006 }
1007 
1008 version(nanosvg_crappy_stylesheet_parser) {
1009 struct Style {
1010   const(char)[] name;
1011   const(char)[] value;
1012 }
1013 }
1014 
1015 struct Parser {
1016   Attrib[NSVG_MAX_ATTR] attr;
1017   int attrHead;
1018   float* stream;
1019   int nsflts;
1020   int csflts;
1021   NSVG.Path* plist;
1022   NSVG* image;
1023   GradientData* gradients;
1024   NSVG.Shape* shapesTail;
1025   float viewMinx, viewMiny, viewWidth, viewHeight;
1026   int alignX, alignY, alignType;
1027   float dpi;
1028   bool pathFlag;
1029   bool defsFlag;
1030   int canvaswdt = -1;
1031   int canvashgt = -1;
1032   version(nanosvg_crappy_stylesheet_parser) {
1033     Style* styles;
1034     uint styleCount;
1035     bool inStyle;
1036   }
1037 }
1038 
1039 const(char)[] fromAsciiz (const(char)[] s) {
1040   //foreach (immutable idx, char ch; s) if (!ch) return s[0..idx];
1041   //return s;
1042   if (s.length) {
1043     import core.stdc.string : memchr;
1044     if (auto zp = cast(const(char)*)memchr(s.ptr, 0, s.length)) return s[0..cast(usize)(zp-s.ptr)];
1045   }
1046   return s;
1047 }
1048 
1049 // ////////////////////////////////////////////////////////////////////////// //
1050 // matrix operations made public for the sake of... something.
1051 
1052 ///
1053 public void nsvg__xformIdentity (float* t) {
1054   t[0] = 1.0f; t[1] = 0.0f;
1055   t[2] = 0.0f; t[3] = 1.0f;
1056   t[4] = 0.0f; t[5] = 0.0f;
1057 }
1058 
1059 ///
1060 public void nsvg__xformSetTranslation (float* t, in float tx, in float ty) {
1061   t[0] = 1.0f; t[1] = 0.0f;
1062   t[2] = 0.0f; t[3] = 1.0f;
1063   t[4] = tx; t[5] = ty;
1064 }
1065 
1066 ///
1067 public void nsvg__xformSetScale (float* t, in float sx, in float sy) {
1068   t[0] = sx; t[1] = 0.0f;
1069   t[2] = 0.0f; t[3] = sy;
1070   t[4] = 0.0f; t[5] = 0.0f;
1071 }
1072 
1073 ///
1074 public void nsvg__xformSetSkewX (float* t, in float a) {
1075   t[0] = 1.0f; t[1] = 0.0f;
1076   t[2] = tanf(a); t[3] = 1.0f;
1077   t[4] = 0.0f; t[5] = 0.0f;
1078 }
1079 
1080 ///
1081 public void nsvg__xformSetSkewY (float* t, in float a) {
1082   t[0] = 1.0f; t[1] = tanf(a);
1083   t[2] = 0.0f; t[3] = 1.0f;
1084   t[4] = 0.0f; t[5] = 0.0f;
1085 }
1086 
1087 ///
1088 public void nsvg__xformSetRotation (float* t, in float a) {
1089   immutable cs = cosf(a), sn = sinf(a);
1090   t[0] = cs; t[1] = sn;
1091   t[2] = -sn; t[3] = cs;
1092   t[4] = 0.0f; t[5] = 0.0f;
1093 }
1094 
1095 ///
1096 public void nsvg__xformMultiply (float* t, const(float)* s) {
1097   immutable t0 = t[0]*s[0]+t[1]*s[2];
1098   immutable t2 = t[2]*s[0]+t[3]*s[2];
1099   immutable t4 = t[4]*s[0]+t[5]*s[2]+s[4];
1100   t[1] = t[0]*s[1]+t[1]*s[3];
1101   t[3] = t[2]*s[1]+t[3]*s[3];
1102   t[5] = t[4]*s[1]+t[5]*s[3]+s[5];
1103   t[0] = t0;
1104   t[2] = t2;
1105   t[4] = t4;
1106 }
1107 
1108 ///
1109 public void nsvg__xformInverse (float* inv, const(float)* t) {
1110   immutable double det = cast(double)t[0]*t[3]-cast(double)t[2]*t[1];
1111   if (det > -1e-6 && det < 1e-6) {
1112     nsvg__xformIdentity(inv);
1113     return;
1114   }
1115   immutable double invdet = 1.0/det;
1116   inv[0] = cast(float)(t[3]*invdet);
1117   inv[2] = cast(float)(-t[2]*invdet);
1118   inv[4] = cast(float)((cast(double)t[2]*t[5]-cast(double)t[3]*t[4])*invdet);
1119   inv[1] = cast(float)(-t[1]*invdet);
1120   inv[3] = cast(float)(t[0]*invdet);
1121   inv[5] = cast(float)((cast(double)t[1]*t[4]-cast(double)t[0]*t[5])*invdet);
1122 }
1123 
1124 ///
1125 public void nsvg__xformPremultiply (float* t, const(float)* s) {
1126   float[6] s2 = s[0..6];
1127   //memcpy(s2.ptr, s, float.sizeof*6);
1128   nsvg__xformMultiply(s2.ptr, t);
1129   //memcpy(t, s2.ptr, float.sizeof*6);
1130   t[0..6] = s2[];
1131 }
1132 
1133 ///
1134 public void nsvg__xformPoint (float* dx, float* dy, in float x, in float y, const(float)* t) {
1135   if (dx !is null) *dx = x*t[0]+y*t[2]+t[4];
1136   if (dy !is null) *dy = x*t[1]+y*t[3]+t[5];
1137 }
1138 
1139 ///
1140 public void nsvg__xformVec (float* dx, float* dy, in float x, in float y, const(float)* t) {
1141   if (dx !is null) *dx = x*t[0]+y*t[2];
1142   if (dy !is null) *dy = x*t[1]+y*t[3];
1143 }
1144 
1145 ///
1146 public enum NSVG_EPSILON = (1e-12);
1147 
1148 ///
1149 public int nsvg__ptInBounds (const(float)* pt, const(float)* bounds) {
1150   pragma(inline, true);
1151   return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
1152 }
1153 
1154 ///
1155 public double nsvg__evalBezier (double t, double p0, double p1, double p2, double p3) {
1156   pragma(inline, true);
1157   double it = 1.0-t;
1158   return it*it*it*p0+3.0*it*it*t*p1+3.0*it*t*t*p2+t*t*t*p3;
1159 }
1160 
1161 ///
1162 public void nsvg__curveBounds (float* bounds, const(float)* curve) {
1163   const float* v0 = &curve[0];
1164   const float* v1 = &curve[2];
1165   const float* v2 = &curve[4];
1166   const float* v3 = &curve[6];
1167 
1168   // Start the bounding box by end points
1169   bounds[0] = nsvg__minf(v0[0], v3[0]);
1170   bounds[1] = nsvg__minf(v0[1], v3[1]);
1171   bounds[2] = nsvg__maxf(v0[0], v3[0]);
1172   bounds[3] = nsvg__maxf(v0[1], v3[1]);
1173 
1174   // Bezier curve fits inside the convex hull of it's control points.
1175   // If control points are inside the bounds, we're done.
1176   if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) return;
1177 
1178   // Add bezier curve inflection points in X and Y.
1179   double[2] roots = void;
1180   foreach (int i; 0..2) {
1181     immutable double a = -3.0*v0[i]+9.0*v1[i]-9.0*v2[i]+3.0*v3[i];
1182     immutable double b = 6.0*v0[i]-12.0*v1[i]+6.0*v2[i];
1183     immutable double c = 3.0*v1[i]-3.0*v0[i];
1184     int count = 0;
1185     if (fabs(a) < NSVG_EPSILON) {
1186       if (fabs(b) > NSVG_EPSILON) {
1187         immutable double t = -c/b;
1188         if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
1189       }
1190     } else {
1191       immutable double b2ac = b*b-4.0*c*a;
1192       if (b2ac > NSVG_EPSILON) {
1193         double t = (-b+sqrt(b2ac))/(2.0*a);
1194         if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
1195         t = (-b-sqrt(b2ac))/(2.0*a);
1196         if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots.ptr[count++] = t;
1197       }
1198     }
1199     foreach (int j; 0..count) {
1200       immutable double v = nsvg__evalBezier(roots.ptr[j], v0[i], v1[i], v2[i], v3[i]);
1201       bounds[0+i] = nsvg__minf(bounds[0+i], cast(float)v);
1202       bounds[2+i] = nsvg__maxf(bounds[2+i], cast(float)v);
1203     }
1204   }
1205 }
1206 
1207 
1208 // ////////////////////////////////////////////////////////////////////////// //
1209 Parser* nsvg__createParser () {
1210   Parser* p = xalloc!Parser;
1211   if (p is null) goto error;
1212 
1213   p.image = xalloc!NSVG;
1214   if (p.image is null) goto error;
1215 
1216   // Init style
1217   nsvg__xformIdentity(p.attr[0].xform.ptr);
1218   p.attr[0].id[] = 0;
1219   p.attr[0].fillColor = NSVG.Paint.rgb(0, 0, 0);
1220   p.attr[0].strokeColor = NSVG.Paint.rgb(0, 0, 0);
1221   p.attr[0].opacity = 1;
1222   p.attr[0].fillOpacity = 1;
1223   p.attr[0].strokeOpacity = 1;
1224   p.attr[0].stopOpacity = 1;
1225   p.attr[0].strokeWidth = 1;
1226   p.attr[0].strokeLineJoin = NSVG.LineJoin.Miter;
1227   p.attr[0].strokeLineCap = NSVG.LineCap.Butt;
1228   p.attr[0].miterLimit = 4;
1229   p.attr[0].fillRule = NSVG.FillRule.EvenOdd;
1230   p.attr[0].hasFill = 1;
1231   p.attr[0].visible = 1;
1232 
1233   return p;
1234 
1235 error:
1236   if (p !is null) {
1237     xfree(p.image);
1238     xfree(p);
1239   }
1240   return null;
1241 }
1242 
1243 void nsvg__deletePaths (NSVG.Path* path) {
1244   while (path !is null) {
1245     NSVG.Path* next = path.next;
1246     xfree(path.stream);
1247     xfree(path);
1248     path = next;
1249   }
1250 }
1251 
1252 void nsvg__deletePaint (NSVG.Paint* paint) {
1253   if (paint.type == NSVG.PaintType.LinearGradient || paint.type == NSVG.PaintType.RadialGradient) xfree(paint.gradient);
1254 }
1255 
1256 void nsvg__deleteGradientData (GradientData* grad) {
1257   GradientData* next;
1258   while (grad !is null) {
1259     next = grad.next;
1260     xfree(grad.stops);
1261     xfree(grad);
1262     grad = next;
1263   }
1264 }
1265 
1266 void nsvg__deleteParser (Parser* p) {
1267   if (p !is null) {
1268     nsvg__deletePaths(p.plist);
1269     nsvg__deleteGradientData(p.gradients);
1270     kill(p.image);
1271     xfree(p.stream);
1272     version(nanosvg_crappy_stylesheet_parser) xfree(p.styles);
1273     xfree(p);
1274   }
1275 }
1276 
1277 void nsvg__resetPath (Parser* p) {
1278   p.nsflts = 0;
1279 }
1280 
1281 void nsvg__addToStream (Parser* p, in float v) {
1282   if (p.nsflts+1 > p.csflts) {
1283     import core.stdc.stdlib : realloc;
1284     p.csflts = (p.csflts == 0 ? 32 : p.csflts < 16384 ? p.csflts*2 : p.csflts+4096); //k8: arbitrary
1285     p.stream = cast(float*)realloc(p.stream, p.csflts*float.sizeof);
1286     if (p.stream is null) assert(0, "nanosvg: out of memory");
1287   }
1288   p.stream[p.nsflts++] = v;
1289 }
1290 
1291 void nsvg__addCommand (Parser* p, NSVG.Command c) {
1292   nsvg__addToStream(p, cast(float)c);
1293 }
1294 
1295 void nsvg__addPoint (Parser* p, in float x, in float y) {
1296   nsvg__addToStream(p, x);
1297   nsvg__addToStream(p, y);
1298 }
1299 
1300 void nsvg__moveTo (Parser* p, in float x, in float y) {
1301   // this is always called right after `nsvg__resetPath()`
1302   if (p.nsflts != 0) assert(0, "internal error in NanoVega.SVG");
1303   nsvg__addCommand(p, NSVG.Command.MoveTo);
1304   nsvg__addPoint(p, x, y);
1305   /*
1306   if (p.npts > 0) {
1307     p.pts[(p.npts-1)*2+0] = x;
1308     p.pts[(p.npts-1)*2+1] = y;
1309   } else {
1310     nsvg__addPoint(p, x, y);
1311   }
1312   */
1313 }
1314 
1315 void nsvg__lineTo (Parser* p, in float x, in float y) {
1316   if (p.nsflts > 0) {
1317     version(nanosvg_use_beziers) {
1318       immutable float px = p.pts[(p.npts-1)*2+0];
1319       immutable float py = p.pts[(p.npts-1)*2+1];
1320       immutable float dx = x-px;
1321       immutable float dy = y-py;
1322       nsvg__addCommand(NSVG.Command.BezierTo);
1323       nsvg__addPoint(p, px+dx/3.0f, py+dy/3.0f);
1324       nsvg__addPoint(p, x-dx/3.0f, y-dy/3.0f);
1325       nsvg__addPoint(p, x, y);
1326     } else {
1327       nsvg__addCommand(p, NSVG.Command.LineTo);
1328       nsvg__addPoint(p, x, y);
1329     }
1330   }
1331 }
1332 
1333 void nsvg__cubicBezTo (Parser* p, in float cpx1, in float cpy1, in float cpx2, in float cpy2, in float x, in float y) {
1334   nsvg__addCommand(p, NSVG.Command.BezierTo);
1335   nsvg__addPoint(p, cpx1, cpy1);
1336   nsvg__addPoint(p, cpx2, cpy2);
1337   nsvg__addPoint(p, x, y);
1338 }
1339 
1340 void nsvg__quadBezTo (Parser* p, in float cpx1, in float cpy1, in float x, in float y) {
1341   nsvg__addCommand(p, NSVG.Command.QuadTo);
1342   nsvg__addPoint(p, cpx1, cpy1);
1343   nsvg__addPoint(p, x, y);
1344 }
1345 
1346 Attrib* nsvg__getAttr (Parser* p) {
1347   return p.attr.ptr+p.attrHead;
1348 }
1349 
1350 void nsvg__pushAttr (Parser* p) {
1351   if (p.attrHead < NSVG_MAX_ATTR-1) {
1352     import core.stdc.string : memmove;
1353     ++p.attrHead;
1354     memmove(p.attr.ptr+p.attrHead, p.attr.ptr+(p.attrHead-1), Attrib.sizeof);
1355   }
1356 }
1357 
1358 void nsvg__popAttr (Parser* p) {
1359   if (p.attrHead > 0) --p.attrHead;
1360 }
1361 
1362 float nsvg__actualOrigX (Parser* p) { pragma(inline, true); return p.viewMinx; }
1363 float nsvg__actualOrigY (Parser* p) { pragma(inline, true); return p.viewMiny; }
1364 float nsvg__actualWidth (Parser* p) { pragma(inline, true); return p.viewWidth; }
1365 float nsvg__actualHeight (Parser* p) { pragma(inline, true); return p.viewHeight; }
1366 
1367 float nsvg__actualLength (Parser* p) {
1368   immutable float w = nsvg__actualWidth(p);
1369   immutable float h = nsvg__actualHeight(p);
1370   return sqrtf(w*w+h*h)/sqrtf(2.0f);
1371 }
1372 
1373 float nsvg__convertToPixels (Parser* p, Coordinate c, float orig, float length) {
1374   Attrib* attr = nsvg__getAttr(p);
1375   switch (c.units) {
1376     case Units.user: return c.value;
1377     case Units.px: return c.value;
1378     case Units.pt: return c.value/72.0f*p.dpi;
1379     case Units.pc: return c.value/6.0f*p.dpi;
1380     case Units.mm: return c.value/25.4f*p.dpi;
1381     case Units.cm: return c.value/2.54f*p.dpi;
1382     case Units.in_: return c.value*p.dpi;
1383     case Units.em: return c.value*attr.fontSize;
1384     case Units.ex: return c.value*attr.fontSize*0.52f; // x-height of Helvetica.
1385     case Units.percent: return orig+c.value/100.0f*length;
1386     default: return c.value;
1387   }
1388   assert(0);
1389   //return c.value;
1390 }
1391 
1392 GradientData* nsvg__findGradientData (Parser* p, const(char)[] id) {
1393   GradientData* grad = p.gradients;
1394   id = id.fromAsciiz;
1395   while (grad !is null) {
1396     if (grad.id.fromAsciiz == id) return grad;
1397     grad = grad.next;
1398   }
1399   return null;
1400 }
1401 
1402 NSVG.Gradient* nsvg__createGradient (Parser* p, const(char)[] id, const(float)* localBounds, NSVG.PaintType* paintType) {
1403   Attrib* attr = nsvg__getAttr(p);
1404   GradientData* data = null;
1405   GradientData* ref_ = null;
1406   NSVG.GradientStop* stops = null;
1407   NSVG.Gradient* grad;
1408   float ox = void, oy = void, sw = void, sh = void;
1409   int nstops = 0;
1410 
1411   id = id.fromAsciiz;
1412   data = nsvg__findGradientData(p, id);
1413   if (data is null) return null;
1414 
1415   // TODO: use ref_ to fill in all unset values too.
1416   ref_ = data;
1417   while (ref_ !is null) {
1418     if (stops is null && ref_.stops !is null) {
1419       stops = ref_.stops;
1420       nstops = ref_.nstops;
1421       break;
1422     }
1423     ref_ = nsvg__findGradientData(p, ref_.ref_[]);
1424   }
1425   if (stops is null) return null;
1426 
1427   grad = xalloc!(NSVG.Gradient)(NSVG.GradientStop.sizeof*nstops);
1428   if (grad is null) return null;
1429 
1430   // The shape width and height.
1431   if (data.units == GradientUnits.Object) {
1432     ox = localBounds[0];
1433     oy = localBounds[1];
1434     sw = localBounds[2]-localBounds[0];
1435     sh = localBounds[3]-localBounds[1];
1436   } else {
1437     ox = nsvg__actualOrigX(p);
1438     oy = nsvg__actualOrigY(p);
1439     sw = nsvg__actualWidth(p);
1440     sh = nsvg__actualHeight(p);
1441   }
1442   immutable float sl = sqrtf(sw*sw+sh*sh)/sqrtf(2.0f);
1443 
1444   if (data.type == NSVG.PaintType.LinearGradient) {
1445     immutable float x1 = nsvg__convertToPixels(p, data.linear.x1, ox, sw);
1446     immutable float y1 = nsvg__convertToPixels(p, data.linear.y1, oy, sh);
1447     immutable float x2 = nsvg__convertToPixels(p, data.linear.x2, ox, sw);
1448     immutable float y2 = nsvg__convertToPixels(p, data.linear.y2, oy, sh);
1449     // Calculate transform aligned to the line
1450     immutable float dx = x2-x1;
1451     immutable float dy = y2-y1;
1452     grad.xform[0] = dy; grad.xform[1] = -dx;
1453     grad.xform[2] = dx; grad.xform[3] = dy;
1454     grad.xform[4] = x1; grad.xform[5] = y1;
1455   } else {
1456     immutable float cx = nsvg__convertToPixels(p, data.radial.cx, ox, sw);
1457     immutable float cy = nsvg__convertToPixels(p, data.radial.cy, oy, sh);
1458     immutable float fx = nsvg__convertToPixels(p, data.radial.fx, ox, sw);
1459     immutable float fy = nsvg__convertToPixels(p, data.radial.fy, oy, sh);
1460     immutable float r = nsvg__convertToPixels(p, data.radial.r, 0, sl);
1461     // Calculate transform aligned to the circle
1462     grad.xform[0] = r; grad.xform[1] = 0;
1463     grad.xform[2] = 0; grad.xform[3] = r;
1464     grad.xform[4] = cx; grad.xform[5] = cy;
1465     // fix from https://github.com/memononen/nanosvg/issues/26#issuecomment-278713651
1466     grad.fx = (fx-cx)/r; // was fx/r;
1467     grad.fy = (fy-cy)/r; // was fy/r;
1468   }
1469 
1470   nsvg__xformMultiply(grad.xform.ptr, data.xform.ptr);
1471   nsvg__xformMultiply(grad.xform.ptr, attr.xform.ptr);
1472 
1473   grad.spread = data.spread;
1474   //memcpy(grad.stops.ptr, stops, nstops*NSVG.GradientStop.sizeof);
1475   grad.stops.ptr[0..nstops] = stops[0..nstops];
1476   grad.nstops = nstops;
1477 
1478   *paintType = data.type;
1479 
1480   return grad;
1481 }
1482 
1483 float nsvg__getAverageScale (float* t) {
1484   float sx = sqrtf(t[0]*t[0]+t[2]*t[2]);
1485   float sy = sqrtf(t[1]*t[1]+t[3]*t[3]);
1486   return (sx+sy)*0.5f;
1487 }
1488 
1489 void nsvg__quadBounds (float* bounds, const(float)* curve) nothrow @trusted @nogc {
1490   // cheat: convert quadratic bezier to cubic bezier
1491   immutable float cx = curve[0];
1492   immutable float cy = curve[1];
1493   immutable float x1 = curve[2];
1494   immutable float y1 = curve[3];
1495   immutable float x2 = curve[4];
1496   immutable float y2 = curve[5];
1497   immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
1498   immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
1499   immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
1500   immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
1501   float[8] cubic = void;
1502   cubic.ptr[0] = cx;
1503   cubic.ptr[1] = cy;
1504   cubic.ptr[2] = cx1;
1505   cubic.ptr[3] = cy1;
1506   cubic.ptr[4] = cx2;
1507   cubic.ptr[5] = cy2;
1508   cubic.ptr[6] = x2;
1509   cubic.ptr[7] = y2;
1510   nsvg__curveBounds(bounds, cubic.ptr);
1511 }
1512 
1513 void nsvg__getLocalBounds (float* bounds, NSVG.Shape* shape, const(float)* xform) {
1514   bool first = true;
1515 
1516   void addPoint (in float x, in float y) nothrow @trusted @nogc {
1517     if (!first) {
1518       bounds[0] = nsvg__minf(bounds[0], x);
1519       bounds[1] = nsvg__minf(bounds[1], y);
1520       bounds[2] = nsvg__maxf(bounds[2], x);
1521       bounds[3] = nsvg__maxf(bounds[3], y);
1522     } else {
1523       bounds[0] = bounds[2] = x;
1524       bounds[1] = bounds[3] = y;
1525       first = false;
1526     }
1527   }
1528 
1529   void addRect (in float x0, in float y0, in float x1, in float y1) nothrow @trusted @nogc {
1530     addPoint(x0, y0);
1531     addPoint(x1, y0);
1532     addPoint(x1, y1);
1533     addPoint(x0, y1);
1534   }
1535 
1536   float cx = 0, cy = 0;
1537   for (NSVG.Path* path = shape.paths; path !is null; path = path.next) {
1538     path.forEachCommand!false(delegate (NSVG.Command cmd, const(float)[] args) nothrow @trusted @nogc {
1539       import core.stdc.string : memmove;
1540       assert(args.length <= 6);
1541       float[8] xpt = void;
1542       // transform points
1543       foreach (immutable n; 0..args.length/2) {
1544         nsvg__xformPoint(&xpt.ptr[n*2+0], &xpt.ptr[n*2+1], args.ptr[n*2+0], args.ptr[n*2+1], xform);
1545       }
1546       // add to bounds
1547       final switch (cmd) {
1548         case NSVG.Command.MoveTo:
1549           cx = xpt.ptr[0];
1550           cy = xpt.ptr[1];
1551           break;
1552         case NSVG.Command.LineTo:
1553           addPoint(cx, cy);
1554           addPoint(xpt.ptr[0], xpt.ptr[1]);
1555           cx = xpt.ptr[0];
1556           cy = xpt.ptr[1];
1557           break;
1558         case NSVG.Command.QuadTo:
1559           memmove(xpt.ptr+2, xpt.ptr, 4); // make room for starting point
1560           xpt.ptr[0] = cx;
1561           xpt.ptr[1] = cy;
1562           float[4] curveBounds = void;
1563           nsvg__quadBounds(curveBounds.ptr, xpt.ptr);
1564           addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
1565           cx = xpt.ptr[4];
1566           cy = xpt.ptr[5];
1567           break;
1568         case NSVG.Command.BezierTo:
1569           memmove(xpt.ptr+2, xpt.ptr, 6); // make room for starting point
1570           xpt.ptr[0] = cx;
1571           xpt.ptr[1] = cy;
1572           float[4] curveBounds = void;
1573           nsvg__curveBounds(curveBounds.ptr, xpt.ptr);
1574           addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
1575           cx = xpt.ptr[6];
1576           cy = xpt.ptr[7];
1577           break;
1578       }
1579     });
1580     /*
1581     nsvg__xformPoint(&curve.ptr[0], &curve.ptr[1], path.pts[0], path.pts[1], xform);
1582     for (int i = 0; i < path.npts-1; i += 3) {
1583       nsvg__xformPoint(&curve.ptr[2], &curve.ptr[3], path.pts[(i+1)*2], path.pts[(i+1)*2+1], xform);
1584       nsvg__xformPoint(&curve.ptr[4], &curve.ptr[5], path.pts[(i+2)*2], path.pts[(i+2)*2+1], xform);
1585       nsvg__xformPoint(&curve.ptr[6], &curve.ptr[7], path.pts[(i+3)*2], path.pts[(i+3)*2+1], xform);
1586       nsvg__curveBounds(curveBounds.ptr, curve.ptr);
1587       if (first) {
1588         bounds[0] = curveBounds.ptr[0];
1589         bounds[1] = curveBounds.ptr[1];
1590         bounds[2] = curveBounds.ptr[2];
1591         bounds[3] = curveBounds.ptr[3];
1592         first = false;
1593       } else {
1594         bounds[0] = nsvg__minf(bounds[0], curveBounds.ptr[0]);
1595         bounds[1] = nsvg__minf(bounds[1], curveBounds.ptr[1]);
1596         bounds[2] = nsvg__maxf(bounds[2], curveBounds.ptr[2]);
1597         bounds[3] = nsvg__maxf(bounds[3], curveBounds.ptr[3]);
1598       }
1599       curve.ptr[0] = curve.ptr[6];
1600       curve.ptr[1] = curve.ptr[7];
1601     }
1602     */
1603   }
1604 }
1605 
1606 void nsvg__addShape (Parser* p) {
1607   Attrib* attr = nsvg__getAttr(p);
1608   float scale = 1.0f;
1609   NSVG.Shape* shape;
1610   NSVG.Path* path;
1611   int i;
1612 
1613   if (p.plist is null) return;
1614 
1615   shape = xalloc!(NSVG.Shape);
1616   if (shape is null) goto error;
1617   //memset(shape, 0, NSVG.Shape.sizeof);
1618 
1619   shape.id[] = attr.id[];
1620   scale = nsvg__getAverageScale(attr.xform.ptr);
1621   shape.strokeWidth = attr.strokeWidth*scale;
1622   shape.strokeDashOffset = attr.strokeDashOffset*scale;
1623   shape.strokeDashCount = cast(char)attr.strokeDashCount;
1624   for (i = 0; i < attr.strokeDashCount; i++) shape.strokeDashArray[i] = attr.strokeDashArray[i]*scale;
1625   shape.strokeLineJoin = attr.strokeLineJoin;
1626   shape.strokeLineCap = attr.strokeLineCap;
1627   shape.miterLimit = attr.miterLimit;
1628   shape.fillRule = attr.fillRule;
1629   shape.opacity = attr.opacity;
1630 
1631   shape.paths = p.plist;
1632   p.plist = null;
1633 
1634   // Calculate shape bounds
1635   shape.bounds.ptr[0] = shape.paths.bounds.ptr[0];
1636   shape.bounds.ptr[1] = shape.paths.bounds.ptr[1];
1637   shape.bounds.ptr[2] = shape.paths.bounds.ptr[2];
1638   shape.bounds.ptr[3] = shape.paths.bounds.ptr[3];
1639   for (path = shape.paths.next; path !is null; path = path.next) {
1640     shape.bounds.ptr[0] = nsvg__minf(shape.bounds.ptr[0], path.bounds[0]);
1641     shape.bounds.ptr[1] = nsvg__minf(shape.bounds.ptr[1], path.bounds[1]);
1642     shape.bounds.ptr[2] = nsvg__maxf(shape.bounds.ptr[2], path.bounds[2]);
1643     shape.bounds.ptr[3] = nsvg__maxf(shape.bounds.ptr[3], path.bounds[3]);
1644   }
1645 
1646   // Set fill
1647   if (attr.hasFill == 0) {
1648     shape.fill.type = NSVG.PaintType.None;
1649   } else if (attr.hasFill == 1) {
1650     shape.fill.type = NSVG.PaintType.Color;
1651     shape.fill.color = attr.fillColor;
1652     shape.fill.color |= cast(uint)(attr.fillOpacity*255)<<24;
1653   } else if (attr.hasFill == 2) {
1654     float[6] inv;
1655     float[4] localBounds;
1656     nsvg__xformInverse(inv.ptr, attr.xform.ptr);
1657     nsvg__getLocalBounds(localBounds.ptr, shape, inv.ptr);
1658     shape.fill.gradient = nsvg__createGradient(p, attr.fillGradient[], localBounds.ptr, &shape.fill.type);
1659     if (shape.fill.gradient is null) shape.fill.type = NSVG.PaintType.None;
1660   }
1661 
1662   // Set stroke
1663   if (attr.hasStroke == 0) {
1664     shape.stroke.type = NSVG.PaintType.None;
1665   } else if (attr.hasStroke == 1) {
1666     shape.stroke.type = NSVG.PaintType.Color;
1667     shape.stroke.color = attr.strokeColor;
1668     shape.stroke.color |= cast(uint)(attr.strokeOpacity*255)<<24;
1669   } else if (attr.hasStroke == 2) {
1670     float[6] inv;
1671     float[4] localBounds;
1672     nsvg__xformInverse(inv.ptr, attr.xform.ptr);
1673     nsvg__getLocalBounds(localBounds.ptr, shape, inv.ptr);
1674     shape.stroke.gradient = nsvg__createGradient(p, attr.strokeGradient[], localBounds.ptr, &shape.stroke.type);
1675     if (shape.stroke.gradient is null) shape.stroke.type = NSVG.PaintType.None;
1676   }
1677 
1678   // Set flags
1679   shape.flags = (attr.visible ? NSVG.Visible : 0x00);
1680 
1681   // Add to tail
1682   if (p.image.shapes is null)
1683     p.image.shapes = shape;
1684   else
1685     p.shapesTail.next = shape;
1686 
1687   p.shapesTail = shape;
1688 
1689   return;
1690 
1691 error:
1692   if (shape) xfree(shape);
1693 }
1694 
1695 void nsvg__addPath (Parser* p, bool closed) {
1696   Attrib* attr = nsvg__getAttr(p);
1697 
1698   if (p.nsflts < 4) return;
1699 
1700   if (closed) {
1701     auto cmd = cast(NSVG.Command)p.stream[0];
1702     if (cmd != NSVG.Command.MoveTo) assert(0, "NanoVega.SVG: invalid path");
1703     nsvg__lineTo(p, p.stream[1], p.stream[2]);
1704   }
1705 
1706   float cx = 0, cy = 0;
1707   float[4] bounds = void;
1708   bool first = true;
1709 
1710   NSVG.Path* path = xalloc!(NSVG.Path);
1711   if (path is null) goto error;
1712   //memset(path, 0, NSVG.Path.sizeof);
1713 
1714   path.stream = xcalloc!float(p.nsflts);
1715   if (path.stream is null) goto error;
1716   path.closed = closed;
1717   path.nsflts = p.nsflts;
1718 
1719   // transform path and calculate bounds
1720   void addPoint (in float x, in float y) nothrow @trusted @nogc {
1721     if (!first) {
1722       bounds[0] = nsvg__minf(bounds[0], x);
1723       bounds[1] = nsvg__minf(bounds[1], y);
1724       bounds[2] = nsvg__maxf(bounds[2], x);
1725       bounds[3] = nsvg__maxf(bounds[3], y);
1726     } else {
1727       bounds[0] = bounds[2] = x;
1728       bounds[1] = bounds[3] = y;
1729       first = false;
1730     }
1731   }
1732 
1733   void addRect (in float x0, in float y0, in float x1, in float y1) nothrow @trusted @nogc {
1734     addPoint(x0, y0);
1735     addPoint(x1, y0);
1736     addPoint(x1, y1);
1737     addPoint(x0, y1);
1738   }
1739 
1740   version(none) {
1741     foreach (immutable idx, float f; p.stream[0..p.nsflts]) {
1742       import core.stdc.stdio;
1743       printf("idx=%u; f=%g\n", cast(uint)idx, cast(double)f);
1744     }
1745   }
1746 
1747   for (int i = 0; i+3 <= p.nsflts; ) {
1748     int argc = 0; // pair of coords
1749     NSVG.Command cmd = cast(NSVG.Command)p.stream[i];
1750     final switch (cmd) {
1751       case NSVG.Command.MoveTo: argc = 1; break;
1752       case NSVG.Command.LineTo: argc = 1; break;
1753       case NSVG.Command.QuadTo: argc = 2; break;
1754       case NSVG.Command.BezierTo: argc = 3; break;
1755     }
1756     // copy command
1757     path.stream[i] = p.stream[i];
1758     ++i;
1759     auto starti = i;
1760     // transform points
1761     while (argc-- > 0) {
1762       nsvg__xformPoint(&path.stream[i+0], &path.stream[i+1], p.stream[i+0], p.stream[i+1], attr.xform.ptr);
1763       i += 2;
1764     }
1765     // do bounds
1766     final switch (cmd) {
1767       case NSVG.Command.MoveTo:
1768         cx = path.stream[starti+0];
1769         cy = path.stream[starti+1];
1770         break;
1771       case NSVG.Command.LineTo:
1772         addPoint(cx, cy);
1773         cx = path.stream[starti+0];
1774         cy = path.stream[starti+1];
1775         addPoint(cx, cy);
1776         break;
1777       case NSVG.Command.QuadTo:
1778         float[6] curve = void;
1779         curve.ptr[0] = cx;
1780         curve.ptr[1] = cy;
1781         curve.ptr[2..6] = path.stream[starti+0..starti+4];
1782         cx = path.stream[starti+2];
1783         cy = path.stream[starti+3];
1784         float[4] curveBounds = void;
1785         nsvg__quadBounds(curveBounds.ptr, curve.ptr);
1786         addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
1787         break;
1788       case NSVG.Command.BezierTo:
1789         float[8] curve = void;
1790         curve.ptr[0] = cx;
1791         curve.ptr[1] = cy;
1792         curve.ptr[2..8] = path.stream[starti+0..starti+6];
1793         cx = path.stream[starti+4];
1794         cy = path.stream[starti+5];
1795         float[4] curveBounds = void;
1796         nsvg__curveBounds(curveBounds.ptr, curve.ptr);
1797         addRect(curveBounds.ptr[0], curveBounds.ptr[1], curveBounds.ptr[2], curveBounds.ptr[3]);
1798         break;
1799     }
1800   }
1801   path.bounds[0..4] = bounds[0..4];
1802 
1803   path.next = p.plist;
1804   p.plist = path;
1805 
1806   return;
1807 
1808 error:
1809   if (path !is null) {
1810     if (path.stream !is null) xfree(path.stream);
1811     xfree(path);
1812   }
1813 }
1814 
1815 // We roll our own string to float because the std library one uses locale and messes things up.
1816 // special hack: stop at '\0' (actually, it stops on any non-digit, so no special code is required)
1817 float nsvg__atof (const(char)[] s) nothrow @trusted @nogc {
1818   if (s.length == 0) return 0; // oops
1819 
1820   const(char)* cur = s.ptr;
1821   auto left = s.length;
1822   double res = 0.0, sign = 1.0;
1823   bool hasIntPart = false, hasFracPart = false;
1824 
1825   char peekChar () nothrow @trusted @nogc { pragma(inline, true); return (left > 0 ? *cur : '\x00'); }
1826   char getChar () nothrow @trusted @nogc { if (left > 0) { --left; return *cur++; } else return '\x00'; }
1827 
1828   // Parse optional sign
1829   switch (peekChar) {
1830     case '-': sign = -1; goto case;
1831     case '+': getChar(); break;
1832     default: break;
1833   }
1834 
1835   // Parse integer part
1836   if (nsvg__isdigit(peekChar)) {
1837     // Parse digit sequence
1838     hasIntPart = true;
1839     while (nsvg__isdigit(peekChar)) res = res*10.0+(getChar()-'0');
1840   }
1841 
1842   // Parse fractional part.
1843   if (peekChar == '.') {
1844     getChar(); // Skip '.'
1845     if (nsvg__isdigit(peekChar)) {
1846       // Parse digit sequence
1847       hasFracPart = true;
1848       int divisor = 1;
1849       long num = 0;
1850       while (nsvg__isdigit(peekChar)) {
1851         divisor *= 10;
1852         num = num*10+(getChar()-'0');
1853       }
1854       res += cast(double)num/divisor;
1855     }
1856   }
1857 
1858   // A valid number should have integer or fractional part.
1859   if (!hasIntPart && !hasFracPart) return 0;
1860 
1861   // Parse optional exponent
1862   if (peekChar == 'e' || peekChar == 'E') {
1863     getChar(); // skip 'E'
1864     // parse optional sign
1865     bool epositive = true;
1866     switch (peekChar) {
1867       case '-': epositive = false; goto case;
1868       case '+': getChar(); break;
1869       default: break;
1870     }
1871     int expPart = 0;
1872     while (nsvg__isdigit(peekChar)) expPart = expPart*10+(getChar()-'0');
1873     if (epositive) {
1874       foreach (immutable _; 0..expPart) res *= 10.0;
1875     } else {
1876       foreach (immutable _; 0..expPart) res /= 10.0;
1877     }
1878   }
1879 
1880   return cast(float)(res*sign);
1881 }
1882 
1883 // `it` should be big enough
1884 // returns number of chars eaten
1885 int nsvg__parseNumber (const(char)[] s, char[] it) {
1886   int i = 0;
1887   it[] = 0;
1888 
1889   const(char)[] os = s;
1890 
1891   // sign
1892   if (s.length && (s[0] == '-' || s[0] == '+')) {
1893     if (it.length-i > 1) it[i++] = s[0];
1894     s = s[1..$];
1895   }
1896   // integer part
1897   while (s.length && nsvg__isdigit(s[0])) {
1898     if (it.length-i > 1) it[i++] = s[0];
1899     s = s[1..$];
1900   }
1901   if (s.length && s[0] == '.') {
1902     // decimal point
1903     if (it.length-i > 1) it[i++] = s[0];
1904     s = s[1..$];
1905     // fraction part
1906     while (s.length && nsvg__isdigit(s[0])) {
1907       if (it.length-i > 1) it[i++] = s[0];
1908       s = s[1..$];
1909     }
1910   }
1911   // exponent
1912   if (s.length && (s[0] == 'e' || s[0] == 'E')) {
1913     if (it.length-i > 1) it[i++] = s[0];
1914     s = s[1..$];
1915     if (s.length && (s[0] == '-' || s[0] == '+')) {
1916       if (it.length-i > 1) it[i++] = s[0];
1917       s = s[1..$];
1918     }
1919     while (s.length && nsvg__isdigit(s[0])) {
1920       if (it.length-i > 1) it[i++] = s[0];
1921       s = s[1..$];
1922     }
1923   }
1924 
1925   return cast(int)(s.ptr-os.ptr);
1926 }
1927 
1928 // `it` should be big enough
1929 int nsvg__getNextPathItem (const(char)[] s, char[] it) {
1930   int res = 0;
1931   it[] = '\0';
1932   // skip white spaces and commas
1933   while (res < s.length && (nsvg__isspace(s[res]) || s[res] == ',')) ++res;
1934   if (res >= s.length) return cast(int)s.length;
1935   if (s[res] == '-' || s[res] == '+' || s[res] == '.' || nsvg__isdigit(s[res])) {
1936     res += nsvg__parseNumber(s[res..$], it);
1937   } else if (s.length) {
1938     // Parse command
1939     it[0] = s[res++];
1940   }
1941   return res;
1942 }
1943 
1944 uint nsvg__parseColorHex (const(char)[] str) {
1945   char[12] tmp = 0;
1946   uint c = 0;
1947   ubyte r = 0, g = 0, b = 0;
1948   int n = 0;
1949   if (str.length) str = str[1..$]; // skip #
1950   // calculate number of characters
1951   while (n < str.length && !nsvg__isspace(str[n])) ++n;
1952   if (n == 3 || n == 6) {
1953     foreach (char ch; str[0..n]) {
1954       auto d0 = nsvg__hexdigit(ch);
1955       if (d0 < 0) break;
1956       c = c*16+d0;
1957     }
1958     if (n == 3) {
1959       c = (c&0xf)|((c&0xf0)<<4)|((c&0xf00)<<8);
1960       c |= c<<4;
1961     }
1962   }
1963   r = (c>>16)&0xff;
1964   g = (c>>8)&0xff;
1965   b = c&0xff;
1966   return NSVG.Paint.rgb(r, g, b);
1967 }
1968 
1969 uint nsvg__parseColorRGB (const(char)[] str) {
1970   int r = -1, g = -1, b = -1;
1971   const(char)[] s1, s2;
1972   assert(str.length > 4);
1973   xsscanf(str[4..$], "%d%[%%, \t]%d%[%%, \t]%d", r, s1, g, s2, b);
1974   if (s1[].xindexOf('%') >= 0) {
1975     return NSVG.Paint.rgb(cast(ubyte)((r*255)/100), cast(ubyte)((g*255)/100), cast(ubyte)((b*255)/100));
1976   } else {
1977     return NSVG.Paint.rgb(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
1978   }
1979 }
1980 
1981 struct NSVGNamedColor {
1982   string name;
1983   uint color;
1984 }
1985 
1986 static immutable NSVGNamedColor[147] nsvg__colors = [
1987   NSVGNamedColor("aliceblue", NSVG.Paint.rgb(240, 248, 255)),
1988   NSVGNamedColor("antiquewhite", NSVG.Paint.rgb(250, 235, 215)),
1989   NSVGNamedColor("aqua", NSVG.Paint.rgb( 0, 255, 255)),
1990   NSVGNamedColor("aquamarine", NSVG.Paint.rgb(127, 255, 212)),
1991   NSVGNamedColor("azure", NSVG.Paint.rgb(240, 255, 255)),
1992   NSVGNamedColor("beige", NSVG.Paint.rgb(245, 245, 220)),
1993   NSVGNamedColor("bisque", NSVG.Paint.rgb(255, 228, 196)),
1994   NSVGNamedColor("black", NSVG.Paint.rgb( 0, 0, 0)), // basic color
1995   NSVGNamedColor("blanchedalmond", NSVG.Paint.rgb(255, 235, 205)),
1996   NSVGNamedColor("blue", NSVG.Paint.rgb( 0, 0, 255)), // basic color
1997   NSVGNamedColor("blueviolet", NSVG.Paint.rgb(138, 43, 226)),
1998   NSVGNamedColor("brown", NSVG.Paint.rgb(165, 42, 42)),
1999   NSVGNamedColor("burlywood", NSVG.Paint.rgb(222, 184, 135)),
2000   NSVGNamedColor("cadetblue", NSVG.Paint.rgb( 95, 158, 160)),
2001   NSVGNamedColor("chartreuse", NSVG.Paint.rgb(127, 255, 0)),
2002   NSVGNamedColor("chocolate", NSVG.Paint.rgb(210, 105, 30)),
2003   NSVGNamedColor("coral", NSVG.Paint.rgb(255, 127, 80)),
2004   NSVGNamedColor("cornflowerblue", NSVG.Paint.rgb(100, 149, 237)),
2005   NSVGNamedColor("cornsilk", NSVG.Paint.rgb(255, 248, 220)),
2006   NSVGNamedColor("crimson", NSVG.Paint.rgb(220, 20, 60)),
2007   NSVGNamedColor("cyan", NSVG.Paint.rgb( 0, 255, 255)), // basic color
2008   NSVGNamedColor("darkblue", NSVG.Paint.rgb( 0, 0, 139)),
2009   NSVGNamedColor("darkcyan", NSVG.Paint.rgb( 0, 139, 139)),
2010   NSVGNamedColor("darkgoldenrod", NSVG.Paint.rgb(184, 134, 11)),
2011   NSVGNamedColor("darkgray", NSVG.Paint.rgb(169, 169, 169)),
2012   NSVGNamedColor("darkgreen", NSVG.Paint.rgb( 0, 100, 0)),
2013   NSVGNamedColor("darkgrey", NSVG.Paint.rgb(169, 169, 169)),
2014   NSVGNamedColor("darkkhaki", NSVG.Paint.rgb(189, 183, 107)),
2015   NSVGNamedColor("darkmagenta", NSVG.Paint.rgb(139, 0, 139)),
2016   NSVGNamedColor("darkolivegreen", NSVG.Paint.rgb( 85, 107, 47)),
2017   NSVGNamedColor("darkorange", NSVG.Paint.rgb(255, 140, 0)),
2018   NSVGNamedColor("darkorchid", NSVG.Paint.rgb(153, 50, 204)),
2019   NSVGNamedColor("darkred", NSVG.Paint.rgb(139, 0, 0)),
2020   NSVGNamedColor("darksalmon", NSVG.Paint.rgb(233, 150, 122)),
2021   NSVGNamedColor("darkseagreen", NSVG.Paint.rgb(143, 188, 143)),
2022   NSVGNamedColor("darkslateblue", NSVG.Paint.rgb( 72, 61, 139)),
2023   NSVGNamedColor("darkslategray", NSVG.Paint.rgb( 47, 79, 79)),
2024   NSVGNamedColor("darkslategrey", NSVG.Paint.rgb( 47, 79, 79)),
2025   NSVGNamedColor("darkturquoise", NSVG.Paint.rgb( 0, 206, 209)),
2026   NSVGNamedColor("darkviolet", NSVG.Paint.rgb(148, 0, 211)),
2027   NSVGNamedColor("deeppink", NSVG.Paint.rgb(255, 20, 147)),
2028   NSVGNamedColor("deepskyblue", NSVG.Paint.rgb( 0, 191, 255)),
2029   NSVGNamedColor("dimgray", NSVG.Paint.rgb(105, 105, 105)),
2030   NSVGNamedColor("dimgrey", NSVG.Paint.rgb(105, 105, 105)),
2031   NSVGNamedColor("dodgerblue", NSVG.Paint.rgb( 30, 144, 255)),
2032   NSVGNamedColor("firebrick", NSVG.Paint.rgb(178, 34, 34)),
2033   NSVGNamedColor("floralwhite", NSVG.Paint.rgb(255, 250, 240)),
2034   NSVGNamedColor("forestgreen", NSVG.Paint.rgb( 34, 139, 34)),
2035   NSVGNamedColor("fuchsia", NSVG.Paint.rgb(255, 0, 255)),
2036   NSVGNamedColor("gainsboro", NSVG.Paint.rgb(220, 220, 220)),
2037   NSVGNamedColor("ghostwhite", NSVG.Paint.rgb(248, 248, 255)),
2038   NSVGNamedColor("gold", NSVG.Paint.rgb(255, 215, 0)),
2039   NSVGNamedColor("goldenrod", NSVG.Paint.rgb(218, 165, 32)),
2040   NSVGNamedColor("gray", NSVG.Paint.rgb(128, 128, 128)), // basic color
2041   NSVGNamedColor("green", NSVG.Paint.rgb( 0, 128, 0)), // basic color
2042   NSVGNamedColor("greenyellow", NSVG.Paint.rgb(173, 255, 47)),
2043   NSVGNamedColor("grey", NSVG.Paint.rgb(128, 128, 128)), // basic color
2044   NSVGNamedColor("honeydew", NSVG.Paint.rgb(240, 255, 240)),
2045   NSVGNamedColor("hotpink", NSVG.Paint.rgb(255, 105, 180)),
2046   NSVGNamedColor("indianred", NSVG.Paint.rgb(205, 92, 92)),
2047   NSVGNamedColor("indigo", NSVG.Paint.rgb( 75, 0, 130)),
2048   NSVGNamedColor("ivory", NSVG.Paint.rgb(255, 255, 240)),
2049   NSVGNamedColor("khaki", NSVG.Paint.rgb(240, 230, 140)),
2050   NSVGNamedColor("lavender", NSVG.Paint.rgb(230, 230, 250)),
2051   NSVGNamedColor("lavenderblush", NSVG.Paint.rgb(255, 240, 245)),
2052   NSVGNamedColor("lawngreen", NSVG.Paint.rgb(124, 252, 0)),
2053   NSVGNamedColor("lemonchiffon", NSVG.Paint.rgb(255, 250, 205)),
2054   NSVGNamedColor("lightblue", NSVG.Paint.rgb(173, 216, 230)),
2055   NSVGNamedColor("lightcoral", NSVG.Paint.rgb(240, 128, 128)),
2056   NSVGNamedColor("lightcyan", NSVG.Paint.rgb(224, 255, 255)),
2057   NSVGNamedColor("lightgoldenrodyellow", NSVG.Paint.rgb(250, 250, 210)),
2058   NSVGNamedColor("lightgray", NSVG.Paint.rgb(211, 211, 211)),
2059   NSVGNamedColor("lightgreen", NSVG.Paint.rgb(144, 238, 144)),
2060   NSVGNamedColor("lightgrey", NSVG.Paint.rgb(211, 211, 211)),
2061   NSVGNamedColor("lightpink", NSVG.Paint.rgb(255, 182, 193)),
2062   NSVGNamedColor("lightsalmon", NSVG.Paint.rgb(255, 160, 122)),
2063   NSVGNamedColor("lightseagreen", NSVG.Paint.rgb( 32, 178, 170)),
2064   NSVGNamedColor("lightskyblue", NSVG.Paint.rgb(135, 206, 250)),
2065   NSVGNamedColor("lightslategray", NSVG.Paint.rgb(119, 136, 153)),
2066   NSVGNamedColor("lightslategrey", NSVG.Paint.rgb(119, 136, 153)),
2067   NSVGNamedColor("lightsteelblue", NSVG.Paint.rgb(176, 196, 222)),
2068   NSVGNamedColor("lightyellow", NSVG.Paint.rgb(255, 255, 224)),
2069   NSVGNamedColor("lime", NSVG.Paint.rgb( 0, 255, 0)),
2070   NSVGNamedColor("limegreen", NSVG.Paint.rgb( 50, 205, 50)),
2071   NSVGNamedColor("linen", NSVG.Paint.rgb(250, 240, 230)),
2072   NSVGNamedColor("magenta", NSVG.Paint.rgb(255, 0, 255)), // basic color
2073   NSVGNamedColor("maroon", NSVG.Paint.rgb(128, 0, 0)),
2074   NSVGNamedColor("mediumaquamarine", NSVG.Paint.rgb(102, 205, 170)),
2075   NSVGNamedColor("mediumblue", NSVG.Paint.rgb( 0, 0, 205)),
2076   NSVGNamedColor("mediumorchid", NSVG.Paint.rgb(186, 85, 211)),
2077   NSVGNamedColor("mediumpurple", NSVG.Paint.rgb(147, 112, 219)),
2078   NSVGNamedColor("mediumseagreen", NSVG.Paint.rgb( 60, 179, 113)),
2079   NSVGNamedColor("mediumslateblue", NSVG.Paint.rgb(123, 104, 238)),
2080   NSVGNamedColor("mediumspringgreen", NSVG.Paint.rgb( 0, 250, 154)),
2081   NSVGNamedColor("mediumturquoise", NSVG.Paint.rgb( 72, 209, 204)),
2082   NSVGNamedColor("mediumvioletred", NSVG.Paint.rgb(199, 21, 133)),
2083   NSVGNamedColor("midnightblue", NSVG.Paint.rgb( 25, 25, 112)),
2084   NSVGNamedColor("mintcream", NSVG.Paint.rgb(245, 255, 250)),
2085   NSVGNamedColor("mistyrose", NSVG.Paint.rgb(255, 228, 225)),
2086   NSVGNamedColor("moccasin", NSVG.Paint.rgb(255, 228, 181)),
2087   NSVGNamedColor("navajowhite", NSVG.Paint.rgb(255, 222, 173)),
2088   NSVGNamedColor("navy", NSVG.Paint.rgb( 0, 0, 128)),
2089   NSVGNamedColor("oldlace", NSVG.Paint.rgb(253, 245, 230)),
2090   NSVGNamedColor("olive", NSVG.Paint.rgb(128, 128, 0)),
2091   NSVGNamedColor("olivedrab", NSVG.Paint.rgb(107, 142, 35)),
2092   NSVGNamedColor("orange", NSVG.Paint.rgb(255, 165, 0)),
2093   NSVGNamedColor("orangered", NSVG.Paint.rgb(255, 69, 0)),
2094   NSVGNamedColor("orchid", NSVG.Paint.rgb(218, 112, 214)),
2095   NSVGNamedColor("palegoldenrod", NSVG.Paint.rgb(238, 232, 170)),
2096   NSVGNamedColor("palegreen", NSVG.Paint.rgb(152, 251, 152)),
2097   NSVGNamedColor("paleturquoise", NSVG.Paint.rgb(175, 238, 238)),
2098   NSVGNamedColor("palevioletred", NSVG.Paint.rgb(219, 112, 147)),
2099   NSVGNamedColor("papayawhip", NSVG.Paint.rgb(255, 239, 213)),
2100   NSVGNamedColor("peachpuff", NSVG.Paint.rgb(255, 218, 185)),
2101   NSVGNamedColor("peru", NSVG.Paint.rgb(205, 133, 63)),
2102   NSVGNamedColor("pink", NSVG.Paint.rgb(255, 192, 203)),
2103   NSVGNamedColor("plum", NSVG.Paint.rgb(221, 160, 221)),
2104   NSVGNamedColor("powderblue", NSVG.Paint.rgb(176, 224, 230)),
2105   NSVGNamedColor("purple", NSVG.Paint.rgb(128, 0, 128)),
2106   NSVGNamedColor("red", NSVG.Paint.rgb(255, 0, 0)), // basic color
2107   NSVGNamedColor("rosybrown", NSVG.Paint.rgb(188, 143, 143)),
2108   NSVGNamedColor("royalblue", NSVG.Paint.rgb( 65, 105, 225)),
2109   NSVGNamedColor("saddlebrown", NSVG.Paint.rgb(139, 69, 19)),
2110   NSVGNamedColor("salmon", NSVG.Paint.rgb(250, 128, 114)),
2111   NSVGNamedColor("sandybrown", NSVG.Paint.rgb(244, 164, 96)),
2112   NSVGNamedColor("seagreen", NSVG.Paint.rgb( 46, 139, 87)),
2113   NSVGNamedColor("seashell", NSVG.Paint.rgb(255, 245, 238)),
2114   NSVGNamedColor("sienna", NSVG.Paint.rgb(160, 82, 45)),
2115   NSVGNamedColor("silver", NSVG.Paint.rgb(192, 192, 192)),
2116   NSVGNamedColor("skyblue", NSVG.Paint.rgb(135, 206, 235)),
2117   NSVGNamedColor("slateblue", NSVG.Paint.rgb(106, 90, 205)),
2118   NSVGNamedColor("slategray", NSVG.Paint.rgb(112, 128, 144)),
2119   NSVGNamedColor("slategrey", NSVG.Paint.rgb(112, 128, 144)),
2120   NSVGNamedColor("snow", NSVG.Paint.rgb(255, 250, 250)),
2121   NSVGNamedColor("springgreen", NSVG.Paint.rgb( 0, 255, 127)),
2122   NSVGNamedColor("steelblue", NSVG.Paint.rgb( 70, 130, 180)),
2123   NSVGNamedColor("tan", NSVG.Paint.rgb(210, 180, 140)),
2124   NSVGNamedColor("teal", NSVG.Paint.rgb( 0, 128, 128)),
2125   NSVGNamedColor("thistle", NSVG.Paint.rgb(216, 191, 216)),
2126   NSVGNamedColor("tomato", NSVG.Paint.rgb(255, 99, 71)),
2127   NSVGNamedColor("turquoise", NSVG.Paint.rgb( 64, 224, 208)),
2128   NSVGNamedColor("violet", NSVG.Paint.rgb(238, 130, 238)),
2129   NSVGNamedColor("wheat", NSVG.Paint.rgb(245, 222, 179)),
2130   NSVGNamedColor("white", NSVG.Paint.rgb(255, 255, 255)), // basic color
2131   NSVGNamedColor("whitesmoke", NSVG.Paint.rgb(245, 245, 245)),
2132   NSVGNamedColor("yellow", NSVG.Paint.rgb(255, 255, 0)), // basic color
2133   NSVGNamedColor("yellowgreen", NSVG.Paint.rgb(154, 205, 50)),
2134 ];
2135 
2136 enum nsvg__color_name_maxlen = () {
2137   int res = 0;
2138   foreach (const ref known; nsvg__colors) if (res < known.name.length) res = cast(int)known.name.length;
2139   return res;
2140 }();
2141 
2142 
2143 // `s0` and `s1` are never empty here
2144 // `s0` is always lowercased
2145 int xstrcmp (const(char)[] s0, const(char)[] s1) {
2146   /*
2147   const(char)* sp0 = s0.ptr;
2148   const(char)* sp1 = s1.ptr;
2149   foreach (; 0..(s0.length < s1.length ? s0.length : s1.length)) {
2150     int c1 = cast(int)(*sp1++);
2151     if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
2152     if (auto diff = cast(int)(*sp0++)-c1) return diff;
2153   }
2154   // equals so far
2155   if (s0.length < s1.length) return -1;
2156   if (s0.length > s1.length) return 1;
2157   return 0;
2158   */
2159   import core.stdc.string : memcmp;
2160   if (auto diff = memcmp(s0.ptr, s1.ptr, (s0.length < s1.length ? s0.length : s1.length))) return diff;
2161   // equals so far
2162   if (s0.length < s1.length) return -1;
2163   if (s0.length > s1.length) return 1;
2164   return 0;
2165 }
2166 
2167 
2168 uint nsvg__parseColorName (const(char)[] str) {
2169   if (str.length == 0 || str.length > nsvg__color_name_maxlen) return NSVG.Paint.rgb(128, 128, 128);
2170   // check if `str` contains only letters, and convert it to lowercase
2171   char[nsvg__color_name_maxlen] slow = void;
2172   foreach (immutable cidx, char ch; str) {
2173     if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
2174     if (ch < 'a' || ch > 'z') return NSVG.Paint.rgb(128, 128, 128); // alas
2175     slow.ptr[cidx] = ch;
2176   }
2177   int low = 0;
2178   int high = cast(int)nsvg__colors.length-1;
2179   while (low <= high) {
2180     int med = (low+high)/2;
2181     assert(med >= 0 && med < nsvg__colors.length);
2182     int res = xstrcmp(nsvg__colors.ptr[med].name, str);
2183          if (res < 0) low = med+1;
2184     else if (res > 0) high = med-1;
2185     else return nsvg__colors.ptr[med].color;
2186   }
2187   return NSVG.Paint.rgb(128, 128, 128);
2188 }
2189 
2190 uint nsvg__parseColor (const(char)[] str) {
2191   while (str.length && str[0] <= ' ') str = str[1..$];
2192   if (str.length >= 1 && str[0] == '#') return nsvg__parseColorHex(str);
2193   if (str.length >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') return nsvg__parseColorRGB(str);
2194   return nsvg__parseColorName(str);
2195 }
2196 
2197 float nsvg__parseOpacity (const(char)[] str) {
2198   float val = 0;
2199   xsscanf(str, "%f", val);
2200   if (val < 0.0f) val = 0.0f;
2201   if (val > 1.0f) val = 1.0f;
2202   return val;
2203 }
2204 
2205 float nsvg__parseMiterLimit (const(char)[] str) {
2206   float val = 0;
2207   xsscanf(str, "%f", val);
2208   if (val < 0.0f) val = 0.0f;
2209   return val;
2210 }
2211 
2212 Units nsvg__parseUnits (const(char)[] units) {
2213   if (units.length && units.ptr[0] == '%') return Units.percent;
2214   if (units.length == 2) {
2215     if (units.ptr[0] == 'p' && units.ptr[1] == 'x') return Units.px;
2216     if (units.ptr[0] == 'p' && units.ptr[1] == 't') return Units.pt;
2217     if (units.ptr[0] == 'p' && units.ptr[1] == 'c') return Units.pc;
2218     if (units.ptr[0] == 'm' && units.ptr[1] == 'm') return Units.mm;
2219     if (units.ptr[0] == 'c' && units.ptr[1] == 'm') return Units.cm;
2220     if (units.ptr[0] == 'i' && units.ptr[1] == 'n') return Units.in_;
2221     if (units.ptr[0] == 'e' && units.ptr[1] == 'm') return Units.em;
2222     if (units.ptr[0] == 'e' && units.ptr[1] == 'x') return Units.ex;
2223   }
2224   return Units.user;
2225 }
2226 
2227 Coordinate nsvg__parseCoordinateRaw (const(char)[] str) {
2228   Coordinate coord = Coordinate(0, Units.user);
2229   const(char)[] units;
2230   xsscanf(str, "%f%s", coord.value, units);
2231   coord.units = nsvg__parseUnits(units);
2232   return coord;
2233 }
2234 
2235 Coordinate nsvg__coord (float v, Units units) {
2236   Coordinate coord = Coordinate(v, units);
2237   return coord;
2238 }
2239 
2240 float nsvg__parseCoordinate (Parser* p, const(char)[] str, float orig, float length) {
2241   Coordinate coord = nsvg__parseCoordinateRaw(str);
2242   return nsvg__convertToPixels(p, coord, orig, length);
2243 }
2244 
2245 int nsvg__parseTransformArgs (const(char)[] str, float* args, int maxNa, int* na) {
2246   usize end, ptr;
2247   char[65] it = void;
2248 
2249   assert(str.length);
2250   *na = 0;
2251 
2252   ptr = 0;
2253   while (ptr < str.length && str[ptr] != '(') ++ptr;
2254   if (ptr >= str.length) return 1;
2255 
2256   end = ptr;
2257   while (end < str.length && str[end] != ')') ++end;
2258   if (end >= str.length) return 1;
2259 
2260   while (ptr < end) {
2261     if (str[ptr] == '-' || str[ptr] == '+' || str[ptr] == '.' || nsvg__isdigit(str[ptr])) {
2262       if (*na >= maxNa) return 0;
2263       ptr += nsvg__parseNumber(str[ptr..end], it[]);
2264       args[(*na)++] = nsvg__atof(it[]); // `it` is guaranteed to be asciiz, and `nsvg__atof()` will stop
2265     } else {
2266       ++ptr;
2267     }
2268   }
2269   return cast(int)end; // fuck off, 64bit
2270 }
2271 
2272 
2273 int nsvg__parseMatrix (float* xform, const(char)[] str) {
2274   float[6] t = void;
2275   int na = 0;
2276   int len = nsvg__parseTransformArgs(str, t.ptr, 6, &na);
2277   if (na != 6) return len;
2278   xform[0..6] = t[];
2279   return len;
2280 }
2281 
2282 int nsvg__parseTranslate (float* xform, const(char)[] str) {
2283   float[2] args = void;
2284   float[6] t = void;
2285   int na = 0;
2286   int len = nsvg__parseTransformArgs(str, args.ptr, 2, &na);
2287   if (na == 1) args[1] = 0.0;
2288   nsvg__xformSetTranslation(t.ptr, args.ptr[0], args.ptr[1]);
2289   xform[0..6] = t[];
2290   return len;
2291 }
2292 
2293 int nsvg__parseScale (float* xform, const(char)[] str) {
2294   float[2] args = void;
2295   int na = 0;
2296   float[6] t = void;
2297   int len = nsvg__parseTransformArgs(str, args.ptr, 2, &na);
2298   if (na == 1) args.ptr[1] = args.ptr[0];
2299   nsvg__xformSetScale(t.ptr, args.ptr[0], args.ptr[1]);
2300   xform[0..6] = t[];
2301   return len;
2302 }
2303 
2304 int nsvg__parseSkewX (float* xform, const(char)[] str) {
2305   float[1] args = void;
2306   int na = 0;
2307   float[6] t = void;
2308   int len = nsvg__parseTransformArgs(str, args.ptr, 1, &na);
2309   nsvg__xformSetSkewX(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
2310   xform[0..6] = t[];
2311   return len;
2312 }
2313 
2314 int nsvg__parseSkewY (float* xform, const(char)[] str) {
2315   float[1] args = void;
2316   int na = 0;
2317   float[6] t = void;
2318   int len = nsvg__parseTransformArgs(str, args.ptr, 1, &na);
2319   nsvg__xformSetSkewY(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
2320   xform[0..6] = t[];
2321   return len;
2322 }
2323 
2324 int nsvg__parseRotate (float* xform, const(char)[] str) {
2325   float[3] args = void;
2326   int na = 0;
2327   float[6] m = void;
2328   float[6] t = void;
2329   int len = nsvg__parseTransformArgs(str, args.ptr, 3, &na);
2330   if (na == 1) args.ptr[1] = args.ptr[2] = 0.0f;
2331   nsvg__xformIdentity(m.ptr);
2332 
2333   if (na > 1) {
2334     nsvg__xformSetTranslation(t.ptr, -args.ptr[1], -args.ptr[2]);
2335     nsvg__xformMultiply(m.ptr, t.ptr);
2336   }
2337 
2338   nsvg__xformSetRotation(t.ptr, args.ptr[0]/180.0f*NSVG_PI);
2339   nsvg__xformMultiply(m.ptr, t.ptr);
2340 
2341   if (na > 1) {
2342     nsvg__xformSetTranslation(t.ptr, args.ptr[1], args.ptr[2]);
2343     nsvg__xformMultiply(m.ptr, t.ptr);
2344   }
2345 
2346   xform[0..6] = m[];
2347 
2348   return len;
2349 }
2350 
2351 bool startsWith (const(char)[] str, const(char)[] sw) {
2352   pragma(inline, true);
2353   return (sw.length <= str.length && str[0..sw.length] == sw[]);
2354 }
2355 
2356 void nsvg__parseTransform (float* xform, const(char)[] str) {
2357   float[6] t = void;
2358   nsvg__xformIdentity(xform);
2359   while (str.length) {
2360     int len;
2361          if (startsWith(str, "matrix")) len = nsvg__parseMatrix(t.ptr, str);
2362     else if (startsWith(str, "translate")) len = nsvg__parseTranslate(t.ptr, str);
2363     else if (startsWith(str, "scale")) len = nsvg__parseScale(t.ptr, str);
2364     else if (startsWith(str, "rotate")) len = nsvg__parseRotate(t.ptr, str);
2365     else if (startsWith(str, "skewX")) len = nsvg__parseSkewX(t.ptr, str);
2366     else if (startsWith(str, "skewY")) len = nsvg__parseSkewY(t.ptr, str);
2367     else { str = str[1..$]; continue; }
2368     str = str[len..$];
2369     nsvg__xformPremultiply(xform, t.ptr);
2370   }
2371 }
2372 
2373 // `id` should be prealloced
2374 void nsvg__parseUrl (char[] id, const(char)[] str) {
2375   int i = 0;
2376   if (str.length >= 4) {
2377     str = str[4..$]; // "url(";
2378     if (str.length && str[0] == '#') str = str[1..$];
2379     while (str.length && str[0] != ')') {
2380       if (id.length-i > 1) id[i++] = str[0];
2381       str = str[1..$];
2382     }
2383   }
2384   if (id.length-i > 0) id[i] = '\0';
2385 }
2386 
2387 NSVG.LineCap nsvg__parseLineCap (const(char)[] str) {
2388   if (str == "butt") return NSVG.LineCap.Butt;
2389   if (str == "round") return NSVG.LineCap.Round;
2390   if (str == "square") return NSVG.LineCap.Square;
2391   // TODO: handle inherit.
2392   return NSVG.LineCap.Butt;
2393 }
2394 
2395 NSVG.LineJoin nsvg__parseLineJoin (const(char)[] str) {
2396   if (str == "miter") return NSVG.LineJoin.Miter;
2397   if (str == "round") return NSVG.LineJoin.Round;
2398   if (str == "bevel") return NSVG.LineJoin.Bevel;
2399   // TODO: handle inherit.
2400   return NSVG.LineJoin.Miter;
2401 }
2402 
2403 NSVG.FillRule nsvg__parseFillRule (const(char)[] str) {
2404   if (str == "nonzero") return NSVG.FillRule.NonZero;
2405   if (str == "evenodd") return NSVG.FillRule.EvenOdd;
2406   // TODO: handle inherit.
2407   return NSVG.FillRule.EvenOdd;
2408 }
2409 
2410 
2411 int nsvg__parseStrokeDashArray (Parser* p, const(char)[] str, float* strokeDashArray) {
2412   char[65] item = 0;
2413   int count = 0;
2414   float sum = 0.0f;
2415 
2416   int nsvg__getNextDashItem () {
2417     int n = 0;
2418     item[] = '\0';
2419     // skip white spaces and commas
2420     while (str.length && (nsvg__isspace(str[0]) || str[0] == ',')) str = str[1..$];
2421     // advance until whitespace, comma or end
2422     while (str.length && (!nsvg__isspace(str[0]) && str[0] != ',')) {
2423       if (item.length-n > 1) item[n++] = str[0];
2424       str = str[1..$];
2425     }
2426     return n;
2427   }
2428 
2429   // Handle "none"
2430   if (!str.length || str[0] == 'n') return 0;
2431 
2432   // Parse dashes
2433   while (str.length) {
2434     auto len = nsvg__getNextDashItem();
2435     if (len < 1) break;
2436     if (count < NSVG_MAX_DASHES) strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item[0..len], 0.0f, nsvg__actualLength(p)));
2437   }
2438 
2439   foreach (int i; 0..count) sum += strokeDashArray[i];
2440   if (sum <= 1e-6f) count = 0;
2441 
2442   return count;
2443 }
2444 
2445 const(char)[] trimLeft (const(char)[] s, char ech=0) {
2446   usize pos = 0;
2447   while (pos < s.length) {
2448     if (s.ptr[pos] <= ' ') { ++pos; continue; }
2449     if (ech && s.ptr[pos] == ech) { ++pos; continue; }
2450     if (s.ptr[pos] == '/' && s.length-pos > 1 && s.ptr[pos+1] == '*') {
2451       pos += 2;
2452       while (s.length-pos > 1 && !(s.ptr[pos] == '*' && s.ptr[pos+1] == '/')) ++pos;
2453       if ((pos += 2) > s.length) pos = s.length;
2454       continue;
2455     }
2456     break;
2457   }
2458   return s[pos..$];
2459 }
2460 
2461 static const(char)[] trimRight (const(char)[] s, char ech=0) {
2462   usize pos = 0;
2463   while (pos < s.length) {
2464     if (s.ptr[pos] <= ' ' || (ech && s.ptr[pos] == ech)) {
2465       if (s[pos..$].trimLeft(ech).length == 0) return s[0..pos];
2466     } else if (s.ptr[pos] == '/' && s.length-pos > 1 && s.ptr[pos+1] == '*') {
2467       if (s[pos..$].trimLeft(ech).length == 0) return s[0..pos];
2468     }
2469     ++pos;
2470   }
2471   return s;
2472 }
2473 
2474 version(nanosvg_crappy_stylesheet_parser) {
2475 Style* findStyle (Parser* p, char fch, const(char)[] name) {
2476   if (name.length == 0) return null;
2477   foreach (ref st; p.styles[0..p.styleCount]) {
2478     if (st.name.length < 2 || st.name.ptr[0] != fch) continue;
2479     if (st.name[1..$] == name) return &st;
2480   }
2481   return null;
2482 }
2483 
2484 void nsvg__parseClassOrId (Parser* p, char lch, const(char)[] str) {
2485   while (str.length) {
2486     while (str.length && str.ptr[0] <= ' ') str = str[1..$];
2487     if (str.length == 0) break;
2488     usize pos = 1;
2489     while (pos < str.length && str.ptr[pos] > ' ') ++pos;
2490     version(nanosvg_debug_styles) { import std.stdio; writeln("class to find: ", lch, str[0..pos].quote); }
2491     if (auto st = p.findStyle(lch, str[0..pos])) {
2492       version(nanosvg_debug_styles) { import std.stdio; writeln("class: [", str[0..pos], "]; value: ", st.value.quote); }
2493       nsvg__parseStyle(p, st.value);
2494     }
2495     str = str[pos..$];
2496   }
2497 }
2498 }
2499 
2500 bool nsvg__parseAttr (Parser* p, const(char)[] name, const(char)[] value) {
2501   float[6] xform = void;
2502   Attrib* attr = nsvg__getAttr(p);
2503   if (attr is null) return false; //???
2504 
2505   if (name == "style") {
2506     nsvg__parseStyle(p, value);
2507   } else if (name == "display") {
2508     if (value == "none") attr.visible = 0;
2509     // Don't reset .visible on display:inline, one display:none hides the whole subtree
2510   } else if (name == "fill") {
2511     if (value == "none") {
2512       attr.hasFill = 0;
2513     } else if (startsWith(value, "url(")) {
2514       attr.hasFill = 2;
2515       nsvg__parseUrl(attr.fillGradient[], value);
2516     } else {
2517       attr.hasFill = 1;
2518       attr.fillColor = nsvg__parseColor(value);
2519     }
2520   } else if (name == "opacity") {
2521     attr.opacity = nsvg__parseOpacity(value);
2522   } else if (name == "fill-opacity") {
2523     attr.fillOpacity = nsvg__parseOpacity(value);
2524   } else if (name == "stroke") {
2525     if (value == "none") {
2526       attr.hasStroke = 0;
2527     } else if (startsWith(value, "url(")) {
2528       attr.hasStroke = 2;
2529       nsvg__parseUrl(attr.strokeGradient[], value);
2530     } else {
2531       attr.hasStroke = 1;
2532       attr.strokeColor = nsvg__parseColor(value);
2533     }
2534   } else if (name == "stroke-width") {
2535     attr.strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
2536   } else if (name == "stroke-dasharray") {
2537     attr.strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr.strokeDashArray.ptr);
2538   } else if (name == "stroke-dashoffset") {
2539     attr.strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
2540   } else if (name == "stroke-opacity") {
2541     attr.strokeOpacity = nsvg__parseOpacity(value);
2542   } else if (name == "stroke-linecap") {
2543     attr.strokeLineCap = nsvg__parseLineCap(value);
2544   } else if (name == "stroke-linejoin") {
2545     attr.strokeLineJoin = nsvg__parseLineJoin(value);
2546   } else if (name == "stroke-miterlimit") {
2547     attr.miterLimit = nsvg__parseMiterLimit(value);
2548   } else if (name == "fill-rule") {
2549     attr.fillRule = nsvg__parseFillRule(value);
2550   } else if (name == "font-size") {
2551     attr.fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
2552   } else if (name == "transform") {
2553     nsvg__parseTransform(xform.ptr, value);
2554     nsvg__xformPremultiply(attr.xform.ptr, xform.ptr);
2555   } else if (name == "stop-color") {
2556     attr.stopColor = nsvg__parseColor(value);
2557   } else if (name == "stop-opacity") {
2558     attr.stopOpacity = nsvg__parseOpacity(value);
2559   } else if (name == "offset") {
2560     attr.stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
2561   } else if (name == "class") {
2562     version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '.', value);
2563   } else if (name == "id") {
2564     // apply classes here too
2565     version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '#', value);
2566     attr.id[] = 0;
2567     if (value.length > attr.id.length-1) value = value[0..attr.id.length-1];
2568     attr.id[0..value.length] = value[];
2569   } else {
2570     return false;
2571   }
2572   return true;
2573 }
2574 
2575 bool nsvg__parseNameValue (Parser* p, const(char)[] str) {
2576   const(char)[] name;
2577 
2578   str = str.trimLeft;
2579   usize pos = 0;
2580   while (pos < str.length && str.ptr[pos] != ':') {
2581     if (str.length-pos > 1 && str.ptr[pos] == '/' && str.ptr[pos+1] == '*') {
2582       pos += 2;
2583       while (str.length-pos > 1 && !(str.ptr[pos] == '*' && str.ptr[pos+1] == '/')) ++pos;
2584       if ((pos += 2) > str.length) pos = str.length;
2585     } else {
2586       ++pos;
2587     }
2588   }
2589 
2590   name = str[0..pos].trimLeft.trimRight;
2591   if (name.length == 0) return false;
2592 
2593   str = str[pos+(pos < str.length ? 1 : 0)..$].trimLeft.trimRight(';');
2594 
2595   version(nanosvg_debug_styles) { import std.stdio; writeln("** name=", name.quote, "; value=", str.quote); }
2596 
2597   return nsvg__parseAttr(p, name, str);
2598 }
2599 
2600 void nsvg__parseStyle (Parser* p, const(char)[] str) {
2601   while (str.length) {
2602     str = str.trimLeft;
2603     usize pos = 0;
2604     while (pos < str.length && str[pos] != ';') {
2605       if (str.length-pos > 1 && str.ptr[pos] == '/' && str.ptr[pos+1] == '*') {
2606         pos += 2;
2607         while (str.length-pos > 1 && !(str.ptr[pos] == '*' && str.ptr[pos+1] == '/')) ++pos;
2608         if ((pos += 2) > str.length) pos = str.length;
2609       } else {
2610         ++pos;
2611       }
2612     }
2613     const(char)[] val = trimRight(str[0..pos]);
2614     version(nanosvg_debug_styles) { import std.stdio; writeln("style: ", val.quote); }
2615     str = str[pos+(pos < str.length ? 1 : 0)..$];
2616     if (val.length > 0) nsvg__parseNameValue(p, val);
2617   }
2618 }
2619 
2620 void nsvg__parseAttribs (Parser* p, AttrList attr) {
2621   for (usize i = 0; attr.length-i >= 2; i += 2) {
2622          if (attr[i] == "style") nsvg__parseStyle(p, attr[i+1]);
2623     else if (attr[i] == "class") { version(nanosvg_crappy_stylesheet_parser) nsvg__parseClassOrId(p, '.', attr[i+1]); }
2624     else nsvg__parseAttr(p, attr[i], attr[i+1]);
2625   }
2626 }
2627 
2628 int nsvg__getArgsPerElement (char cmd) {
2629   switch (cmd) {
2630     case 'v': case 'V':
2631     case 'h': case 'H':
2632       return 1;
2633     case 'm': case 'M':
2634     case 'l': case 'L':
2635     case 't': case 'T':
2636       return 2;
2637     case 'q': case 'Q':
2638     case 's': case 'S':
2639       return 4;
2640     case 'c': case 'C':
2641       return 6;
2642     case 'a': case 'A':
2643       return 7;
2644     default:
2645   }
2646   return 0;
2647 }
2648 
2649 void nsvg__pathMoveTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
2650   debug(nanosvg) { import std.stdio; writeln("nsvg__pathMoveTo: args=", args[0..2]); }
2651   if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; }
2652   nsvg__moveTo(p, *cpx, *cpy);
2653 }
2654 
2655 void nsvg__pathLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
2656   debug(nanosvg) { import std.stdio; writeln("nsvg__pathLineTo: args=", args[0..2]); }
2657   if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; }
2658   nsvg__lineTo(p, *cpx, *cpy);
2659 }
2660 
2661 void nsvg__pathHLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
2662   debug(nanosvg) { import std.stdio; writeln("nsvg__pathHLineTo: args=", args[0..1]); }
2663   if (rel) *cpx += args[0]; else *cpx = args[0];
2664   nsvg__lineTo(p, *cpx, *cpy);
2665 }
2666 
2667 void nsvg__pathVLineTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
2668   debug(nanosvg) { import std.stdio; writeln("nsvg__pathVLineTo: args=", args[0..1]); }
2669   if (rel) *cpy += args[0]; else *cpy = args[0];
2670   nsvg__lineTo(p, *cpx, *cpy);
2671 }
2672 
2673 void nsvg__pathCubicBezTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
2674   debug(nanosvg) { import std.stdio; writeln("nsvg__pathCubicBezTo: args=", args[0..6]); }
2675   float cx1 = args[0];
2676   float cy1 = args[1];
2677   float cx2 = args[2];
2678   float cy2 = args[3];
2679   float x2 = args[4];
2680   float y2 = args[5];
2681 
2682   if (rel) {
2683     cx1 += *cpx;
2684     cy1 += *cpy;
2685     cx2 += *cpx;
2686     cy2 += *cpy;
2687     x2 += *cpx;
2688     y2 += *cpy;
2689   }
2690 
2691   nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
2692 
2693   *cpx2 = cx2;
2694   *cpy2 = cy2;
2695   *cpx = x2;
2696   *cpy = y2;
2697 }
2698 
2699 void nsvg__pathCubicBezShortTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
2700   debug(nanosvg) { import std.stdio; writeln("nsvg__pathCubicBezShortTo: args=", args[0..4]); }
2701 
2702   float cx2 = args[0];
2703   float cy2 = args[1];
2704   float x2 = args[2];
2705   float y2 = args[3];
2706   immutable float x1 = *cpx;
2707   immutable float y1 = *cpy;
2708 
2709   if (rel) {
2710     cx2 += *cpx;
2711     cy2 += *cpy;
2712     x2 += *cpx;
2713     y2 += *cpy;
2714   }
2715 
2716   immutable float cx1 = 2*x1-*cpx2;
2717   immutable float cy1 = 2*y1-*cpy2;
2718 
2719   nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
2720 
2721   *cpx2 = cx2;
2722   *cpy2 = cy2;
2723   *cpx = x2;
2724   *cpy = y2;
2725 }
2726 
2727 void nsvg__pathQuadBezTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
2728   debug(nanosvg) { import std.stdio; writeln("nsvg__pathQuadBezTo: args=", args[0..4]); }
2729 
2730   float cx = args[0];
2731   float cy = args[1];
2732   float x2 = args[2];
2733   float y2 = args[3];
2734   immutable float x1 = *cpx;
2735   immutable float y1 = *cpy;
2736 
2737   if (rel) {
2738     cx += *cpx;
2739     cy += *cpy;
2740     x2 += *cpx;
2741     y2 += *cpy;
2742   }
2743 
2744   version(nanosvg_only_cubic_beziers) {
2745     // convert to cubic bezier
2746     immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
2747     immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
2748     immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
2749     immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
2750     nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
2751   } else {
2752     nsvg__quadBezTo(p, cx, cy, x2, y2);
2753   }
2754 
2755   *cpx2 = cx;
2756   *cpy2 = cy;
2757   *cpx = x2;
2758   *cpy = y2;
2759 }
2760 
2761 void nsvg__pathQuadBezShortTo (Parser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, const(float)* args, bool rel) {
2762   debug(nanosvg) { import std.stdio; writeln("nsvg__pathQuadBezShortTo: args=", args[0..2]); }
2763 
2764   float x2 = args[0];
2765   float y2 = args[1];
2766   immutable float x1 = *cpx;
2767   immutable float y1 = *cpy;
2768 
2769   if (rel) {
2770     x2 += *cpx;
2771     y2 += *cpy;
2772   }
2773 
2774   immutable float cx = 2*x1-*cpx2;
2775   immutable float cy = 2*y1-*cpy2;
2776 
2777   version(nanosvg_only_cubic_beziers) {
2778     // convert to cubic bezier
2779     immutable float cx1 = x1+2.0f/3.0f*(cx-x1);
2780     immutable float cy1 = y1+2.0f/3.0f*(cy-y1);
2781     immutable float cx2 = x2+2.0f/3.0f*(cx-x2);
2782     immutable float cy2 = y2+2.0f/3.0f*(cy-y2);
2783     nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
2784   } else {
2785     nsvg__quadBezTo(p, cx, cy, x2, y2);
2786   }
2787 
2788   *cpx2 = cx;
2789   *cpy2 = cy;
2790   *cpx = x2;
2791   *cpy = y2;
2792 }
2793 
2794 float nsvg__sqr (in float x) pure nothrow @safe @nogc { pragma(inline, true); return x*x; }
2795 float nsvg__vmag (in float x, float y) nothrow @safe @nogc { pragma(inline, true); return sqrtf(x*x+y*y); }
2796 
2797 float nsvg__vecrat (float ux, float uy, float vx, float vy) nothrow @safe @nogc {
2798   pragma(inline, true);
2799   return (ux*vx+uy*vy)/(nsvg__vmag(ux, uy)*nsvg__vmag(vx, vy));
2800 }
2801 
2802 float nsvg__vecang (float ux, float uy, float vx, float vy) nothrow @safe @nogc {
2803   float r = nsvg__vecrat(ux, uy, vx, vy);
2804   if (r < -1.0f) r = -1.0f;
2805   if (r > 1.0f) r = 1.0f;
2806   return (ux*vy < uy*vx ? -1.0f : 1.0f)*acosf(r);
2807 }
2808 
2809 void nsvg__pathArcTo (Parser* p, float* cpx, float* cpy, const(float)* args, bool rel) {
2810   // ported from canvg (https://code.google.com/p/canvg/)
2811   float rx = fabsf(args[0]); // y radius
2812   float ry = fabsf(args[1]); // x radius
2813   immutable float rotx = args[2]/180.0f*NSVG_PI; // x rotation engle
2814   immutable float fa = (fabsf(args[3]) > 1e-6 ? 1 : 0); // large arc
2815   immutable float fs = (fabsf(args[4]) > 1e-6 ? 1 : 0); // sweep direction
2816   immutable float x1 = *cpx; // start point
2817   immutable float y1 = *cpy;
2818 
2819   // end point
2820   float x2 = args[5];
2821   float y2 = args[6];
2822 
2823   if (rel) { x2 += *cpx; y2 += *cpy; }
2824 
2825   float dx = x1-x2;
2826   float dy = y1-y2;
2827   immutable float d0 = sqrtf(dx*dx+dy*dy);
2828   if (d0 < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
2829     // the arc degenerates to a line
2830     nsvg__lineTo(p, x2, y2);
2831     *cpx = x2;
2832     *cpy = y2;
2833     return;
2834   }
2835 
2836   immutable float sinrx = sinf(rotx);
2837   immutable float cosrx = cosf(rotx);
2838 
2839   // convert to center point parameterization
2840   // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
2841   // 1) Compute x1', y1'
2842   immutable float x1p = cosrx*dx/2.0f+sinrx*dy/2.0f;
2843   immutable float y1p = -sinrx*dx/2.0f+cosrx*dy/2.0f;
2844   immutable float d1 = nsvg__sqr(x1p)/nsvg__sqr(rx)+nsvg__sqr(y1p)/nsvg__sqr(ry);
2845   if (d1 > 1) {
2846     immutable float d2 = sqrtf(d1);
2847     rx *= d2;
2848     ry *= d2;
2849   }
2850   // 2) Compute cx', cy'
2851   float s = 0.0f;
2852   float sa = nsvg__sqr(rx)*nsvg__sqr(ry)-nsvg__sqr(rx)*nsvg__sqr(y1p)-nsvg__sqr(ry)*nsvg__sqr(x1p);
2853   immutable float sb = nsvg__sqr(rx)*nsvg__sqr(y1p)+nsvg__sqr(ry)*nsvg__sqr(x1p);
2854   if (sa < 0.0f) sa = 0.0f;
2855   if (sb > 0.0f) s = sqrtf(sa/sb);
2856   if (fa == fs) s = -s;
2857   immutable float cxp = s*rx*y1p/ry;
2858   immutable float cyp = s*-ry*x1p/rx;
2859 
2860   // 3) Compute cx,cy from cx',cy'
2861   immutable float cx = (x1+x2)/2.0f+cosrx*cxp-sinrx*cyp;
2862   immutable float cy = (y1+y2)/2.0f+sinrx*cxp+cosrx*cyp;
2863 
2864   // 4) Calculate theta1, and delta theta.
2865   immutable float ux = (x1p-cxp)/rx;
2866   immutable float uy = (y1p-cyp)/ry;
2867   immutable float vx = (-x1p-cxp)/rx;
2868   immutable float vy = (-y1p-cyp)/ry;
2869   immutable float a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle
2870   float da = nsvg__vecang(ux, uy, vx, vy); // Delta angle
2871 
2872        if (fs == 0 && da > 0) da -= 2*NSVG_PI;
2873   else if (fs == 1 && da < 0) da += 2*NSVG_PI;
2874 
2875   float[6] t = void;
2876   // approximate the arc using cubic spline segments
2877   t.ptr[0] = cosrx; t.ptr[1] = sinrx;
2878   t.ptr[2] = -sinrx; t.ptr[3] = cosrx;
2879   t.ptr[4] = cx; t.ptr[5] = cy;
2880 
2881   // split arc into max 90 degree segments
2882   // the loop assumes an iteration per end point (including start and end), this +1
2883   immutable ndivs = cast(int)(fabsf(da)/(NSVG_PI*0.5f)+1.0f);
2884   immutable float hda = (da/cast(float)ndivs)/2.0f;
2885   float kappa = fabsf(4.0f/3.0f*(1.0f-cosf(hda))/sinf(hda));
2886   if (da < 0.0f) kappa = -kappa;
2887 
2888   immutable float ndivsf = cast(float)ndivs;
2889   float px = 0, py = 0, ptanx = 0, ptany = 0;
2890   foreach (int i; 0..ndivs+1) {
2891     float x = void, y = void, tanx = void, tany = void;
2892     immutable float a = a1+da*(i/ndivsf);
2893     immutable float loopdx = cosf(a);
2894     immutable float loopdy = sinf(a);
2895     nsvg__xformPoint(&x, &y, loopdx*rx, loopdy*ry, t.ptr); // position
2896     nsvg__xformVec(&tanx, &tany, -loopdy*rx*kappa, loopdx*ry*kappa, t.ptr); // tangent
2897     if (i > 0) nsvg__cubicBezTo(p, px+ptanx, py+ptany, x-tanx, y-tany, x, y);
2898     px = x;
2899     py = y;
2900     ptanx = tanx;
2901     ptany = tany;
2902   }
2903 
2904   *cpx = x2;
2905   *cpy = y2;
2906 }
2907 
2908 void nsvg__parsePath (Parser* p, AttrList attr) {
2909   const(char)[] s = null;
2910   char cmd = '\0';
2911   float[10] args = void;
2912   int nargs;
2913   int rargs = 0;
2914   float cpx = void, cpy = void, cpx2 = void, cpy2 = void;
2915   bool closedFlag = false;
2916   char[65] item = void;
2917 
2918   for (usize i = 0; attr.length-i >= 2; i += 2) {
2919     if (attr[i] == "d") {
2920       s = attr[i+1];
2921     } else {
2922       const(char)[][2] tmp;
2923       tmp[0] = attr[i];
2924       tmp[1] = attr[i+1];
2925       nsvg__parseAttribs(p, tmp[]);
2926     }
2927   }
2928 
2929   if (s.length) {
2930     nsvg__resetPath(p);
2931     cpx = 0;
2932     cpy = 0;
2933     cpx2 = 0;
2934     cpy2 = 0;
2935     closedFlag = false;
2936     nargs = 0;
2937 
2938     while (s.length) {
2939       auto skl = nsvg__getNextPathItem(s, item[]);
2940       if (skl < s.length) s = s[skl..$]; else s = s[$..$];
2941       debug(nanosvg) { import std.stdio; writeln(":: ", item.fromAsciiz.quote, " : ", s.quote); }
2942       if (!item[0]) break;
2943       if (nsvg__isnum(item[0])) {
2944         if (nargs < 10) {
2945           args[nargs++] = nsvg__atof(item[]);
2946         }
2947         if (nargs >= rargs) {
2948           switch (cmd) {
2949             case 'm': case 'M': // move to
2950               nsvg__pathMoveTo(p, &cpx, &cpy, args.ptr, (cmd == 'm' ? 1 : 0));
2951               // Moveto can be followed by multiple coordinate pairs,
2952               // which should be treated as linetos.
2953               cmd = (cmd == 'm' ? 'l' : 'L');
2954               rargs = nsvg__getArgsPerElement(cmd);
2955               cpx2 = cpx; cpy2 = cpy;
2956               break;
2957             case 'l': case 'L': // line to
2958               nsvg__pathLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'l' ? 1 : 0));
2959               cpx2 = cpx; cpy2 = cpy;
2960               break;
2961             case 'H': case 'h': // horizontal line to
2962               nsvg__pathHLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'h' ? 1 : 0));
2963               cpx2 = cpx; cpy2 = cpy;
2964               break;
2965             case 'V': case 'v': // vertical line to
2966               nsvg__pathVLineTo(p, &cpx, &cpy, args.ptr, (cmd == 'v' ? 1 : 0));
2967               cpx2 = cpx; cpy2 = cpy;
2968               break;
2969             case 'C': case 'c': // cubic bezier
2970               nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 'c' ? 1 : 0));
2971               break;
2972             case 'S': case 's': // "short" cubic bezier
2973               nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 's' ? 1 : 0));
2974               break;
2975             case 'Q': case 'q': // quadratic bezier
2976               nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, (cmd == 'q' ? 1 : 0));
2977               break;
2978             case 'T': case 't': // "short" quadratic bezier
2979               nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args.ptr, cmd == 't' ? 1 : 0);
2980               break;
2981             case 'A': case 'a': // arc
2982               nsvg__pathArcTo(p, &cpx, &cpy, args.ptr, cmd == 'a' ? 1 : 0);
2983               cpx2 = cpx; cpy2 = cpy;
2984               break;
2985             default:
2986               if (nargs >= 2) {
2987                 cpx = args[nargs-2];
2988                 cpy = args[nargs-1];
2989                 cpx2 = cpx;
2990                 cpy2 = cpy;
2991               }
2992               break;
2993           }
2994           nargs = 0;
2995         }
2996       } else {
2997         cmd = item[0];
2998         rargs = nsvg__getArgsPerElement(cmd);
2999         if (cmd == 'M' || cmd == 'm') {
3000           // commit path
3001           if (p.nsflts > 0) nsvg__addPath(p, closedFlag);
3002           // start new subpath
3003           nsvg__resetPath(p);
3004           closedFlag = false;
3005           nargs = 0;
3006         } else if (cmd == 'Z' || cmd == 'z') {
3007           closedFlag = true;
3008           // commit path
3009           if (p.nsflts > 0) {
3010             // move current point to first point
3011             if ((cast(NSVG.Command)p.stream[0]) != NSVG.Command.MoveTo) assert(0, "NanoVega.SVG: invalid path");
3012             cpx = p.stream[1];
3013             cpy = p.stream[2];
3014             cpx2 = cpx;
3015             cpy2 = cpy;
3016             nsvg__addPath(p, closedFlag);
3017           }
3018           // start new subpath
3019           nsvg__resetPath(p);
3020           nsvg__moveTo(p, cpx, cpy);
3021           closedFlag = false;
3022           nargs = 0;
3023         }
3024       }
3025     }
3026     // commit path
3027     if (p.nsflts) nsvg__addPath(p, closedFlag);
3028   }
3029 
3030   nsvg__addShape(p);
3031 }
3032 
3033 void nsvg__parseRect (Parser* p, AttrList attr) {
3034   float x = 0.0f;
3035   float y = 0.0f;
3036   float w = 0.0f;
3037   float h = 0.0f;
3038   float rx = -1.0f; // marks not set
3039   float ry = -1.0f;
3040 
3041   for (usize i = 0; attr.length-i >= 2; i += 2) {
3042     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3043            if (attr[i] == "x") x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
3044       else if (attr[i] == "y") y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
3045       else if (attr[i] == "width") w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p));
3046       else if (attr[i] == "height") h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p));
3047       else if (attr[i] == "rx") rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
3048       else if (attr[i] == "ry") ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
3049     }
3050   }
3051 
3052   if (rx < 0.0f && ry > 0.0f) rx = ry;
3053   if (ry < 0.0f && rx > 0.0f) ry = rx;
3054   if (rx < 0.0f) rx = 0.0f;
3055   if (ry < 0.0f) ry = 0.0f;
3056   if (rx > w/2.0f) rx = w/2.0f;
3057   if (ry > h/2.0f) ry = h/2.0f;
3058 
3059   if (w != 0.0f && h != 0.0f) {
3060     nsvg__resetPath(p);
3061 
3062     if (rx < 0.00001f || ry < 0.0001f) {
3063       nsvg__moveTo(p, x, y);
3064       nsvg__lineTo(p, x+w, y);
3065       nsvg__lineTo(p, x+w, y+h);
3066       nsvg__lineTo(p, x, y+h);
3067     } else {
3068       // Rounded rectangle
3069       nsvg__moveTo(p, x+rx, y);
3070       nsvg__lineTo(p, x+w-rx, y);
3071       nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry);
3072       nsvg__lineTo(p, x+w, y+h-ry);
3073       nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h);
3074       nsvg__lineTo(p, x+rx, y+h);
3075       nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry);
3076       nsvg__lineTo(p, x, y+ry);
3077       nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y);
3078     }
3079 
3080     nsvg__addPath(p, 1);
3081 
3082     nsvg__addShape(p);
3083   }
3084 }
3085 
3086 void nsvg__parseCircle (Parser* p, AttrList attr) {
3087   float cx = 0.0f;
3088   float cy = 0.0f;
3089   float r = 0.0f;
3090 
3091   for (usize i = 0; attr.length-i >= 2; i += 2) {
3092     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3093            if (attr[i] == "cx") cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
3094       else if (attr[i] == "cy") cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
3095       else if (attr[i] == "r") r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p)));
3096     }
3097   }
3098 
3099   if (r > 0.0f) {
3100     nsvg__resetPath(p);
3101 
3102     nsvg__moveTo(p, cx+r, cy);
3103     nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r);
3104     nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy);
3105     nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r);
3106     nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy);
3107 
3108     nsvg__addPath(p, 1);
3109 
3110     nsvg__addShape(p);
3111   }
3112 }
3113 
3114 void nsvg__parseEllipse (Parser* p, AttrList attr) {
3115   float cx = 0.0f;
3116   float cy = 0.0f;
3117   float rx = 0.0f;
3118   float ry = 0.0f;
3119 
3120   for (usize i = 0; attr.length-i >= 2; i += 2) {
3121     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3122            if (attr[i] == "cx") cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
3123       else if (attr[i] == "cy") cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
3124       else if (attr[i] == "rx") rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
3125       else if (attr[i] == "ry") ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
3126     }
3127   }
3128 
3129   if (rx > 0.0f && ry > 0.0f) {
3130     nsvg__resetPath(p);
3131 
3132     nsvg__moveTo(p, cx+rx, cy);
3133     nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry);
3134     nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy);
3135     nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry);
3136     nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy);
3137 
3138     nsvg__addPath(p, 1);
3139 
3140     nsvg__addShape(p);
3141   }
3142 }
3143 
3144 void nsvg__parseLine (Parser* p, AttrList attr) {
3145   float x1 = 0.0;
3146   float y1 = 0.0;
3147   float x2 = 0.0;
3148   float y2 = 0.0;
3149 
3150   for (usize i = 0; attr.length-i >= 2; i += 2) {
3151     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3152            if (attr[i] == "x1") x1 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
3153       else if (attr[i] == "y1") y1 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
3154       else if (attr[i] == "x2") x2 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
3155       else if (attr[i] == "y2") y2 = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
3156     }
3157   }
3158 
3159   nsvg__resetPath(p);
3160 
3161   nsvg__moveTo(p, x1, y1);
3162   nsvg__lineTo(p, x2, y2);
3163 
3164   nsvg__addPath(p, 0);
3165 
3166   nsvg__addShape(p);
3167 }
3168 
3169 void nsvg__parsePoly (Parser* p, AttrList attr, bool closeFlag) {
3170   float[2] args = void;
3171   int nargs, npts = 0;
3172   char[65] item = 0;
3173 
3174   nsvg__resetPath(p);
3175 
3176   for (usize i = 0; attr.length-i >= 2; i += 2) {
3177     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3178       if (attr[i] == "points") {
3179         const(char)[]s = attr[i+1];
3180         nargs = 0;
3181         while (s.length) {
3182           auto skl = nsvg__getNextPathItem(s, item[]);
3183           if (skl < s.length) s = s[skl..$]; else s = s[$..$];
3184           args[nargs++] = nsvg__atof(item[]);
3185           if (nargs >= 2) {
3186             if (npts == 0) nsvg__moveTo(p, args[0], args[1]); else nsvg__lineTo(p, args[0], args[1]);
3187             nargs = 0;
3188             ++npts;
3189           }
3190         }
3191       }
3192     }
3193   }
3194 
3195   nsvg__addPath(p, closeFlag);
3196 
3197   nsvg__addShape(p);
3198 }
3199 
3200 void nsvg__parseSVG (Parser* p, AttrList attr) {
3201   for (usize i = 0; attr.length-i >= 2; i += 2) {
3202     if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3203       if (attr[i] == "width") {
3204         p.image.width = nsvg__parseCoordinate(p, attr[i+1], 0.0f, p.canvaswdt);
3205         //{ 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); }
3206       } else if (attr[i] == "height") {
3207         p.image.height = nsvg__parseCoordinate(p, attr[i+1], 0.0f, p.canvashgt);
3208       } else if (attr[i] == "viewBox") {
3209         xsscanf(attr[i+1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", p.viewMinx, p.viewMiny, p.viewWidth, p.viewHeight);
3210       } else if (attr[i] == "preserveAspectRatio") {
3211         if (attr[i+1].xindexOf("none") >= 0) {
3212           // No uniform scaling
3213           p.alignType = NSVG_ALIGN_NONE;
3214         } else {
3215           // Parse X align
3216                if (attr[i+1].xindexOf("xMin") >= 0) p.alignX = NSVG_ALIGN_MIN;
3217           else if (attr[i+1].xindexOf("xMid") >= 0) p.alignX = NSVG_ALIGN_MID;
3218           else if (attr[i+1].xindexOf("xMax") >= 0) p.alignX = NSVG_ALIGN_MAX;
3219           // Parse X align
3220                if (attr[i+1].xindexOf("yMin") >= 0) p.alignY = NSVG_ALIGN_MIN;
3221           else if (attr[i+1].xindexOf("yMid") >= 0) p.alignY = NSVG_ALIGN_MID;
3222           else if (attr[i+1].xindexOf("yMax") >= 0) p.alignY = NSVG_ALIGN_MAX;
3223           // Parse meet/slice
3224           p.alignType = NSVG_ALIGN_MEET;
3225           if (attr[i+1].xindexOf("slice") >= 0) p.alignType = NSVG_ALIGN_SLICE;
3226         }
3227       }
3228     }
3229   }
3230 }
3231 
3232 void nsvg__parseGradient (Parser* p, AttrList attr, NSVG.PaintType type) {
3233   GradientData* grad = xalloc!GradientData;
3234   if (grad is null) return;
3235   //memset(grad, 0, GradientData.sizeof);
3236   grad.units = GradientUnits.Object;
3237   grad.type = type;
3238   if (grad.type == NSVG.PaintType.LinearGradient) {
3239     grad.linear.x1 = nsvg__coord(0.0f, Units.percent);
3240     grad.linear.y1 = nsvg__coord(0.0f, Units.percent);
3241     grad.linear.x2 = nsvg__coord(100.0f, Units.percent);
3242     grad.linear.y2 = nsvg__coord(0.0f, Units.percent);
3243   } else if (grad.type == NSVG.PaintType.RadialGradient) {
3244     grad.radial.cx = nsvg__coord(50.0f, Units.percent);
3245     grad.radial.cy = nsvg__coord(50.0f, Units.percent);
3246     grad.radial.r = nsvg__coord(50.0f, Units.percent);
3247   }
3248 
3249   nsvg__xformIdentity(grad.xform.ptr);
3250 
3251   for (usize i = 0; attr.length-i >= 2; i += 2) {
3252     if (attr[i] == "id") {
3253       grad.id[] = 0;
3254       const(char)[] s = attr[i+1];
3255       if (s.length > grad.id.length-1) s = s[0..grad.id.length-1];
3256       grad.id[0..s.length] = s[];
3257     } else if (!nsvg__parseAttr(p, attr[i], attr[i+1])) {
3258            if (attr[i] == "gradientUnits") { if (attr[i+1] == "objectBoundingBox") grad.units = GradientUnits.Object; else grad.units = GradientUnits.User; }
3259       else if (attr[i] == "gradientTransform") { nsvg__parseTransform(grad.xform.ptr, attr[i+1]); }
3260       else if (attr[i] == "cx") { grad.radial.cx = nsvg__parseCoordinateRaw(attr[i+1]); }
3261       else if (attr[i] == "cy") { grad.radial.cy = nsvg__parseCoordinateRaw(attr[i+1]); }
3262       else if (attr[i] == "r") { grad.radial.r = nsvg__parseCoordinateRaw(attr[i+1]); }
3263       else if (attr[i] == "fx") { grad.radial.fx = nsvg__parseCoordinateRaw(attr[i+1]); }
3264       else if (attr[i] == "fy") { grad.radial.fy = nsvg__parseCoordinateRaw(attr[i+1]); }
3265       else if (attr[i] == "x1") { grad.linear.x1 = nsvg__parseCoordinateRaw(attr[i+1]); }
3266       else if (attr[i] == "y1") { grad.linear.y1 = nsvg__parseCoordinateRaw(attr[i+1]); }
3267       else if (attr[i] == "x2") { grad.linear.x2 = nsvg__parseCoordinateRaw(attr[i+1]); }
3268       else if (attr[i] == "y2") { grad.linear.y2 = nsvg__parseCoordinateRaw(attr[i+1]); }
3269       else if (attr[i] == "spreadMethod") {
3270              if (attr[i+1] == "pad") grad.spread = NSVG.SpreadType.Pad;
3271         else if (attr[i+1] == "reflect") grad.spread = NSVG.SpreadType.Reflect;
3272         else if (attr[i+1] == "repeat") grad.spread = NSVG.SpreadType.Repeat;
3273       } else if (attr[i] == "xlink:href") {
3274         grad.ref_[] = 0;
3275         const(char)[] s = attr[i+1];
3276         if (s.length > 0 && s.ptr[0] == '#') s = s[1..$]; // remove '#'
3277         if (s.length > grad.ref_.length-1) s = s[0..grad.ref_.length-1];
3278         grad.ref_[0..s.length] = s[];
3279       }
3280     }
3281   }
3282 
3283   grad.next = p.gradients;
3284   p.gradients = grad;
3285 }
3286 
3287 void nsvg__parseGradientStop (Parser* p, AttrList attr) {
3288   import core.stdc.stdlib : realloc;
3289 
3290   Attrib* curAttr = nsvg__getAttr(p);
3291   GradientData* grad;
3292   NSVG.GradientStop* stop;
3293   int idx;
3294 
3295   curAttr.stopOffset = 0;
3296   curAttr.stopColor = 0;
3297   curAttr.stopOpacity = 1.0f;
3298 
3299   for (usize i = 0; attr.length-i >= 2; i += 2) nsvg__parseAttr(p, attr[i], attr[i+1]);
3300 
3301   // Add stop to the last gradient.
3302   grad = p.gradients;
3303   if (grad is null) return;
3304 
3305   ++grad.nstops;
3306   grad.stops = cast(NSVG.GradientStop*)realloc(grad.stops, NSVG.GradientStop.sizeof*grad.nstops+256);
3307   if (grad.stops is null) assert(0, "nanosvg: out of memory");
3308 
3309   // Insert
3310   idx = grad.nstops-1;
3311   foreach (int i; 0..grad.nstops-1) {
3312     if (curAttr.stopOffset < grad.stops[i].offset) {
3313       idx = i;
3314       break;
3315     }
3316   }
3317   if (idx != grad.nstops-1) {
3318     for (int i = grad.nstops-1; i > idx; --i) grad.stops[i] = grad.stops[i-1];
3319   }
3320 
3321   stop = grad.stops+idx;
3322   stop.color = curAttr.stopColor;
3323   stop.color |= cast(uint)(curAttr.stopOpacity*255)<<24;
3324   stop.offset = curAttr.stopOffset;
3325 }
3326 
3327 void nsvg__startElement (void* ud, const(char)[] el, AttrList attr) {
3328   Parser* p = cast(Parser*)ud;
3329 
3330   version(nanosvg_debug_styles) { import std.stdio; writeln("tagB: ", el.quote); }
3331   version(nanosvg_crappy_stylesheet_parser) { p.inStyle = (el == "style"); }
3332 
3333   if (p.defsFlag) {
3334     // Skip everything but gradients in defs
3335     if (el == "linearGradient") {
3336       nsvg__parseGradient(p, attr, NSVG.PaintType.LinearGradient);
3337     } else if (el == "radialGradient") {
3338       nsvg__parseGradient(p, attr, NSVG.PaintType.RadialGradient);
3339     } else if (el == "stop") {
3340       nsvg__parseGradientStop(p, attr);
3341     }
3342     return;
3343   }
3344 
3345   if (el == "g") {
3346     nsvg__pushAttr(p);
3347     nsvg__parseAttribs(p, attr);
3348   } else if (el == "path") {
3349     if (p.pathFlag) return; // do not allow nested paths
3350     p.pathFlag = true;
3351     nsvg__pushAttr(p);
3352     nsvg__parsePath(p, attr);
3353     nsvg__popAttr(p);
3354   } else if (el == "rect") {
3355     nsvg__pushAttr(p);
3356     nsvg__parseRect(p, attr);
3357     nsvg__popAttr(p);
3358   } else if (el == "circle") {
3359     nsvg__pushAttr(p);
3360     nsvg__parseCircle(p, attr);
3361     nsvg__popAttr(p);
3362   } else if (el == "ellipse") {
3363     nsvg__pushAttr(p);
3364     nsvg__parseEllipse(p, attr);
3365     nsvg__popAttr(p);
3366   } else if (el == "line")  {
3367     nsvg__pushAttr(p);
3368     nsvg__parseLine(p, attr);
3369     nsvg__popAttr(p);
3370   } else if (el == "polyline")  {
3371     nsvg__pushAttr(p);
3372     nsvg__parsePoly(p, attr, 0);
3373     nsvg__popAttr(p);
3374   } else if (el == "polygon")  {
3375     nsvg__pushAttr(p);
3376     nsvg__parsePoly(p, attr, 1);
3377     nsvg__popAttr(p);
3378   } else  if (el == "linearGradient") {
3379     nsvg__parseGradient(p, attr, NSVG.PaintType.LinearGradient);
3380   } else if (el == "radialGradient") {
3381     nsvg__parseGradient(p, attr, NSVG.PaintType.RadialGradient);
3382   } else if (el == "stop") {
3383     nsvg__parseGradientStop(p, attr);
3384   } else if (el == "defs") {
3385     p.defsFlag = true;
3386   } else if (el == "svg") {
3387     nsvg__parseSVG(p, attr);
3388   }
3389 }
3390 
3391 void nsvg__endElement (void* ud, const(char)[] el) {
3392   version(nanosvg_debug_styles) { import std.stdio; writeln("tagE: ", el.quote); }
3393   Parser* p = cast(Parser*)ud;
3394        if (el == "g") nsvg__popAttr(p);
3395   else if (el == "path") p.pathFlag = false;
3396   else if (el == "defs") p.defsFlag = false;
3397   else if (el == "style") { version(nanosvg_crappy_stylesheet_parser) p.inStyle = false; }
3398 }
3399 
3400 void nsvg__content (void* ud, const(char)[] s) {
3401   version(nanosvg_crappy_stylesheet_parser) {
3402     Parser* p = cast(Parser*)ud;
3403     if (!p.inStyle) {
3404       return;
3405     }
3406     // cheap hack
3407     for (;;) {
3408       while (s.length && s.ptr[0] <= ' ') s = s[1..$];
3409       if (!s.startsWith("<![CDATA[")) break;
3410       s = s[9..$];
3411     }
3412     for (;;) {
3413       while (s.length && (s[$-1] <= ' ' || s[$-1] == '>')) s = s[0..$-1];
3414       if (s.length > 1 && s[$-2..$] == "]]") s = s[0..$-2]; else break;
3415     }
3416     version(nanosvg_debug_styles) { import std.stdio; writeln("ctx: ", s.quote); }
3417     uint tokensAdded = 0;
3418     while (s.length) {
3419       if (s.length > 1 && s.ptr[0] == '/' && s.ptr[1] == '*') {
3420         // comment
3421         s = s[2..$];
3422         while (s.length > 1 && !(s.ptr[0] == '*' && s.ptr[1] == '/')) s = s[1..$];
3423         if (s.length <= 2) break;
3424         s = s[2..$];
3425         continue;
3426       } else if (s.ptr[0] <= ' ') {
3427         while (s.length && s.ptr[0] <= ' ') s = s[1..$];
3428         continue;
3429       }
3430       //version(nanosvg_debug_styles) { import std.stdio; writeln("::: ", s.quote); }
3431       if (s.ptr[0] == '{') {
3432         usize pos = 1;
3433         while (pos < s.length && s.ptr[pos] != '}') {
3434           if (s.length-pos > 1 && s.ptr[pos] == '/' && s.ptr[pos+1] == '*') {
3435             // skip comment
3436             pos += 2;
3437             while (s.length-pos > 1 && !(s.ptr[pos] == '*' && s.ptr[pos+1] == '/')) ++pos;
3438             if (s.length-pos <= 2) { pos = cast(uint)s.length; break; }
3439             pos += 2;
3440           } else {
3441             ++pos;
3442           }
3443         }
3444         version(nanosvg_debug_styles) { import std.stdio; writeln("*** style: ", s[1..pos].quote); }
3445         if (tokensAdded > 0) {
3446           foreach (immutable idx; p.styleCount-tokensAdded..p.styleCount) p.styles[idx].value = s[1..pos];
3447         }
3448         tokensAdded = 0;
3449         if (s.length-pos < 1) break;
3450         s = s[pos+1..$];
3451       } else {
3452         usize pos = 0;
3453         while (pos < s.length && s.ptr[pos] > ' ' && s.ptr[pos] != '{' && s.ptr[pos] != '/') ++pos;
3454         const(char)[] tk = s[0..pos];
3455         version(nanosvg_debug_styles) { import std.stdio; writeln("token: ", tk.quote); }
3456         s = s[pos..$];
3457         {
3458           import core.stdc.stdlib : realloc;
3459           import core.stdc.string : memset;
3460           p.styles = cast(typeof(p.styles))realloc(p.styles, p.styles[0].sizeof*(p.styleCount+1));
3461           memset(p.styles+p.styleCount, 0, p.styles[0].sizeof);
3462           ++p.styleCount;
3463         }
3464         p.styles[p.styleCount-1].name = tk;
3465         ++tokensAdded;
3466       }
3467     }
3468     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); }
3469   }
3470 }
3471 
3472 void nsvg__imageBounds (Parser* p, float* bounds) {
3473   NSVG.Shape* shape;
3474   shape = p.image.shapes;
3475   if (shape is null) {
3476     bounds[0..4] = 0.0;
3477     return;
3478   }
3479   bounds[0] = shape.bounds.ptr[0];
3480   bounds[1] = shape.bounds.ptr[1];
3481   bounds[2] = shape.bounds.ptr[2];
3482   bounds[3] = shape.bounds.ptr[3];
3483   for (shape = shape.next; shape !is null; shape = shape.next) {
3484     bounds[0] = nsvg__minf(bounds[0], shape.bounds.ptr[0]);
3485     bounds[1] = nsvg__minf(bounds[1], shape.bounds.ptr[1]);
3486     bounds[2] = nsvg__maxf(bounds[2], shape.bounds.ptr[2]);
3487     bounds[3] = nsvg__maxf(bounds[3], shape.bounds.ptr[3]);
3488   }
3489 }
3490 
3491 float nsvg__viewAlign (float content, float container, int type) {
3492   if (type == NSVG_ALIGN_MIN) return 0;
3493   if (type == NSVG_ALIGN_MAX) return container-content;
3494   // mid
3495   return (container-content)*0.5f;
3496 }
3497 
3498 void nsvg__scaleGradient (NSVG.Gradient* grad, float tx, float ty, float sx, float sy) {
3499   float[6] t = void;
3500   nsvg__xformSetTranslation(t.ptr, tx, ty);
3501   nsvg__xformMultiply(grad.xform.ptr, t.ptr);
3502 
3503   nsvg__xformSetScale(t.ptr, sx, sy);
3504   nsvg__xformMultiply(grad.xform.ptr, t.ptr);
3505 }
3506 
3507 void nsvg__scaleToViewbox (Parser* p, const(char)[] units) {
3508   NSVG.Shape* shape;
3509   NSVG.Path* path;
3510   float tx = void, ty = void, sx = void, sy = void, us = void, avgs = void;
3511   float[4] bounds = void;
3512   float[6] t = void;
3513   float* pt;
3514 
3515   // Guess image size if not set completely.
3516   nsvg__imageBounds(p, bounds.ptr);
3517 
3518   if (p.viewWidth == 0) {
3519     if (p.image.width > 0) {
3520       p.viewWidth = p.image.width;
3521     } else {
3522       p.viewMinx = bounds[0];
3523       p.viewWidth = bounds[2]-bounds[0];
3524     }
3525   }
3526   if (p.viewHeight == 0) {
3527     if (p.image.height > 0) {
3528       p.viewHeight = p.image.height;
3529     } else {
3530       p.viewMiny = bounds[1];
3531       p.viewHeight = bounds[3]-bounds[1];
3532     }
3533   }
3534   if (p.image.width == 0)
3535     p.image.width = p.viewWidth;
3536   if (p.image.height == 0)
3537     p.image.height = p.viewHeight;
3538 
3539   tx = -p.viewMinx;
3540   ty = -p.viewMiny;
3541   sx = p.viewWidth > 0 ? p.image.width/p.viewWidth : 0;
3542   sy = p.viewHeight > 0 ? p.image.height/p.viewHeight : 0;
3543   // Unit scaling
3544   us = 1.0f/nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);
3545 
3546   // Fix aspect ratio
3547   if (p.alignType == NSVG_ALIGN_MEET) {
3548     // fit whole image into viewbox
3549     sx = sy = nsvg__minf(sx, sy);
3550     tx += nsvg__viewAlign(p.viewWidth*sx, p.image.width, p.alignX)/sx;
3551     ty += nsvg__viewAlign(p.viewHeight*sy, p.image.height, p.alignY)/sy;
3552   } else if (p.alignType == NSVG_ALIGN_SLICE) {
3553     // fill whole viewbox with image
3554     sx = sy = nsvg__maxf(sx, sy);
3555     tx += nsvg__viewAlign(p.viewWidth*sx, p.image.width, p.alignX)/sx;
3556     ty += nsvg__viewAlign(p.viewHeight*sy, p.image.height, p.alignY)/sy;
3557   }
3558 
3559   // Transform
3560   sx *= us;
3561   sy *= us;
3562   avgs = (sx+sy)/2.0f;
3563   for (shape = p.image.shapes; shape !is null; shape = shape.next) {
3564     shape.bounds.ptr[0] = (shape.bounds.ptr[0]+tx)*sx;
3565     shape.bounds.ptr[1] = (shape.bounds.ptr[1]+ty)*sy;
3566     shape.bounds.ptr[2] = (shape.bounds.ptr[2]+tx)*sx;
3567     shape.bounds.ptr[3] = (shape.bounds.ptr[3]+ty)*sy;
3568     for (path = shape.paths; path !is null; path = path.next) {
3569       path.bounds[0] = (path.bounds[0]+tx)*sx;
3570       path.bounds[1] = (path.bounds[1]+ty)*sy;
3571       path.bounds[2] = (path.bounds[2]+tx)*sx;
3572       path.bounds[3] = (path.bounds[3]+ty)*sy;
3573       for (int i = 0; i+3 <= path.nsflts; ) {
3574         int argc = 0; // pair of coords
3575         NSVG.Command cmd = cast(NSVG.Command)path.stream[i++];
3576         final switch (cmd) {
3577           case NSVG.Command.MoveTo: argc = 1; break;
3578           case NSVG.Command.LineTo: argc = 1; break;
3579           case NSVG.Command.QuadTo: argc = 2; break;
3580           case NSVG.Command.BezierTo: argc = 3; break;
3581         }
3582         // scale points
3583         while (argc-- > 0) {
3584           path.stream[i+0] = (path.stream[i+0]+tx)*sx;
3585           path.stream[i+1] = (path.stream[i+1]+ty)*sy;
3586           i += 2;
3587         }
3588       }
3589     }
3590 
3591     if (shape.fill.type == NSVG.PaintType.LinearGradient || shape.fill.type == NSVG.PaintType.RadialGradient) {
3592       nsvg__scaleGradient(shape.fill.gradient, tx, ty, sx, sy);
3593       //memcpy(t.ptr, shape.fill.gradient.xform.ptr, float.sizeof*6);
3594       t.ptr[0..6] = shape.fill.gradient.xform[0..6];
3595       nsvg__xformInverse(shape.fill.gradient.xform.ptr, t.ptr);
3596     }
3597     if (shape.stroke.type == NSVG.PaintType.LinearGradient || shape.stroke.type == NSVG.PaintType.RadialGradient) {
3598       nsvg__scaleGradient(shape.stroke.gradient, tx, ty, sx, sy);
3599       //memcpy(t.ptr, shape.stroke.gradient.xform.ptr, float.sizeof*6);
3600       t.ptr[0..6] = shape.stroke.gradient.xform[0..6];
3601       nsvg__xformInverse(shape.stroke.gradient.xform.ptr, t.ptr);
3602     }
3603 
3604     shape.strokeWidth *= avgs;
3605     shape.strokeDashOffset *= avgs;
3606     foreach (immutable int i; 0..shape.strokeDashCount) shape.strokeDashArray[i] *= avgs;
3607   }
3608 }
3609 
3610 ///
3611 public NSVG* nsvgParse (const(char)[] input, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) {
3612   Parser* p;
3613   NSVG* ret = null;
3614 
3615   /*
3616   static if (NanoSVGHasVFS) {
3617     if (input.length > 4 && input[0..5] == "NSVG\x00" && units == "px" && dpi == 96) {
3618       return nsvgUnserialize(wrapStream(MemoryStreamRO(input)));
3619     }
3620   }
3621   */
3622 
3623   p = nsvg__createParser();
3624   if (p is null) return null;
3625   p.dpi = dpi;
3626   p.canvaswdt = (canvaswdt < 1 ? NSVGDefaults.CanvasWidth : canvaswdt);
3627   p.canvashgt = (canvashgt < 1 ? NSVGDefaults.CanvasHeight : canvashgt);
3628 
3629   nsvg__parseXML(input, &nsvg__startElement, &nsvg__endElement, &nsvg__content, p);
3630 
3631   // Scale to viewBox
3632   nsvg__scaleToViewbox(p, units);
3633 
3634   ret = p.image;
3635   p.image = null;
3636 
3637   nsvg__deleteParser(p);
3638 
3639   return ret;
3640 }
3641 
3642 ///
3643 public void kill (NSVG* image) {
3644   import core.stdc.string : memset;
3645   NSVG.Shape* snext, shape;
3646   if (image is null) return;
3647   shape = image.shapes;
3648   while (shape !is null) {
3649     snext = shape.next;
3650     nsvg__deletePaths(shape.paths);
3651     nsvg__deletePaint(&shape.fill);
3652     nsvg__deletePaint(&shape.stroke);
3653     xfree(shape);
3654     shape = snext;
3655   }
3656   memset(image, 0, (*image).sizeof);
3657   xfree(image);
3658 }
3659 
3660 } // nothrow @trusted @nogc
3661 
3662 
3663 ///
3664 public NSVG* nsvgParseFromFile (const(char)[] filename, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) nothrow {
3665   import core.stdc.stdlib : malloc, free;
3666   enum AddedBytes = 8;
3667 
3668   char* data = null;
3669   scope(exit) if (data !is null) free(data);
3670 
3671   if (filename.length == 0) return null;
3672 
3673   try {
3674     static if (NanoSVGHasIVVFS) {
3675       auto fl = VFile(filename);
3676       auto size = fl.size;
3677       if (size > int.max/8 || size < 1) return null;
3678       data = cast(char*)malloc(cast(uint)size+AddedBytes);
3679       if (data is null) return null;
3680       data[0..cast(uint)size+AddedBytes] = 0;
3681       fl.rawReadExact(data[0..cast(uint)size]);
3682       fl.close();
3683     } else {
3684       import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
3685       import std.internal.cstring : tempCString;
3686       auto fl = fopen(filename.tempCString, "rb");
3687       if (fl is null) return null;
3688       scope(exit) fclose(fl);
3689       if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return null;
3690       auto size = ftell(fl);
3691       if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return null;
3692       if (size < 16 || size > int.max/32) return null;
3693       data = cast(char*)malloc(cast(uint)size+AddedBytes);
3694       if (data is null) assert(0, "out of memory in NanoVega fontstash");
3695       data[0..cast(uint)size+AddedBytes] = 0;
3696       char* dptr = data;
3697       auto left = cast(uint)size;
3698       while (left > 0) {
3699         auto rd = fread(dptr, 1, left, fl);
3700         if (rd == 0) return null; // unexpected EOF or reading error, it doesn't matter
3701         dptr += rd;
3702         left -= rd;
3703       }
3704     }
3705     return nsvgParse(data[0..cast(uint)size], units, dpi, canvaswdt, canvashgt);
3706   } catch (Exception e) {
3707     return null;
3708   }
3709 }
3710 
3711 
3712 static if (NanoSVGHasIVVFS) {
3713 ///
3714 public NSVG* nsvgParseFromFile(ST) (auto ref ST fi, const(char)[] units="px", float dpi=96, int canvaswdt=-1, int canvashgt=-1) nothrow
3715 if (isReadableStream!ST && isSeekableStream!ST && streamHasSize!ST)
3716 {
3717   import core.stdc.stdlib : malloc, free;
3718 
3719   enum AddedBytes = 8;
3720   usize size;
3721   char* data = null;
3722   scope(exit) if (data is null) free(data);
3723 
3724   try {
3725     auto sz = fi.size;
3726     auto pp = fi.tell;
3727     if (pp >= sz) return null;
3728     sz -= pp;
3729     if (sz > 0x3ff_ffff) return null;
3730     size = cast(usize)sz;
3731     data = cast(char*)malloc(size+AddedBytes);
3732     if (data is null) return null;
3733     scope(exit) free(data);
3734     data[0..size+AddedBytes] = 0;
3735     fi.rawReadExact(data[0..size]);
3736     return nsvgParse(data[0..size], units, dpi, canvaswdt, canvashgt);
3737   } catch (Exception e) {
3738     return null;
3739   }
3740 }
3741 }
3742 
3743 
3744 // ////////////////////////////////////////////////////////////////////////// //
3745 // rasterizer
3746 private:
3747 nothrow @trusted @nogc {
3748 
3749 enum NSVG__SUBSAMPLES = 5;
3750 enum NSVG__FIXSHIFT = 10;
3751 enum NSVG__FIX = 1<<NSVG__FIXSHIFT;
3752 enum NSVG__FIXMASK = NSVG__FIX-1;
3753 enum NSVG__MEMPAGE_SIZE = 1024;
3754 
3755 struct NSVGedge {
3756   float x0 = 0, y0 = 0, x1 = 0, y1 = 0;
3757   int dir = 0;
3758   NSVGedge* next;
3759 }
3760 
3761 struct NSVGpoint {
3762   float x = 0, y = 0;
3763   float dx = 0, dy = 0;
3764   float len = 0;
3765   float dmx = 0, dmy = 0;
3766   ubyte flags = 0;
3767 }
3768 
3769 struct NSVGactiveEdge {
3770   int x = 0, dx = 0;
3771   float ey = 0;
3772   int dir = 0;
3773   NSVGactiveEdge *next;
3774 }
3775 
3776 struct NSVGmemPage {
3777   ubyte[NSVG__MEMPAGE_SIZE] mem;
3778   int size;
3779   NSVGmemPage* next;
3780 }
3781 
3782 struct NSVGcachedPaint {
3783   char type;
3784   char spread;
3785   float[6] xform = 0;
3786   uint[256] colors;
3787 }
3788 
3789 struct NSVGrasterizerS {
3790   float px = 0, py = 0;
3791 
3792   float tessTol = 0;
3793   float distTol = 0;
3794 
3795   NSVGedge* edges;
3796   int nedges;
3797   int cedges;
3798 
3799   NSVGpoint* points;
3800   int npoints;
3801   int cpoints;
3802 
3803   NSVGpoint* points2;
3804   int npoints2;
3805   int cpoints2;
3806 
3807   NSVGactiveEdge* freelist;
3808   NSVGmemPage* pages;
3809   NSVGmemPage* curpage;
3810 
3811   ubyte* scanline;
3812   int cscanline;
3813 
3814   ubyte* bitmap;
3815   int width, height, stride;
3816 }
3817 
3818 
3819 ///
3820 public NSVGrasterizer nsvgCreateRasterizer () {
3821   NSVGrasterizer r = xalloc!NSVGrasterizerS;
3822   if (r is null) goto error;
3823 
3824   r.tessTol = 0.25f;
3825   r.distTol = 0.01f;
3826 
3827   return r;
3828 
3829 error:
3830   r.kill();
3831   return null;
3832 }
3833 
3834 ///
3835 public void kill (NSVGrasterizer r) {
3836   NSVGmemPage* p;
3837 
3838   if (r is null) return;
3839 
3840   p = r.pages;
3841   while (p !is null) {
3842     NSVGmemPage* next = p.next;
3843     xfree(p);
3844     p = next;
3845   }
3846 
3847   if (r.edges) xfree(r.edges);
3848   if (r.points) xfree(r.points);
3849   if (r.points2) xfree(r.points2);
3850   if (r.scanline) xfree(r.scanline);
3851 
3852   xfree(r);
3853 }
3854 
3855 NSVGmemPage* nsvg__nextPage (NSVGrasterizer r, NSVGmemPage* cur) {
3856   NSVGmemPage *newp;
3857 
3858   // If using existing chain, return the next page in chain
3859   if (cur !is null && cur.next !is null) return cur.next;
3860 
3861   // Alloc new page
3862   newp = xalloc!NSVGmemPage;
3863   if (newp is null) return null;
3864 
3865   // Add to linked list
3866   if (cur !is null)
3867     cur.next = newp;
3868   else
3869     r.pages = newp;
3870 
3871   return newp;
3872 }
3873 
3874 void nsvg__resetPool (NSVGrasterizer r) {
3875   NSVGmemPage* p = r.pages;
3876   while (p !is null) {
3877     p.size = 0;
3878     p = p.next;
3879   }
3880   r.curpage = r.pages;
3881 }
3882 
3883 ubyte* nsvg__alloc (NSVGrasterizer r, int size) {
3884   ubyte* buf;
3885   if (size > NSVG__MEMPAGE_SIZE) return null;
3886   if (r.curpage is null || r.curpage.size+size > NSVG__MEMPAGE_SIZE) {
3887     r.curpage = nsvg__nextPage(r, r.curpage);
3888   }
3889   buf = &r.curpage.mem[r.curpage.size];
3890   r.curpage.size += size;
3891   return buf;
3892 }
3893 
3894 int nsvg__ptEquals (float x1, float y1, float x2, float y2, float tol) {
3895   immutable float dx = x2-x1;
3896   immutable float dy = y2-y1;
3897   return dx*dx+dy*dy < tol*tol;
3898 }
3899 
3900 void nsvg__addPathPoint (NSVGrasterizer r, float x, float y, int flags) {
3901   import core.stdc.stdlib : realloc;
3902 
3903   NSVGpoint* pt;
3904 
3905   if (r.npoints > 0) {
3906     pt = r.points+(r.npoints-1);
3907     if (nsvg__ptEquals(pt.x, pt.y, x, y, r.distTol)) {
3908       pt.flags |= flags;
3909       return;
3910     }
3911   }
3912 
3913   if (r.npoints+1 > r.cpoints) {
3914     r.cpoints = (r.cpoints > 0 ? r.cpoints*2 : 64);
3915     r.points = cast(NSVGpoint*)realloc(r.points, NSVGpoint.sizeof*r.cpoints+256);
3916     if (r.points is null) assert(0, "nanosvg: out of memory");
3917   }
3918 
3919   pt = r.points+r.npoints;
3920   pt.x = x;
3921   pt.y = y;
3922   pt.flags = cast(ubyte)flags;
3923   ++r.npoints;
3924 }
3925 
3926 void nsvg__appendPathPoint (NSVGrasterizer r, NSVGpoint pt) {
3927   import core.stdc.stdlib : realloc;
3928   if (r.npoints+1 > r.cpoints) {
3929     r.cpoints = (r.cpoints > 0 ? r.cpoints*2 : 64);
3930     r.points = cast(NSVGpoint*)realloc(r.points, NSVGpoint.sizeof*r.cpoints+256);
3931     if (r.points is null) assert(0, "nanosvg: out of memory");
3932   }
3933   r.points[r.npoints] = pt;
3934   ++r.npoints;
3935 }
3936 
3937 void nsvg__duplicatePoints (NSVGrasterizer r) {
3938   import core.stdc.stdlib : realloc;
3939   import core.stdc.string : memmove;
3940   if (r.npoints > r.cpoints2) {
3941     r.cpoints2 = r.npoints;
3942     r.points2 = cast(NSVGpoint*)realloc(r.points2, NSVGpoint.sizeof*r.cpoints2+256);
3943     if (r.points2 is null) assert(0, "nanosvg: out of memory");
3944   }
3945   memmove(r.points2, r.points, NSVGpoint.sizeof*r.npoints);
3946   r.npoints2 = r.npoints;
3947 }
3948 
3949 void nsvg__addEdge (NSVGrasterizer r, float x0, float y0, float x1, float y1) {
3950   NSVGedge* e;
3951 
3952   // Skip horizontal edges
3953   if (y0 == y1) return;
3954 
3955   if (r.nedges+1 > r.cedges) {
3956     import core.stdc.stdlib : realloc;
3957     r.cedges = (r.cedges > 0 ? r.cedges*2 : 64);
3958     r.edges = cast(NSVGedge*)realloc(r.edges, NSVGedge.sizeof*r.cedges+256);
3959     if (r.edges is null) assert(0, "nanosvg: out of memory");
3960   }
3961 
3962   e = &r.edges[r.nedges];
3963   ++r.nedges;
3964 
3965   if (y0 < y1) {
3966     e.x0 = x0;
3967     e.y0 = y0;
3968     e.x1 = x1;
3969     e.y1 = y1;
3970     e.dir = 1;
3971   } else {
3972     e.x0 = x1;
3973     e.y0 = y1;
3974     e.x1 = x0;
3975     e.y1 = y0;
3976     e.dir = -1;
3977   }
3978 }
3979 
3980 float nsvg__normalize (float *x, float* y) {
3981   immutable float d = sqrtf((*x)*(*x)+(*y)*(*y));
3982   if (d > 1e-6f) {
3983     float id = 1.0f/d;
3984     *x *= id;
3985     *y *= id;
3986   }
3987   return d;
3988 }
3989 
3990 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) {
3991   if (level > 10) return;
3992 
3993   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
3994   version(none) {
3995     if (level == 0) {
3996       static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
3997         immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
3998         return (fabsf(cz*cz) <= 0.01f);
3999       }
4000       if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4001         //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4002         nsvg__flattenCubicBezAFD(r, x1, y1, x2, y2, x3, y3, x4, y4, type);
4003         return;
4004       }
4005     }
4006   }
4007 
4008   immutable x12 = (x1+x2)*0.5f;
4009   immutable y12 = (y1+y2)*0.5f;
4010   immutable x23 = (x2+x3)*0.5f;
4011   immutable y23 = (y2+y3)*0.5f;
4012   immutable x34 = (x3+x4)*0.5f;
4013   immutable y34 = (y3+y4)*0.5f;
4014   immutable x123 = (x12+x23)*0.5f;
4015   immutable y123 = (y12+y23)*0.5f;
4016 
4017   immutable dx = x4-x1;
4018   immutable dy = y4-y1;
4019   immutable d2 = fabsf(((x2-x4)*dy-(y2-y4)*dx));
4020   immutable d3 = fabsf(((x3-x4)*dy-(y3-y4)*dx));
4021 
4022   if ((d2+d3)*(d2+d3) < r.tessTol*(dx*dx+dy*dy)) {
4023     nsvg__addPathPoint(r, x4, y4, type);
4024     return;
4025   }
4026 
4027   immutable x234 = (x23+x34)*0.5f;
4028   immutable y234 = (y23+y34)*0.5f;
4029   immutable x1234 = (x123+x234)*0.5f;
4030   immutable y1234 = (y123+y234)*0.5f;
4031 
4032   // "taxicab" / "manhattan" check for flat curves
4033   if (fabsf(x1+x3-x2-x2)+fabsf(y1+y3-y2-y2)+fabsf(x2+x4-x3-x3)+fabsf(y2+y4-y3-y3) < r.tessTol/4) {
4034     nsvg__addPathPoint(r, x1234, y1234, type);
4035     return;
4036   }
4037 
4038   nsvg__flattenCubicBez(r, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4039   nsvg__flattenCubicBez(r, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4040 }
4041 
4042 // Adaptive forward differencing for bezier tesselation.
4043 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4044 // "Adaptive forward differencing for rendering curves and surfaces."
4045 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4046 // original code by Taylor Holliday <taylor@audulus.com>
4047 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 {
4048   enum AFD_ONE = (1<<10);
4049 
4050   // power basis
4051   immutable float ax = -x1+3*x2-3*x3+x4;
4052   immutable float ay = -y1+3*y2-3*y3+y4;
4053   immutable float bx = 3*x1-6*x2+3*x3;
4054   immutable float by = 3*y1-6*y2+3*y3;
4055   immutable float cx = -3*x1+3*x2;
4056   immutable float cy = -3*y1+3*y2;
4057 
4058   // Transform to forward difference basis (stepsize 1)
4059   float px = x1;
4060   float py = y1;
4061   float dx = ax+bx+cx;
4062   float dy = ay+by+cy;
4063   float ddx = 6*ax+2*bx;
4064   float ddy = 6*ay+2*by;
4065   float dddx = 6*ax;
4066   float dddy = 6*ay;
4067 
4068   //printf("dx: %f, dy: %f\n", dx, dy);
4069   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4070   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4071 
4072   int t = 0;
4073   int dt = AFD_ONE;
4074 
4075   immutable float tol = r.tessTol*4;
4076 
4077   while (t < AFD_ONE) {
4078     // Flatness measure.
4079     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4080 
4081     // printf("d: %f, th: %f\n", d, th);
4082 
4083     // Go to higher resolution if we're moving a lot or overshooting the end.
4084     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4085       // printf("up\n");
4086 
4087       // Apply L to the curve. Increase curve resolution.
4088       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4089       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4090       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4091       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4092       dddx = (1.0f/8.0f)*dddx;
4093       dddy = (1.0f/8.0f)*dddy;
4094 
4095       // Half the stepsize.
4096       dt >>= 1;
4097 
4098       // Recompute d
4099       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4100     }
4101 
4102     // Go to lower resolution if we're really flat
4103     // and we aren't going to overshoot the end.
4104     // XXX: tol/32 is just a guess for when we are too flat.
4105     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4106       // printf("down\n");
4107 
4108       // Apply L^(-1) to the curve. Decrease curve resolution.
4109       dx = 2*dx+ddx;
4110       dy = 2*dy+ddy;
4111       ddx = 4*ddx+4*dddx;
4112       ddy = 4*ddy+4*dddy;
4113       dddx = 8*dddx;
4114       dddy = 8*dddy;
4115 
4116       // Double the stepsize.
4117       dt <<= 1;
4118 
4119       // Recompute d
4120       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4121     }
4122 
4123     // Forward differencing.
4124     px += dx;
4125     py += dy;
4126     dx += ddx;
4127     dy += ddy;
4128     ddx += dddx;
4129     ddy += dddy;
4130 
4131     // Output a point.
4132     nsvg__addPathPoint(r, px, py, (t > 0 ? type : 0));
4133 
4134     // Advance along the curve.
4135     t += dt;
4136 
4137     // Ensure we don't overshoot.
4138     assert(t <= AFD_ONE);
4139   }
4140 }
4141 
4142 void nsvg__flattenShape (NSVGrasterizer r, const(NSVG.Shape)* shape, float scale) {
4143   for (const(NSVG.Path)* path = shape.paths; path !is null; path = path.next) {
4144     r.npoints = 0;
4145     if (path.empty) continue;
4146     // first point
4147     float x0, y0;
4148     path.startPoint(&x0, &y0);
4149     nsvg__addPathPoint(r, x0*scale, y0*scale, 0);
4150     // cubic beziers
4151     path.asCubics(delegate (const(float)[] cubic) {
4152       assert(cubic.length >= 8);
4153       nsvg__flattenCubicBez(r,
4154         cubic.ptr[0]*scale, cubic.ptr[1]*scale,
4155         cubic.ptr[2]*scale, cubic.ptr[3]*scale,
4156         cubic.ptr[4]*scale, cubic.ptr[5]*scale,
4157         cubic.ptr[6]*scale, cubic.ptr[7]*scale,
4158         0, 0);
4159     });
4160     // close path
4161     nsvg__addPathPoint(r, x0*scale, y0*scale, 0);
4162     // Flatten path
4163     /+
4164     nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, 0);
4165     for (int i = 0; i < path.npts-1; i += 3) {
4166       const(float)* p = path.pts+(i*2);
4167       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);
4168     }
4169     // Close path
4170     nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, 0);
4171     +/
4172     // Build edges
4173     for (int i = 0, j = r.npoints-1; i < r.npoints; j = i++) {
4174       nsvg__addEdge(r, r.points[j].x, r.points[j].y, r.points[i].x, r.points[i].y);
4175     }
4176   }
4177 }
4178 
4179 alias PtFlags = ubyte;
4180 enum : ubyte {
4181   PtFlagsCorner = 0x01,
4182   PtFlagsBevel = 0x02,
4183   PtFlagsLeft = 0x04,
4184 }
4185 
4186 void nsvg__initClosed (NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
4187   immutable float w = lineWidth*0.5f;
4188   float dx = p1.x-p0.x;
4189   float dy = p1.y-p0.y;
4190   immutable float len = nsvg__normalize(&dx, &dy);
4191   immutable float px = p0.x+dx*len*0.5f, py = p0.y+dy*len*0.5f;
4192   immutable float dlx = dy, dly = -dx;
4193   immutable float lx = px-dlx*w, ly = py-dly*w;
4194   immutable float rx = px+dlx*w, ry = py+dly*w;
4195   left.x = lx; left.y = ly;
4196   right.x = rx; right.y = ry;
4197 }
4198 
4199 void nsvg__buttCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int connect) {
4200   immutable float w = lineWidth*0.5f;
4201   immutable float px = p.x, py = p.y;
4202   immutable float dlx = dy, dly = -dx;
4203   immutable float lx = px-dlx*w, ly = py-dly*w;
4204   immutable float rx = px+dlx*w, ry = py+dly*w;
4205 
4206   nsvg__addEdge(r, lx, ly, rx, ry);
4207 
4208   if (connect) {
4209     nsvg__addEdge(r, left.x, left.y, lx, ly);
4210     nsvg__addEdge(r, rx, ry, right.x, right.y);
4211   }
4212   left.x = lx; left.y = ly;
4213   right.x = rx; right.y = ry;
4214 }
4215 
4216 void nsvg__squareCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int connect) {
4217   immutable float w = lineWidth*0.5f;
4218   immutable float px = p.x-dx*w, py = p.y-dy*w;
4219   immutable float dlx = dy, dly = -dx;
4220   immutable float lx = px-dlx*w, ly = py-dly*w;
4221   immutable float rx = px+dlx*w, ry = py+dly*w;
4222 
4223   nsvg__addEdge(r, lx, ly, rx, ry);
4224 
4225   if (connect) {
4226     nsvg__addEdge(r, left.x, left.y, lx, ly);
4227     nsvg__addEdge(r, rx, ry, right.x, right.y);
4228   }
4229   left.x = lx; left.y = ly;
4230   right.x = rx; right.y = ry;
4231 }
4232 
4233 void nsvg__roundCap (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p, float dx, float dy, float lineWidth, int ncap, int connect) {
4234   immutable float w = lineWidth*0.5f;
4235   immutable float px = p.x, py = p.y;
4236   immutable float dlx = dy, dly = -dx;
4237   float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0;
4238 
4239   foreach (int i; 0..ncap) {
4240     immutable float a = i/cast(float)(ncap-1)*NSVG_PI;
4241     immutable float ax = cosf(a)*w, ay = sinf(a)*w;
4242     immutable float x = px-dlx*ax-dx*ay;
4243     immutable float y = py-dly*ax-dy*ay;
4244 
4245     if (i > 0) nsvg__addEdge(r, prevx, prevy, x, y);
4246 
4247     prevx = x;
4248     prevy = y;
4249 
4250     if (i == 0) {
4251       lx = x;
4252       ly = y;
4253     } else if (i == ncap-1) {
4254       rx = x;
4255       ry = y;
4256     }
4257   }
4258 
4259   if (connect) {
4260     nsvg__addEdge(r, left.x, left.y, lx, ly);
4261     nsvg__addEdge(r, rx, ry, right.x, right.y);
4262   }
4263 
4264   left.x = lx; left.y = ly;
4265   right.x = rx; right.y = ry;
4266 }
4267 
4268 void nsvg__bevelJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
4269   immutable float w = lineWidth*0.5f;
4270   immutable float dlx0 = p0.dy, dly0 = -p0.dx;
4271   immutable float dlx1 = p1.dy, dly1 = -p1.dx;
4272   immutable float lx0 = p1.x-(dlx0*w), ly0 = p1.y-(dly0*w);
4273   immutable float rx0 = p1.x+(dlx0*w), ry0 = p1.y+(dly0*w);
4274   immutable float lx1 = p1.x-(dlx1*w), ly1 = p1.y-(dly1*w);
4275   immutable float rx1 = p1.x+(dlx1*w), ry1 = p1.y+(dly1*w);
4276 
4277   nsvg__addEdge(r, lx0, ly0, left.x, left.y);
4278   nsvg__addEdge(r, lx1, ly1, lx0, ly0);
4279 
4280   nsvg__addEdge(r, right.x, right.y, rx0, ry0);
4281   nsvg__addEdge(r, rx0, ry0, rx1, ry1);
4282 
4283   left.x = lx1; left.y = ly1;
4284   right.x = rx1; right.y = ry1;
4285 }
4286 
4287 void nsvg__miterJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth) {
4288   immutable float w = lineWidth*0.5f;
4289   immutable float dlx0 = p0.dy, dly0 = -p0.dx;
4290   immutable float dlx1 = p1.dy, dly1 = -p1.dx;
4291   float lx0 = void, rx0 = void, lx1 = void, rx1 = void;
4292   float ly0 = void, ry0 = void, ly1 = void, ry1 = void;
4293 
4294   if (p1.flags&PtFlagsLeft) {
4295     lx0 = lx1 = p1.x-p1.dmx*w;
4296     ly0 = ly1 = p1.y-p1.dmy*w;
4297     nsvg__addEdge(r, lx1, ly1, left.x, left.y);
4298 
4299     rx0 = p1.x+(dlx0*w);
4300     ry0 = p1.y+(dly0*w);
4301     rx1 = p1.x+(dlx1*w);
4302     ry1 = p1.y+(dly1*w);
4303     nsvg__addEdge(r, right.x, right.y, rx0, ry0);
4304     nsvg__addEdge(r, rx0, ry0, rx1, ry1);
4305   } else {
4306     lx0 = p1.x-(dlx0*w);
4307     ly0 = p1.y-(dly0*w);
4308     lx1 = p1.x-(dlx1*w);
4309     ly1 = p1.y-(dly1*w);
4310     nsvg__addEdge(r, lx0, ly0, left.x, left.y);
4311     nsvg__addEdge(r, lx1, ly1, lx0, ly0);
4312 
4313     rx0 = rx1 = p1.x+p1.dmx*w;
4314     ry0 = ry1 = p1.y+p1.dmy*w;
4315     nsvg__addEdge(r, right.x, right.y, rx1, ry1);
4316   }
4317 
4318   left.x = lx1; left.y = ly1;
4319   right.x = rx1; right.y = ry1;
4320 }
4321 
4322 void nsvg__roundJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p0, const(NSVGpoint)* p1, float lineWidth, int ncap) {
4323   int i, n;
4324   float w = lineWidth*0.5f;
4325   float dlx0 = p0.dy, dly0 = -p0.dx;
4326   float dlx1 = p1.dy, dly1 = -p1.dx;
4327   float a0 = atan2f(dly0, dlx0);
4328   float a1 = atan2f(dly1, dlx1);
4329   float da = a1-a0;
4330   float lx, ly, rx, ry;
4331 
4332   if (da < NSVG_PI) da += NSVG_PI*2;
4333   if (da > NSVG_PI) da -= NSVG_PI*2;
4334 
4335   n = cast(int)ceilf((fabsf(da)/NSVG_PI)*ncap);
4336   if (n < 2) n = 2;
4337   if (n > ncap) n = ncap;
4338 
4339   lx = left.x;
4340   ly = left.y;
4341   rx = right.x;
4342   ry = right.y;
4343 
4344   for (i = 0; i < n; i++) {
4345     float u = i/cast(float)(n-1);
4346     float a = a0+u*da;
4347     float ax = cosf(a)*w, ay = sinf(a)*w;
4348     float lx1 = p1.x-ax, ly1 = p1.y-ay;
4349     float rx1 = p1.x+ax, ry1 = p1.y+ay;
4350 
4351     nsvg__addEdge(r, lx1, ly1, lx, ly);
4352     nsvg__addEdge(r, rx, ry, rx1, ry1);
4353 
4354     lx = lx1; ly = ly1;
4355     rx = rx1; ry = ry1;
4356   }
4357 
4358   left.x = lx; left.y = ly;
4359   right.x = rx; right.y = ry;
4360 }
4361 
4362 void nsvg__straightJoin (NSVGrasterizer r, NSVGpoint* left, NSVGpoint* right, const(NSVGpoint)* p1, float lineWidth) {
4363   float w = lineWidth*0.5f;
4364   float lx = p1.x-(p1.dmx*w), ly = p1.y-(p1.dmy*w);
4365   float rx = p1.x+(p1.dmx*w), ry = p1.y+(p1.dmy*w);
4366 
4367   nsvg__addEdge(r, lx, ly, left.x, left.y);
4368   nsvg__addEdge(r, right.x, right.y, rx, ry);
4369 
4370   left.x = lx; left.y = ly;
4371   right.x = rx; right.y = ry;
4372 }
4373 
4374 int nsvg__curveDivs (float r, float arc, float tol) {
4375   float da = acosf(r/(r+tol))*2.0f;
4376   int divs = cast(int)ceilf(arc/da);
4377   if (divs < 2) divs = 2;
4378   return divs;
4379 }
4380 
4381 void nsvg__expandStroke (NSVGrasterizer r, const(NSVGpoint)* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) {
4382   int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r.tessTol);  // Calculate divisions per half circle.
4383   //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};
4384   NSVGpoint left, right, firstLeft, firstRight;
4385   const(NSVGpoint)* p0, p1;
4386   int j, s, e;
4387 
4388   // Build stroke edges
4389   if (closed) {
4390     // Looping
4391     p0 = &points[npoints-1];
4392     p1 = &points[0];
4393     s = 0;
4394     e = npoints;
4395   } else {
4396     // Add cap
4397     p0 = &points[0];
4398     p1 = &points[1];
4399     s = 1;
4400     e = npoints-1;
4401   }
4402 
4403   if (closed) {
4404     nsvg__initClosed(&left, &right, p0, p1, lineWidth);
4405     firstLeft = left;
4406     firstRight = right;
4407   } else {
4408     // Add cap
4409     float dx = p1.x-p0.x;
4410     float dy = p1.y-p0.y;
4411     nsvg__normalize(&dx, &dy);
4412     if (lineCap == NSVG.LineCap.Butt)
4413       nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
4414     else if (lineCap == NSVG.LineCap.Square)
4415       nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
4416     else if (lineCap == NSVG.LineCap.Round)
4417       nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);
4418   }
4419 
4420   for (j = s; j < e; ++j) {
4421     if (p1.flags&PtFlagsCorner) {
4422       if (lineJoin == NSVG.LineJoin.Round)
4423         nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);
4424       else if (lineJoin == NSVG.LineJoin.Bevel || (p1.flags&PtFlagsBevel))
4425         nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);
4426       else
4427         nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);
4428     } else {
4429       nsvg__straightJoin(r, &left, &right, p1, lineWidth);
4430     }
4431     p0 = p1++;
4432   }
4433 
4434   if (closed) {
4435     // Loop it
4436     nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);
4437     nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);
4438   } else {
4439     // Add cap
4440     float dx = p1.x-p0.x;
4441     float dy = p1.y-p0.y;
4442     nsvg__normalize(&dx, &dy);
4443     if (lineCap == NSVG.LineCap.Butt)
4444       nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
4445     else if (lineCap == NSVG.LineCap.Square)
4446       nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
4447     else if (lineCap == NSVG.LineCap.Round)
4448       nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);
4449   }
4450 }
4451 
4452 void nsvg__prepareStroke (NSVGrasterizer r, float miterLimit, int lineJoin) {
4453   int i, j;
4454   NSVGpoint* p0, p1;
4455 
4456   p0 = r.points+(r.npoints-1);
4457   p1 = r.points;
4458   for (i = 0; i < r.npoints; i++) {
4459     // Calculate segment direction and length
4460     p0.dx = p1.x-p0.x;
4461     p0.dy = p1.y-p0.y;
4462     p0.len = nsvg__normalize(&p0.dx, &p0.dy);
4463     // Advance
4464     p0 = p1++;
4465   }
4466 
4467   // calculate joins
4468   p0 = r.points+(r.npoints-1);
4469   p1 = r.points;
4470   for (j = 0; j < r.npoints; j++) {
4471     float dlx0, dly0, dlx1, dly1, dmr2, cross;
4472     dlx0 = p0.dy;
4473     dly0 = -p0.dx;
4474     dlx1 = p1.dy;
4475     dly1 = -p1.dx;
4476     // Calculate extrusions
4477     p1.dmx = (dlx0+dlx1)*0.5f;
4478     p1.dmy = (dly0+dly1)*0.5f;
4479     dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
4480     if (dmr2 > 0.000001f) {
4481       float s2 = 1.0f/dmr2;
4482       if (s2 > 600.0f) {
4483         s2 = 600.0f;
4484       }
4485       p1.dmx *= s2;
4486       p1.dmy *= s2;
4487     }
4488 
4489     // Clear flags, but keep the corner.
4490     p1.flags = (p1.flags&PtFlagsCorner) ? PtFlagsCorner : 0;
4491 
4492     // Keep track of left turns.
4493     cross = p1.dx*p0.dy-p0.dx*p1.dy;
4494     if (cross > 0.0f)
4495       p1.flags |= PtFlagsLeft;
4496 
4497     // Check to see if the corner needs to be beveled.
4498     if (p1.flags&PtFlagsCorner) {
4499       if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NSVG.LineJoin.Bevel || lineJoin == NSVG.LineJoin.Round) {
4500         p1.flags |= PtFlagsBevel;
4501       }
4502     }
4503 
4504     p0 = p1++;
4505   }
4506 }
4507 
4508 void nsvg__flattenShapeStroke (NSVGrasterizer r, const(NSVG.Shape)* shape, float scale) {
4509   int i, j, closed;
4510   const(NSVG.Path)* path;
4511   const(NSVGpoint)* p0, p1;
4512   float miterLimit = shape.miterLimit;
4513   int lineJoin = shape.strokeLineJoin;
4514   int lineCap = shape.strokeLineCap;
4515   float lineWidth = shape.strokeWidth*scale;
4516 
4517   for (path = shape.paths; path !is null; path = path.next) {
4518     // Flatten path
4519     r.npoints = 0;
4520     if (!path.empty) {
4521       // first point
4522       {
4523         float x0, y0;
4524         path.startPoint(&x0, &y0);
4525         nsvg__addPathPoint(r, x0*scale, y0*scale, PtFlagsCorner);
4526       }
4527       // cubic beziers
4528       path.asCubics(delegate (const(float)[] cubic) {
4529         assert(cubic.length >= 8);
4530         nsvg__flattenCubicBez(r,
4531           cubic.ptr[0]*scale, cubic.ptr[1]*scale,
4532           cubic.ptr[2]*scale, cubic.ptr[3]*scale,
4533           cubic.ptr[4]*scale, cubic.ptr[5]*scale,
4534           cubic.ptr[6]*scale, cubic.ptr[7]*scale,
4535           0, PtFlagsCorner);
4536       });
4537     }
4538     /+
4539     nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, PtFlagsCorner);
4540     for (i = 0; i < path.npts-1; i += 3) {
4541       const(float)* p = &path.pts[i*2];
4542       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);
4543     }
4544     +/
4545     if (r.npoints < 2) continue;
4546 
4547     closed = path.closed;
4548 
4549     // If the first and last points are the same, remove the last, mark as closed path.
4550     p0 = &r.points[r.npoints-1];
4551     p1 = &r.points[0];
4552     if (nsvg__ptEquals(p0.x, p0.y, p1.x, p1.y, r.distTol)) {
4553       r.npoints--;
4554       p0 = &r.points[r.npoints-1];
4555       closed = 1;
4556     }
4557 
4558     if (shape.strokeDashCount > 0) {
4559       int idash = 0, dashState = 1;
4560       float totalDist = 0, dashLen, allDashLen, dashOffset;
4561       NSVGpoint cur;
4562 
4563       if (closed) nsvg__appendPathPoint(r, r.points[0]);
4564 
4565       // Duplicate points . points2.
4566       nsvg__duplicatePoints(r);
4567 
4568       r.npoints = 0;
4569       cur = r.points2[0];
4570       nsvg__appendPathPoint(r, cur);
4571 
4572       // Figure out dash offset.
4573       allDashLen = 0;
4574       for (j = 0; j < shape.strokeDashCount; j++) allDashLen += shape.strokeDashArray[j];
4575       if (shape.strokeDashCount&1) allDashLen *= 2.0f;
4576       // Find location inside pattern
4577       dashOffset = fmodf(shape.strokeDashOffset, allDashLen);
4578       if (dashOffset < 0.0f) dashOffset += allDashLen;
4579 
4580       while (dashOffset > shape.strokeDashArray[idash]) {
4581         dashOffset -= shape.strokeDashArray[idash];
4582         idash = (idash+1)%shape.strokeDashCount;
4583       }
4584       dashLen = (shape.strokeDashArray[idash]-dashOffset)*scale;
4585 
4586       for (j = 1; j < r.npoints2; ) {
4587         float dx = r.points2[j].x-cur.x;
4588         float dy = r.points2[j].y-cur.y;
4589         float dist = sqrtf(dx*dx+dy*dy);
4590 
4591         if (totalDist+dist > dashLen) {
4592           // Calculate intermediate point
4593           float d = (dashLen-totalDist)/dist;
4594           float x = cur.x+dx*d;
4595           float y = cur.y+dy*d;
4596           nsvg__addPathPoint(r, x, y, PtFlagsCorner);
4597 
4598           // Stroke
4599           if (r.npoints > 1 && dashState) {
4600             nsvg__prepareStroke(r, miterLimit, lineJoin);
4601             nsvg__expandStroke(r, r.points, r.npoints, 0, lineJoin, lineCap, lineWidth);
4602           }
4603           // Advance dash pattern
4604           dashState = !dashState;
4605           idash = (idash+1)%shape.strokeDashCount;
4606           dashLen = shape.strokeDashArray[idash]*scale;
4607           // Restart
4608           cur.x = x;
4609           cur.y = y;
4610           cur.flags = PtFlagsCorner;
4611           totalDist = 0.0f;
4612           r.npoints = 0;
4613           nsvg__appendPathPoint(r, cur);
4614         } else {
4615           totalDist += dist;
4616           cur = r.points2[j];
4617           nsvg__appendPathPoint(r, cur);
4618           j++;
4619         }
4620       }
4621       // Stroke any leftover path
4622       if (r.npoints > 1 && dashState) nsvg__expandStroke(r, r.points, r.npoints, 0, lineJoin, lineCap, lineWidth);
4623     } else {
4624       nsvg__prepareStroke(r, miterLimit, lineJoin);
4625       nsvg__expandStroke(r, r.points, r.npoints, closed, lineJoin, lineCap, lineWidth);
4626     }
4627   }
4628 }
4629 
4630 extern(C) int nsvg__cmpEdge (in void *p, in void *q) nothrow @trusted @nogc {
4631   NSVGedge* a = cast(NSVGedge*)p;
4632   NSVGedge* b = cast(NSVGedge*)q;
4633   if (a.y0 < b.y0) return -1;
4634   if (a.y0 > b.y0) return  1;
4635   return 0;
4636 }
4637 
4638 
4639 static NSVGactiveEdge* nsvg__addActive (NSVGrasterizer r, const(NSVGedge)* e, float startPoint) {
4640   NSVGactiveEdge* z;
4641 
4642   if (r.freelist !is null) {
4643     // Restore from freelist.
4644     z = r.freelist;
4645     r.freelist = z.next;
4646   } else {
4647     // Alloc new edge.
4648     z = cast(NSVGactiveEdge*)nsvg__alloc(r, NSVGactiveEdge.sizeof);
4649     if (z is null) return null;
4650   }
4651 
4652   immutable float dxdy = (e.x1-e.x0)/(e.y1-e.y0);
4653   //STBTT_assert(e.y0 <= start_point);
4654   // round dx down to avoid going too far
4655   if (dxdy < 0)
4656     z.dx = cast(int)(-floorf(NSVG__FIX*-dxdy));
4657   else
4658     z.dx = cast(int)floorf(NSVG__FIX*dxdy);
4659   z.x = cast(int)floorf(NSVG__FIX*(e.x0+dxdy*(startPoint-e.y0)));
4660   //z.x -= off_x*FIX;
4661   z.ey = e.y1;
4662   z.next = null;
4663   z.dir = e.dir;
4664 
4665   return z;
4666 }
4667 
4668 void nsvg__freeActive (NSVGrasterizer r, NSVGactiveEdge* z) {
4669   z.next = r.freelist;
4670   r.freelist = z;
4671 }
4672 
4673 void nsvg__fillScanline (ubyte* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) {
4674   int i = x0>>NSVG__FIXSHIFT;
4675   int j = x1>>NSVG__FIXSHIFT;
4676   if (i < *xmin) *xmin = i;
4677   if (j > *xmax) *xmax = j;
4678   if (i < len && j >= 0) {
4679     if (i == j) {
4680       // x0, x1 are the same pixel, so compute combined coverage
4681       scanline[i] += cast(ubyte)((x1-x0)*maxWeight>>NSVG__FIXSHIFT);
4682     } else {
4683       if (i >= 0) // add antialiasing for x0
4684         scanline[i] += cast(ubyte)(((NSVG__FIX-(x0&NSVG__FIXMASK))*maxWeight)>>NSVG__FIXSHIFT);
4685       else
4686         i = -1; // clip
4687 
4688       if (j < len) // add antialiasing for x1
4689         scanline[j] += cast(ubyte)(((x1&NSVG__FIXMASK)*maxWeight)>>NSVG__FIXSHIFT);
4690       else
4691         j = len; // clip
4692 
4693       for (++i; i < j; ++i) // fill pixels between x0 and x1
4694         scanline[i] += cast(ubyte)maxWeight;
4695     }
4696   }
4697 }
4698 
4699 // note: this routine clips fills that extend off the edges... ideally this
4700 // wouldn't happen, but it could happen if the truetype glyph bounding boxes
4701 // are wrong, or if the user supplies a too-small bitmap
4702 void nsvg__fillActiveEdges (ubyte* scanline, int len, const(NSVGactiveEdge)* e, int maxWeight, int* xmin, int* xmax, char fillRule) {
4703   // non-zero winding fill
4704   int x0 = 0, w = 0;
4705   if (fillRule == NSVG.FillRule.NonZero) {
4706     // Non-zero
4707     while (e !is null) {
4708       if (w == 0) {
4709         // if we're currently at zero, we need to record the edge start point
4710         x0 = e.x; w += e.dir;
4711       } else {
4712         int x1 = e.x; w += e.dir;
4713         // if we went to zero, we need to draw
4714         if (w == 0) nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
4715       }
4716       e = e.next;
4717     }
4718   } else if (fillRule == NSVG.FillRule.EvenOdd) {
4719     // Even-odd
4720     while (e !is null) {
4721       if (w == 0) {
4722         // if we're currently at zero, we need to record the edge start point
4723         x0 = e.x; w = 1;
4724       } else {
4725         int x1 = e.x; w = 0;
4726         nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
4727       }
4728       e = e.next;
4729     }
4730   }
4731 }
4732 
4733 float nsvg__clampf() (float a, float mn, float mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
4734 
4735 uint nsvg__RGBA() (ubyte r, ubyte g, ubyte b, ubyte a) { pragma(inline, true); return (r)|(g<<8)|(b<<16)|(a<<24); }
4736 
4737 uint nsvg__lerpRGBA (uint c0, uint c1, float u) {
4738   int iu = cast(int)(nsvg__clampf(u, 0.0f, 1.0f)*256.0f);
4739   int r = (((c0)&0xff)*(256-iu)+(((c1)&0xff)*iu))>>8;
4740   int g = (((c0>>8)&0xff)*(256-iu)+(((c1>>8)&0xff)*iu))>>8;
4741   int b = (((c0>>16)&0xff)*(256-iu)+(((c1>>16)&0xff)*iu))>>8;
4742   int a = (((c0>>24)&0xff)*(256-iu)+(((c1>>24)&0xff)*iu))>>8;
4743   return nsvg__RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
4744 }
4745 
4746 uint nsvg__applyOpacity (uint c, float u) {
4747   int iu = cast(int)(nsvg__clampf(u, 0.0f, 1.0f)*256.0f);
4748   int r = (c)&0xff;
4749   int g = (c>>8)&0xff;
4750   int b = (c>>16)&0xff;
4751   int a = (((c>>24)&0xff)*iu)>>8;
4752   return nsvg__RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
4753 }
4754 
4755 int nsvg__div255() (int x) { pragma(inline, true); return ((x+1)*257)>>16; }
4756 
4757 void nsvg__scanlineSolid (ubyte* dst, int count, ubyte* cover, int x, int y, float tx, float ty, float scale, const(NSVGcachedPaint)* cache) {
4758   if (cache.type == NSVG.PaintType.Color) {
4759     int cr = cache.colors[0]&0xff;
4760     int cg = (cache.colors[0]>>8)&0xff;
4761     int cb = (cache.colors[0]>>16)&0xff;
4762     int ca = (cache.colors[0]>>24)&0xff;
4763 
4764     foreach (int i; 0..count) {
4765       int r, g, b;
4766       int a = nsvg__div255(cast(int)cover[0]*ca);
4767       int ia = 255-a;
4768       // Premultiply
4769       r = nsvg__div255(cr*a);
4770       g = nsvg__div255(cg*a);
4771       b = nsvg__div255(cb*a);
4772 
4773       // Blend over
4774       r += nsvg__div255(ia*cast(int)dst[0]);
4775       g += nsvg__div255(ia*cast(int)dst[1]);
4776       b += nsvg__div255(ia*cast(int)dst[2]);
4777       a += nsvg__div255(ia*cast(int)dst[3]);
4778 
4779       dst[0] = cast(ubyte)r;
4780       dst[1] = cast(ubyte)g;
4781       dst[2] = cast(ubyte)b;
4782       dst[3] = cast(ubyte)a;
4783 
4784       ++cover;
4785       dst += 4;
4786     }
4787   } else if (cache.type == NSVG.PaintType.LinearGradient) {
4788     // TODO: spread modes.
4789     // TODO: plenty of opportunities to optimize.
4790     const(float)* t = cache.xform.ptr;
4791     //int i, cr, cg, cb, ca;
4792     //uint c;
4793 
4794     float fx = (x-tx)/scale;
4795     float fy = (y-ty)/scale;
4796     float dx = 1.0f/scale;
4797 
4798     foreach (int i; 0..count) {
4799       //int r, g, b, a, ia;
4800       float gy = fx*t[1]+fy*t[3]+t[5];
4801       uint c = cache.colors[cast(int)nsvg__clampf(gy*255.0f, 0, 255.0f)];
4802       int cr = (c)&0xff;
4803       int cg = (c>>8)&0xff;
4804       int cb = (c>>16)&0xff;
4805       int ca = (c>>24)&0xff;
4806 
4807       int a = nsvg__div255(cast(int)cover[0]*ca);
4808       int ia = 255-a;
4809 
4810       // Premultiply
4811       int r = nsvg__div255(cr*a);
4812       int g = nsvg__div255(cg*a);
4813       int b = nsvg__div255(cb*a);
4814 
4815       // Blend over
4816       r += nsvg__div255(ia*cast(int)dst[0]);
4817       g += nsvg__div255(ia*cast(int)dst[1]);
4818       b += nsvg__div255(ia*cast(int)dst[2]);
4819       a += nsvg__div255(ia*cast(int)dst[3]);
4820 
4821       dst[0] = cast(ubyte)r;
4822       dst[1] = cast(ubyte)g;
4823       dst[2] = cast(ubyte)b;
4824       dst[3] = cast(ubyte)a;
4825 
4826       ++cover;
4827       dst += 4;
4828       fx += dx;
4829     }
4830   } else if (cache.type == NSVG.PaintType.RadialGradient) {
4831     // TODO: spread modes.
4832     // TODO: plenty of opportunities to optimize.
4833     // TODO: focus (fx, fy)
4834     //float fx, fy, dx, gx, gy, gd;
4835     const(float)* t = cache.xform.ptr;
4836     //int i, cr, cg, cb, ca;
4837     //uint c;
4838 
4839     float fx = (x-tx)/scale;
4840     float fy = (y-ty)/scale;
4841     float dx = 1.0f/scale;
4842 
4843     foreach (int i; 0..count) {
4844       //int r, g, b, a, ia;
4845       float gx = fx*t[0]+fy*t[2]+t[4];
4846       float gy = fx*t[1]+fy*t[3]+t[5];
4847       float gd = sqrtf(gx*gx+gy*gy);
4848       uint c = cache.colors[cast(int)nsvg__clampf(gd*255.0f, 0, 255.0f)];
4849       int cr = (c)&0xff;
4850       int cg = (c>>8)&0xff;
4851       int cb = (c>>16)&0xff;
4852       int ca = (c>>24)&0xff;
4853 
4854       int a = nsvg__div255(cast(int)cover[0]*ca);
4855       int ia = 255-a;
4856 
4857       // Premultiply
4858       int r = nsvg__div255(cr*a);
4859       int g = nsvg__div255(cg*a);
4860       int b = nsvg__div255(cb*a);
4861 
4862       // Blend over
4863       r += nsvg__div255(ia*cast(int)dst[0]);
4864       g += nsvg__div255(ia*cast(int)dst[1]);
4865       b += nsvg__div255(ia*cast(int)dst[2]);
4866       a += nsvg__div255(ia*cast(int)dst[3]);
4867 
4868       dst[0] = cast(ubyte)r;
4869       dst[1] = cast(ubyte)g;
4870       dst[2] = cast(ubyte)b;
4871       dst[3] = cast(ubyte)a;
4872 
4873       ++cover;
4874       dst += 4;
4875       fx += dx;
4876     }
4877   }
4878 }
4879 
4880 void nsvg__rasterizeSortedEdges (NSVGrasterizer r, float tx, float ty, float scale, const(NSVGcachedPaint)* cache, char fillRule) {
4881   NSVGactiveEdge* active = null;
4882   int s;
4883   int e = 0;
4884   int maxWeight = (255/NSVG__SUBSAMPLES);  // weight per vertical scanline
4885   int xmin, xmax;
4886 
4887   foreach (int y; 0..r.height) {
4888     import core.stdc.string : memset;
4889     memset(r.scanline, 0, r.width);
4890     xmin = r.width;
4891     xmax = 0;
4892     for (s = 0; s < NSVG__SUBSAMPLES; ++s) {
4893       // find center of pixel for this scanline
4894       float scany = y*NSVG__SUBSAMPLES+s+0.5f;
4895       NSVGactiveEdge** step = &active;
4896 
4897       // update all active edges;
4898       // remove all active edges that terminate before the center of this scanline
4899       while (*step) {
4900         NSVGactiveEdge* z = *step;
4901         if (z.ey <= scany) {
4902           *step = z.next; // delete from list
4903           //NSVG__assert(z.valid);
4904           nsvg__freeActive(r, z);
4905         } else {
4906           z.x += z.dx; // advance to position for current scanline
4907           step = &((*step).next); // advance through list
4908         }
4909       }
4910 
4911       // resort the list if needed
4912       for (;;) {
4913         int changed = 0;
4914         step = &active;
4915         while (*step && (*step).next) {
4916           if ((*step).x > (*step).next.x) {
4917             NSVGactiveEdge* t = *step;
4918             NSVGactiveEdge* q = t.next;
4919             t.next = q.next;
4920             q.next = t;
4921             *step = q;
4922             changed = 1;
4923           }
4924           step = &(*step).next;
4925         }
4926         if (!changed) break;
4927       }
4928 
4929       // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
4930       while (e < r.nedges && r.edges[e].y0 <= scany) {
4931         if (r.edges[e].y1 > scany) {
4932           NSVGactiveEdge* z = nsvg__addActive(r, &r.edges[e], scany);
4933           if (z is null) break;
4934           // find insertion point
4935           if (active is null) {
4936             active = z;
4937           } else if (z.x < active.x) {
4938             // insert at front
4939             z.next = active;
4940             active = z;
4941           } else {
4942             // find thing to insert AFTER
4943             NSVGactiveEdge* p = active;
4944             while (p.next && p.next.x < z.x)
4945               p = p.next;
4946             // at this point, p.next.x is NOT < z.x
4947             z.next = p.next;
4948             p.next = z;
4949           }
4950         }
4951         e++;
4952       }
4953 
4954       // now process all active edges in non-zero fashion
4955       if (active !is null)
4956         nsvg__fillActiveEdges(r.scanline, r.width, active, maxWeight, &xmin, &xmax, fillRule);
4957     }
4958     // Blit
4959     if (xmin < 0) xmin = 0;
4960     if (xmax > r.width-1) xmax = r.width-1;
4961     if (xmin <= xmax) {
4962       nsvg__scanlineSolid(&r.bitmap[y*r.stride]+xmin*4, xmax-xmin+1, &r.scanline[xmin], xmin, y, tx, ty, scale, cache);
4963     }
4964   }
4965 
4966 }
4967 
4968 void nsvg__unpremultiplyAlpha (ubyte* image, int w, int h, int stride) {
4969   // Unpremultiply
4970   foreach (int y; 0..h) {
4971     ubyte *row = &image[y*stride];
4972     foreach (int x; 0..w) {
4973       int r = row[0], g = row[1], b = row[2], a = row[3];
4974       if (a != 0) {
4975         row[0] = cast(ubyte)(r*255/a);
4976         row[1] = cast(ubyte)(g*255/a);
4977         row[2] = cast(ubyte)(b*255/a);
4978       }
4979       row += 4;
4980     }
4981   }
4982 
4983   // Defringe
4984   foreach (int y; 0..h) {
4985     ubyte *row = &image[y*stride];
4986     foreach (int x; 0..w) {
4987       int r = 0, g = 0, b = 0, a = row[3], n = 0;
4988       if (a == 0) {
4989         if (x-1 > 0 && row[-1] != 0) {
4990           r += row[-4];
4991           g += row[-3];
4992           b += row[-2];
4993           n++;
4994         }
4995         if (x+1 < w && row[7] != 0) {
4996           r += row[4];
4997           g += row[5];
4998           b += row[6];
4999           n++;
5000         }
5001         if (y-1 > 0 && row[-stride+3] != 0) {
5002           r += row[-stride];
5003           g += row[-stride+1];
5004           b += row[-stride+2];
5005           n++;
5006         }
5007         if (y+1 < h && row[stride+3] != 0) {
5008           r += row[stride];
5009           g += row[stride+1];
5010           b += row[stride+2];
5011           n++;
5012         }
5013         if (n > 0) {
5014           row[0] = cast(ubyte)(r/n);
5015           row[1] = cast(ubyte)(g/n);
5016           row[2] = cast(ubyte)(b/n);
5017         }
5018       }
5019       row += 4;
5020     }
5021   }
5022 }
5023 
5024 
5025 void nsvg__initPaint (NSVGcachedPaint* cache, const(NSVG.Paint)* paint, float opacity) {
5026   const(NSVG.Gradient)* grad;
5027 
5028   cache.type = paint.type;
5029 
5030   if (paint.type == NSVG.PaintType.Color) {
5031     cache.colors[0] = nsvg__applyOpacity(paint.color, opacity);
5032     return;
5033   }
5034 
5035   grad = paint.gradient;
5036 
5037   cache.spread = grad.spread;
5038   //memcpy(cache.xform.ptr, grad.xform.ptr, float.sizeof*6);
5039   cache.xform[0..6] = grad.xform[0..6];
5040 
5041   if (grad.nstops == 0) {
5042     //for (i = 0; i < 256; i++) cache.colors[i] = 0;
5043     cache.colors[0..256] = 0;
5044   } if (grad.nstops == 1) {
5045     foreach (int i; 0..256) cache.colors[i] = nsvg__applyOpacity(grad.stops.ptr[i].color, opacity);
5046   } else {
5047     uint cb = 0;
5048     //float ua, ub, du, u;
5049     int ia, ib, count;
5050 
5051     uint ca = nsvg__applyOpacity(grad.stops.ptr[0].color, opacity);
5052     float ua = nsvg__clampf(grad.stops.ptr[0].offset, 0, 1);
5053     float ub = nsvg__clampf(grad.stops.ptr[grad.nstops-1].offset, ua, 1);
5054     ia = cast(int)(ua*255.0f);
5055     ib = cast(int)(ub*255.0f);
5056     //for (i = 0; i < ia; i++) cache.colors[i] = ca;
5057     cache.colors[0..ia] = ca;
5058 
5059     foreach (int i; 0..grad.nstops-1) {
5060       ca = nsvg__applyOpacity(grad.stops.ptr[i].color, opacity);
5061       cb = nsvg__applyOpacity(grad.stops.ptr[i+1].color, opacity);
5062       ua = nsvg__clampf(grad.stops.ptr[i].offset, 0, 1);
5063       ub = nsvg__clampf(grad.stops.ptr[i+1].offset, 0, 1);
5064       ia = cast(int)(ua*255.0f);
5065       ib = cast(int)(ub*255.0f);
5066       count = ib-ia;
5067       if (count <= 0) continue;
5068       float u = 0;
5069       immutable float du = 1.0f/cast(float)count;
5070       foreach (int j; 0..count) {
5071         cache.colors[ia+j] = nsvg__lerpRGBA(ca, cb, u);
5072         u += du;
5073       }
5074     }
5075 
5076     //for (i = ib; i < 256; i++) cache.colors[i] = cb;
5077     cache.colors[ib..256] = cb;
5078   }
5079 
5080 }
5081 
5082 extern(C) {
5083   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
5084   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
5085 }
5086 
5087 /**
5088  * Rasterizes SVG image, returns RGBA image (non-premultiplied alpha).
5089  *
5090  * Params:
5091  *   r = pointer to rasterizer context
5092  *   image = pointer to SVG image to rasterize
5093  *   tx, ty = image offset (applied after scaling)
5094  *   scale = image scale
5095  *   dst = pointer to destination image data, 4 bytes per pixel (RGBA)
5096  *   w = width of the image to render
5097  *   h = height of the image to render
5098  *   stride = number of bytes per scaleline in the destination buffer
5099  */
5100 public void rasterize (NSVGrasterizer r, const(NSVG)* image, float tx, float ty, float scale, ubyte* dst, int w, int h, int stride=-1) {
5101   const(NSVG.Shape)* shape = null;
5102   NSVGedge* e = null;
5103   NSVGcachedPaint cache;
5104   int i;
5105 
5106   if (stride <= 0) stride = w*4;
5107   r.bitmap = dst;
5108   r.width = w;
5109   r.height = h;
5110   r.stride = stride;
5111 
5112   if (w > r.cscanline) {
5113     import core.stdc.stdlib : realloc;
5114     r.cscanline = w;
5115     r.scanline = cast(ubyte*)realloc(r.scanline, w+256);
5116     if (r.scanline is null) assert(0, "nanosvg: out of memory");
5117   }
5118 
5119   for (i = 0; i < h; i++) {
5120     import core.stdc.string : memset;
5121     memset(&dst[i*stride], 0, w*4);
5122   }
5123 
5124   for (shape = image.shapes; shape !is null; shape = shape.next) {
5125     if (!(shape.flags&NSVG.Visible)) continue;
5126 
5127     if (shape.fill.type != NSVG.PaintType.None) {
5128       //import core.stdc.stdlib : qsort; // not @nogc
5129 
5130       nsvg__resetPool(r);
5131       r.freelist = null;
5132       r.nedges = 0;
5133 
5134       nsvg__flattenShape(r, shape, scale);
5135 
5136       // Scale and translate edges
5137       for (i = 0; i < r.nedges; i++) {
5138         e = &r.edges[i];
5139         e.x0 = tx+e.x0;
5140         e.y0 = (ty+e.y0)*NSVG__SUBSAMPLES;
5141         e.x1 = tx+e.x1;
5142         e.y1 = (ty+e.y1)*NSVG__SUBSAMPLES;
5143       }
5144 
5145       // Rasterize edges
5146       qsort(r.edges, r.nedges, NSVGedge.sizeof, &nsvg__cmpEdge);
5147 
5148       // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
5149       nsvg__initPaint(&cache, &shape.fill, shape.opacity);
5150 
5151       nsvg__rasterizeSortedEdges(r, tx, ty, scale, &cache, shape.fillRule);
5152     }
5153     if (shape.stroke.type != NSVG.PaintType.None && (shape.strokeWidth*scale) > 0.01f) {
5154       //import core.stdc.stdlib : qsort; // not @nogc
5155 
5156       nsvg__resetPool(r);
5157       r.freelist = null;
5158       r.nedges = 0;
5159 
5160       nsvg__flattenShapeStroke(r, shape, scale);
5161 
5162       //dumpEdges(r, "edge.svg");
5163 
5164       // Scale and translate edges
5165       for (i = 0; i < r.nedges; i++) {
5166         e = &r.edges[i];
5167         e.x0 = tx+e.x0;
5168         e.y0 = (ty+e.y0)*NSVG__SUBSAMPLES;
5169         e.x1 = tx+e.x1;
5170         e.y1 = (ty+e.y1)*NSVG__SUBSAMPLES;
5171       }
5172 
5173       // Rasterize edges
5174       qsort(r.edges, r.nedges, NSVGedge.sizeof, &nsvg__cmpEdge);
5175 
5176       // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
5177       nsvg__initPaint(&cache, &shape.stroke, shape.opacity);
5178 
5179       nsvg__rasterizeSortedEdges(r, tx, ty, scale, &cache, NSVG.FillRule.NonZero);
5180     }
5181   }
5182 
5183   nsvg__unpremultiplyAlpha(dst, w, h, stride);
5184 
5185   r.bitmap = null;
5186   r.width = 0;
5187   r.height = 0;
5188   r.stride = 0;
5189 }
5190 
5191 } // nothrow @trusted @nogc
5192 
5193 
5194 // ////////////////////////////////////////////////////////////////////////// //
5195 ptrdiff_t xindexOf (const(void)[] hay, const(void)[] need, usize stIdx=0) pure @trusted nothrow @nogc {
5196   if (hay.length <= stIdx || need.length == 0 || need.length > hay.length-stIdx) {
5197     return -1;
5198   } else {
5199     //import iv.strex : memmem;
5200     auto res = memmem(hay.ptr+stIdx, hay.length-stIdx, need.ptr, need.length);
5201     return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
5202   }
5203 }
5204 
5205 ptrdiff_t xindexOf (const(void)[] hay, ubyte ch, usize stIdx=0) pure @trusted nothrow @nogc {
5206   return xindexOf(hay, (&ch)[0..1], stIdx);
5207 }
5208 
5209 pure nothrow @trusted @nogc:
5210 version(linux) {
5211   extern(C) inout(void)* memmem (inout(void)* haystack, usize haystacklen, inout(void)* needle, usize needlelen);
5212 } else {
5213   inout(void)* memmem (inout(void)* haystack, usize haystacklen, inout(void)* needle, usize needlelen) {
5214     auto h = cast(const(ubyte)*)haystack;
5215     auto n = cast(const(ubyte)*)needle;
5216     // usize is unsigned
5217     if (needlelen > haystacklen) return null;
5218     foreach (immutable i; 0..haystacklen-needlelen+1) {
5219       import core.stdc.string : memcmp;
5220       if (memcmp(h+i, n, needlelen) == 0) return cast(void*)(h+i);
5221     }
5222     return null;
5223   }
5224 }