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