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