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