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>
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.
52 NSVG* image = nsvgParseFromFile("test.svg", "px", 96);
53 printf("size: %f x %f\n", image.width, image.height);
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 {
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;
70 NSVGrasterizer rast = nsvgCreateRasterizer();
71 // Allocate memory for image
72 ubyte* img = malloc(w*h*4);
74 nsvgRasterize(rast, image, 0, 0, 1, img, w, h, w*4);
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;
88 static if (is(typeof((){import iv
.vfs
;}))) {
89 enum NanoSVGHasIVVFS
= true;
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
{
114 // ////////////////////////////////////////////////////////////////////////// //
115 public alias NSVGrasterizer
= NSVGrasterizerS
*; ///
116 public alias NSVGRasterizer
= NSVGrasterizer
; ///
120 @disable this (this);
127 BezierTo
, /// cubic bezier
131 enum PaintType
: ubyte {
139 enum SpreadType
: ubyte {
146 enum LineJoin
: ubyte {
153 enum LineCap
: ubyte {
160 enum FillRule
: ubyte {
165 alias Flags
= ubyte; ///
171 static struct GradientStop
{
177 static struct Gradient
{
179 SpreadType spread
; ///
182 GradientStop
[0] stops
; ///
186 static struct Paint
{
187 pure nothrow @safe @nogc:
188 @disable this (this);
192 Gradient
* gradient
; ///
194 static uint rgb (ubyte r
, ubyte g
, ubyte b
) { pragma(inline
, true); return (r|
(g
<<8)|
(b
<<16)); } ///
196 bool isNone () { pragma(inline
, true); return (type
== PaintType
.None
); } ///
197 bool isColor () { pragma(inline
, true); return (type
== PaintType
.Color
); } ///
199 bool isLinear () { pragma(inline
, true); return (type
== PaintType
.LinearGradient
); } ///
200 bool isRadial () { pragma(inline
, true); return (type
== PaintType
.RadialGradient
); } ///
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; } ///
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];
240 if (dx
!is null) *dx
= 0;
241 if (dy
!is null) *dy
= 0;
247 int countCubics () const nothrow @trusted @nogc {
248 if (nsflts
< 3) return 0;
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
264 int countCommands(bool synthesizeCloseCommand
=true) () const nothrow @trusted @nogc {
265 if (nsflts
< 3) return 0;
267 for (int pidx
= 0; pidx
+3 <= nsflts
; ) {
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
278 static if (synthesizeCloseCommand
) { if (closed
) ++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
;
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;
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
);
320 for (int pidx
= 0; pidx
+3 <= nsflts
; ) {
321 final switch (cast(Command
)stream
[pidx
++]) {
323 static if (withMoveTo
) {
324 static if (HasRes
) { if (dg(stream
[pidx
+0..pidx
+2])) return; } else { dg(stream
[pidx
+0..pidx
+2]); }
330 synthLine(cx
, cy
, stream
[pidx
+0], stream
[pidx
+1]);
334 synthQuad(cx
, cy
, stream
[pidx
+0], stream
[pidx
+1], stream
[pidx
+2], stream
[pidx
+3]);
337 case Command
.BezierTo
:
340 cubic
.ptr
[2..8] = stream
[pidx
..pidx
+6];
346 static if (withMoveTo
) {
347 static if (HasRes
) { if (dg(cubic
[2..8])) return; } else { dg(cubic
[2..8]); }
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
); });
363 for (int pidx
= 0; pidx
+3 <= nsflts
; ) {
364 cmd
= cast(Command
)stream
[pidx
++];
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
]); }
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:
407 /// const(NSVG.Path)*
410 /// delegate can return:
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
); });
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
*;
427 alias TP
= const(NSVG
.Path
)*;
429 for (TP path
= paths
; path
!is null; path
= path
.next
) {
431 static if (WantPtr
) {
432 if (dg(path
)) return;
434 if (dg(*path
)) return;
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:
449 /// const(NSVG.Shape)*
451 /// in ref NSVG.Shape
452 /// delegate can return:
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
); });
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
*;
469 alias TP
= const(NSVG
.Shape
)*;
471 for (TP shape
= shapes
; shape
!is null; shape
= shape
.next
) {
473 static if (WantPtr
) {
474 if (dg(shape
)) return;
476 if (dg(*shape
)) return;
479 static if (WantPtr
) dg(shape
); else dg(*shape
);
486 // ////////////////////////////////////////////////////////////////////////// //
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
) {
494 while (spos
< str.length
&& str.ptr
[spos
] <= ' ') ++spos
;
496 static int hexdigit() (char c
) {
497 pragma(inline
, true);
499 (c
>= '0' && c
<= '9' ? c
-'0' :
500 c
>= 'A' && c
<= 'F' ? c
-'A'+10 :
501 c
>= 'a' && c
<= 'f' ? c
-'a'+10 :
505 bool parseInt(T
: ulong) (ref T res
) {
507 debug(xsscanf_int
) { import std
.stdio
; writeln("parseInt00: str=", str[spos
..$].quote
); }
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
); }
518 bool parseHex(T
: ulong) (ref T res
) {
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
]);
528 debug(xsscanf_int
) { import std
.stdio
; writeln("parseHex10: str=", str[spos
..$].quote
); }
532 bool parseFloat(T
: real) (ref T res
) {
534 debug(xsscanf_float
) { import std
.stdio
; writeln("parseFloat00: str=", str[spos
..$].quote
); }
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;
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';
544 if (spos
< str.length
&& str.ptr
[spos
] == '.') {
545 debug(xsscanf_float
) { import std
.stdio
; writeln("parseFloat02: str=", str[spos
..$].quote
); }
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');
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
); }
562 if (spos
< str.length
&& str.ptr
[spos
] == '+') ++spos
;
563 else if (spos
< str.length
&& str.ptr
[spos
] == '-') { xneg
= true; ++spos
; }
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';
569 while (n
-- > 0) res
/= 10;
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
); }
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
] <= ' ') {
597 if (fmt
.ptr
[fpos
] != '%') {
598 if (spos
>= str.length ||
str.ptr
[spos
] != fmt
.ptr
[spos
]) return false;
603 if (fmt
.length
-fpos
< 2) return false; // stray percent
605 bool skipAss
= false;
606 if (fmt
.ptr
[fpos
-1] == '*') {
608 if (fpos
>= fmt
.length
) return false; // stray star
611 switch (fmt
.ptr
[fpos
-1]) {
613 if (spos
>= str.length ||
str.ptr
[spos
] != '%') return false;
617 static if (is(T
: ulong)) {
620 if (!parseInt
!long(v
)) return false;
622 return parseInt
!T(res
);
625 if (!skipAss
) assert(0, "invalid type");
627 if (!parseInt
!long(v
)) return false;
631 static if (is(T
: ulong)) {
634 if (!parseHex
!long(v
)) return false;
636 return parseHex
!T(res
);
639 if (!skipAss
) assert(0, "invalid type");
641 if (!parseHex
!ulong(v
)) return false;
645 static if (is(T
== float) ||
is(T
== double) ||
is(T
== real)) {
648 if (!parseFloat
!double(v
)) return false;
650 return parseFloat
!T(res
);
653 if (!skipAss
) assert(0, "invalid type");
655 if (!parseFloat
!double(v
)) return false;
659 if (fmt
.length
-fpos
< 1) return false;
661 while (spos
< str.length
) {
663 foreach (immutable cidx
, char c
; fmt
[fpos
..$]) {
665 if (c
== '-') assert(0, "not yet");
669 if (str.ptr
[spos
] <= ' ') { ok
= true; break; }
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
679 while (fpos
< fmt
.length
&& fmt
[fpos
] != ']') ++fpos
;
680 if (fpos
< fmt
.length
) ++fpos
;
681 static if (is(T
== const(char)[])) {
683 res
= str[stp
..spos
];
687 if (!skipAss
) assert(0, "invalid type");
692 while (spos
< str.length
&& str.ptr
[spos
] > ' ') ++spos
;
693 static if (is(T
== const(char)[])) {
695 res
= str[stp
..spos
];
700 if (!skipAss
) assert(0, "invalid type");
703 default: assert(0, "unknown format specifier");
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); }
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;
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
;
733 auto res
= cast(ubyte*)malloc(sz
+256);
734 if (res
is null) assert(0, "NanoVega.SVG: out of memory");
739 void xfree(T
) (ref T
* p
) {
741 import core
.stdc
.stdlib
: free
;
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);
768 (c
>= '0' && c
<= '9' ? c
-'0' :
769 c
>= 'A' && c
<= 'F' ? c
-'A'+10 :
770 c
>= 'a' && c
<= 'f' ? c
-'a'+10 :
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
); }
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
,
796 const(char)[][NSVG_XML_MAX_ATTRIBS
] attr
;
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] == '/') {
814 // Skip comments, data and preprocessor stuff.
815 if (s
.length
== 0 || s
[0] == '?' || s
[0] == '!') return;
818 //{ import std.stdio; writeln("bs=", s.quote); }
821 while (pos
< s
.length
&& !nsvg__isspace(s
[pos
])) ++pos
;
825 //{ import std.stdio; writeln("name=", name.quote); }
826 //{ import std.stdio; writeln("as=", s.quote); }
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
837 while (pos
< s
.length
&& !nsvg__isspace(s
[pos
]) && s
[pos
] != '=') ++pos
;
838 attr
[nattr
++] = s
[0..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
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"); }
858 writeln("===========================");
859 foreach (immutable idx
, const(char)[] v
; attr
[0..nattr
]) writeln(" #", idx
, ": ", v
.quote
);
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
,
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[") {
879 while (cpos
< input
.length
) {
880 if (input
.length
-cpos
> 1 && input
.ptr
[cpos
] == ']' && input
.ptr
[cpos
+1] == ']') {
882 while (cpos
< input
.length
&& input
.ptr
[cpos
] <= ' ') ++cpos
;
883 if (cpos
< input
.length
&& input
.ptr
[cpos
] == '>') { ++cpos
; break; }
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); }
899 while (cpos
< input
.length
) {
900 if (input
.length
-cpos
> 2 && input
.ptr
[cpos
] == '-' && input
.ptr
[cpos
+1] == '-' && input
.ptr
[cpos
+2] == '>') {
906 input
= input
[cpos
..$];
907 //{ import std.stdio; writeln("ctx1: ", input.quote); }
909 state
= NSVG_XML_TAG
;
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..$];
918 state
= NSVG_XML_CONTENT
;
926 /* Simple SVG parser. */
928 enum NSVG_MAX_ATTR
= 128;
930 enum GradientUnits
: ubyte {
935 enum NSVG_MAX_DASHES
= 8;
956 Coordinate x1
, y1
, x2
, y2
;
960 Coordinate cx
, cy
, r
, fx
, fy
;
963 struct GradientData
{
971 NSVG
.SpreadType spread
;
975 NSVG
.GradientStop
* stops
;
987 char[64] fillGradient
= 0;
988 char[64] strokeGradient
= 0;
990 float strokeDashOffset
;
991 float[NSVG_MAX_DASHES
] strokeDashArray
;
993 NSVG
.LineJoin strokeLineJoin
;
994 NSVG
.LineCap strokeLineCap
;
996 NSVG
.FillRule fillRule
;
1006 version(nanosvg_crappy_stylesheet_parser
) {
1009 const(char)[] value
;
1014 Attrib
[NSVG_MAX_ATTR
] attr
;
1021 GradientData
* gradients
;
1022 NSVG
.Shape
* shapesTail
;
1023 float viewMinx
, viewMiny
, viewWidth
, viewHeight
;
1024 int alignX
, alignY
, alignType
;
1030 version(nanosvg_crappy_stylesheet_parser
) {
1037 const(char)[] fromAsciiz (const(char)[] s
) {
1038 //foreach (immutable idx, char ch; s) if (!ch) return s[0..idx];
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
)];
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];
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
);
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);
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);
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
];
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
;
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
;
1215 nsvg__xformIdentity(p
.attr
[0].xform
.ptr
);
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;
1241 void nsvg__deletePaths (NSVG
.Path
* path
) {
1242 while (path
!is null) {
1243 NSVG
.Path
* next
= 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
) {
1256 while (grad
!is null) {
1264 void nsvg__deleteParser (Parser
* p
) {
1266 nsvg__deletePaths(p
.plist
);
1267 nsvg__deleteGradientData(p
.gradients
);
1270 version(nanosvg_crappy_stylesheet_parser
) xfree(p
.styles
);
1275 void nsvg__resetPath (Parser
* p
) {
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
);
1305 p.pts[(p.npts-1)*2+0] = x;
1306 p.pts[(p.npts-1)*2+1] = y;
1308 nsvg__addPoint(p, x, y);
1313 void nsvg__lineTo (Parser
* p
, in float x
, in float y
) {
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
);
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
;
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
);
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
;
1390 GradientData
* nsvg__findGradientData (Parser
* p
, const(char)[] id
) {
1391 GradientData
* grad
= p
.gradients
;
1393 while (grad
!is null) {
1394 if (grad
.id
.fromAsciiz
== id
) return grad
;
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;
1410 data
= nsvg__findGradientData(p
, id
);
1411 if (data
is null) return null;
1413 // TODO: use ref_ to fill in all unset values too.
1415 while (ref_
!is null) {
1416 if (stops
is null && ref_
.stops
!is null) {
1418 nstops
= ref_
.nstops
;
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];
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
;
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
;
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;
1508 nsvg__curveBounds(bounds
, cubic
.ptr
);
1511 void nsvg__getLocalBounds (float* bounds
, NSVG
.Shape
* shape
, const(float)* xform
) {
1514 void addPoint (in float x
, in float y
) nothrow @trusted @nogc {
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
);
1521 bounds
[0] = bounds
[2] = x
;
1522 bounds
[1] = bounds
[3] = y
;
1527 void addRect (in float x0
, in float y0
, in float x1
, in float y1
) nothrow @trusted @nogc {
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;
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
);
1545 final switch (cmd
) {
1546 case NSVG
.Command
.MoveTo
:
1550 case NSVG
.Command
.LineTo
:
1552 addPoint(xpt
.ptr
[0], xpt
.ptr
[1]);
1556 case NSVG
.Command
.QuadTo
:
1557 memmove(xpt
.ptr
+2, xpt
.ptr
, 4); // make room for starting point
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]);
1566 case NSVG
.Command
.BezierTo
:
1567 memmove(xpt
.ptr
+2, xpt
.ptr
, 6); // make room for starting point
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]);
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);
1586 bounds[0] = curveBounds.ptr[0];
1587 bounds[1] = curveBounds.ptr[1];
1588 bounds[2] = curveBounds.ptr[2];
1589 bounds[3] = curveBounds.ptr[3];
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
);
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
;
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]);
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) {
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
;
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) {
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
;
1677 shape
.flags
= (attr
.visible ? NSVG
.Visible
: 0x00);
1680 if (p
.image
.shapes
is null)
1681 p
.image
.shapes
= shape
;
1683 p
.shapesTail
.next
= shape
;
1685 p
.shapesTail
= shape
;
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;
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;
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 {
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
);
1725 bounds
[0] = bounds
[2] = x
;
1726 bounds
[1] = bounds
[3] = y
;
1731 void addRect (in float x0
, in float y0
, in float x1
, in float y1
) nothrow @trusted @nogc {
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;
1755 path
.stream
[i
] = p
.stream
[i
];
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
);
1764 final switch (cmd
) {
1765 case NSVG
.Command
.MoveTo
:
1766 cx
= path
.stream
[starti
+0];
1767 cy
= path
.stream
[starti
+1];
1769 case NSVG
.Command
.LineTo
:
1771 cx
= path
.stream
[starti
+0];
1772 cy
= path
.stream
[starti
+1];
1775 case NSVG
.Command
.QuadTo
:
1776 float[6] curve
= void;
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]);
1786 case NSVG
.Command
.BezierTo
:
1787 float[8] curve
= void;
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]);
1799 path
.bounds
[0..4] = bounds
[0..4];
1801 path
.next
= p
.plist
;
1807 if (path
!is null) {
1808 if (path
.stream
!is null) xfree(path
.stream
);
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
1828 case '-': sign
= -1; goto case;
1829 case '+': getChar(); break;
1833 // Parse integer part
1834 if (nsvg__isdigit(peekChar
)) {
1835 // Parse digit sequence
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
1848 while (nsvg__isdigit(peekChar
)) {
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;
1865 case '-': epositive
= false; goto case;
1866 case '+': getChar(); break;
1870 while (nsvg__isdigit(peekChar
)) expPart
= expPart
*10+(getChar()-'0');
1872 foreach (immutable _
; 0..expPart
) res
*= 10.0;
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
) {
1887 const(char)[] os
= s
;
1890 if (s
.length
&& (s
[0] == '-' || s
[0] == '+')) {
1891 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1895 while (s
.length
&& nsvg__isdigit(s
[0])) {
1896 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1899 if (s
.length
&& s
[0] == '.') {
1901 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1904 while (s
.length
&& nsvg__isdigit(s
[0])) {
1905 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1910 if (s
.length
&& (s
[0] == 'e' || s
[0] == 'E')) {
1911 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1913 if (s
.length
&& (s
[0] == '-' || s
[0] == '+')) {
1914 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1917 while (s
.length
&& nsvg__isdigit(s
[0])) {
1918 if (it
.length
-i
> 1) it
[i
++] = s
[0];
1923 return cast(int)(s
.ptr
-os
.ptr
);
1926 // `it` should be big enough
1927 int nsvg__getNextPathItem (const(char)[] s
, char[] it
) {
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
) {
1942 uint nsvg__parseColorHex (const(char)[] str) {
1945 ubyte r
= 0, g
= 0, b
= 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
);
1957 c
= (c
&0xf)|
((c
&0xf0)<<4)|
((c
&0xf00)<<8);
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));
1975 return NSVG
.Paint
.rgb(cast(ubyte)r
, cast(ubyte)g
, cast(ubyte)b
);
1979 struct NSVGNamedColor
{
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
= () {
2136 foreach (const ref known
; nsvg__colors
) if (res
< known
.name
.length
) res
= cast(int)known
.name
.length
;
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;
2153 if (s0.length < s1.length) return -1;
2154 if (s0.length > s1.length) return 1;
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
;
2160 if (s0
.length
< s1
.length
) return -1;
2161 if (s0
.length
> s1
.length
) return 1;
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
;
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) {
2197 xsscanf(str, "%f", val
);
2198 if (val
< 0.0f) val
= 0.0f;
2199 if (val
> 1.0f) val
= 1.0f;
2203 float nsvg__parseMiterLimit (const(char)[] str) {
2205 xsscanf(str, "%f", val
);
2206 if (val
< 0.0f) val
= 0.0f;
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
;
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
);
2233 Coordinate
nsvg__coord (float v
, Units units
) {
2234 Coordinate coord
= Coordinate(v
, units
);
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
) {
2251 while (ptr
< str.length
&& str[ptr
] != '(') ++ptr
;
2252 if (ptr
>= str.length
) return 1;
2255 while (end
< str.length
&& str[end
] != ')') ++end
;
2256 if (end
>= str.length
) return 1;
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
2267 return cast(int)end
; // fuck off, 64bit
2271 int nsvg__parseMatrix (float* xform
, const(char)[] str) {
2274 int len
= nsvg__parseTransformArgs(str, t
.ptr
, 6, &na
);
2275 if (na
!= 6) return len
;
2280 int nsvg__parseTranslate (float* xform
, const(char)[] str) {
2281 float[2] args
= void;
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]);
2291 int nsvg__parseScale (float* xform
, const(char)[] str) {
2292 float[2] args
= 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]);
2302 int nsvg__parseSkewX (float* xform
, const(char)[] str) {
2303 float[1] args
= void;
2306 int len
= nsvg__parseTransformArgs(str, args
.ptr
, 1, &na
);
2307 nsvg__xformSetSkewX(t
.ptr
, args
.ptr
[0]/180.0f*NSVG_PI
);
2312 int nsvg__parseSkewY (float* xform
, const(char)[] str) {
2313 float[1] args
= void;
2316 int len
= nsvg__parseTransformArgs(str, args
.ptr
, 1, &na
);
2317 nsvg__xformSetSkewY(t
.ptr
, args
.ptr
[0]/180.0f*NSVG_PI
);
2322 int nsvg__parseRotate (float* xform
, const(char)[] str) {
2323 float[3] args
= 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
);
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
);
2340 nsvg__xformSetTranslation(t
.ptr
, args
.ptr
[1], args
.ptr
[2]);
2341 nsvg__xformMultiply(m
.ptr
, t
.ptr
);
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) {
2356 nsvg__xformIdentity(xform
);
2357 while (str.length
) {
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; }
2367 nsvg__xformPremultiply(xform
, t
.ptr
);
2371 // `id` should be prealloced
2372 void nsvg__parseUrl (char[] id
, const(char)[] str) {
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];
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
) {
2414 int nsvg__getNextDashItem () {
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];
2428 if (!str.length ||
str[0] == 'n') return 0;
2431 while (str.length
) {
2432 auto len
= nsvg__getNextDashItem();
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;
2443 const(char)[] trimLeft (const(char)[] s
, char ech
=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] == '*') {
2450 while (s
.length
-pos
> 1 && !(s
.ptr
[pos
] == '*' && s
.ptr
[pos
+1] == '/')) ++pos
;
2451 if ((pos
+= 2) > s
.length
) pos
= s
.length
;
2459 static const(char)[] trimRight (const(char)[] s
, char ech
=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
];
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
;
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;
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
);
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") {
2511 } else if (startsWith(value
, "url(")) {
2513 nsvg__parseUrl(attr
.fillGradient
[], value
);
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") {
2525 } else if (startsWith(value
, "url(")) {
2527 nsvg__parseUrl(attr
.strokeGradient
[], value
);
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
);
2565 if (value
.length
> attr
.id
.length
-1) value
= value
[0..attr
.id
.length
-1];
2566 attr
.id
[0..value
.length
] = value
[];
2573 bool nsvg__parseNameValue (Parser
* p
, const(char)[] str) {
2578 while (pos
< str.length
&& str.ptr
[pos
] != ':') {
2579 if (str.length
-pos
> 1 && str.ptr
[pos
] == '/' && str.ptr
[pos
+1] == '*') {
2581 while (str.length
-pos
> 1 && !(str.ptr
[pos
] == '*' && str.ptr
[pos
+1] == '/')) ++pos
;
2582 if ((pos
+= 2) > str.length
) pos
= str.length
;
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
) {
2602 while (pos
< str.length
&& str[pos
] != ';') {
2603 if (str.length
-pos
> 1 && str.ptr
[pos
] == '/' && str.ptr
[pos
+1] == '*') {
2605 while (str.length
-pos
> 1 && !(str.ptr
[pos
] == '*' && str.ptr
[pos
+1] == '/')) ++pos
;
2606 if ((pos
+= 2) > str.length
) pos
= str.length
;
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
) {
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];
2689 nsvg__cubicBezTo(p
, cx1
, cy1
, cx2
, cy2
, x2
, 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];
2704 immutable float x1
= *cpx
;
2705 immutable float y1
= *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
);
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]); }
2732 immutable float x1
= *cpx
;
2733 immutable float y1
= *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
);
2750 nsvg__quadBezTo(p
, cx
, cy
, x2
, 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]); }
2764 immutable float x1
= *cpx
;
2765 immutable float y1
= *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
);
2783 nsvg__quadBezTo(p
, cx
, cy
, x2
, 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
;
2821 if (rel
) { x2
+= *cpx
; y2
+= *cpy
; }
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
);
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
);
2844 immutable float d2
= sqrtf(d1
);
2848 // 2) Compute cx', cy'
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
;
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
);
2906 void nsvg__parsePath (Parser
* p
, AttrList attr
) {
2907 const(char)[] s
= null;
2909 float[10] args
= void;
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") {
2920 const(char)[][2] tmp
;
2923 nsvg__parseAttribs(p
, tmp
[]);
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])) {
2943 args
[nargs
++] = nsvg__atof(item
[]);
2945 if (nargs
>= rargs
) {
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
;
2955 case 'l': case 'L': // line to
2956 nsvg__pathLineTo(p
, &cpx
, &cpy
, args
.ptr
, (cmd
== 'l' ?
1 : 0));
2957 cpx2
= cpx
; cpy2
= cpy
;
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
;
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
;
2967 case 'C': case 'c': // cubic bezier
2968 nsvg__pathCubicBezTo(p
, &cpx
, &cpy
, &cpx2
, &cpy2
, args
.ptr
, (cmd
== 'c' ?
1 : 0));
2970 case 'S': case 's': // "short" cubic bezier
2971 nsvg__pathCubicBezShortTo(p
, &cpx
, &cpy
, &cpx2
, &cpy2
, args
.ptr
, (cmd
== 's' ?
1 : 0));
2973 case 'Q': case 'q': // quadratic bezier
2974 nsvg__pathQuadBezTo(p
, &cpx
, &cpy
, &cpx2
, &cpy2
, args
.ptr
, (cmd
== 'q' ?
1 : 0));
2976 case 'T': case 't': // "short" quadratic bezier
2977 nsvg__pathQuadBezShortTo(p
, &cpx
, &cpy
, &cpx2
, &cpy2
, args
.ptr
, cmd
== 't' ?
1 : 0);
2979 case 'A': case 'a': // arc
2980 nsvg__pathArcTo(p
, &cpx
, &cpy
, args
.ptr
, cmd
== 'a' ?
1 : 0);
2981 cpx2
= cpx
; cpy2
= cpy
;
2985 cpx
= args
[nargs
-2];
2986 cpy
= args
[nargs
-1];
2996 rargs
= nsvg__getArgsPerElement(cmd
);
2997 if (cmd
== 'M' || cmd
== 'm') {
2999 if (p
.nsflts
> 0) nsvg__addPath(p
, closedFlag
);
3000 // start new subpath
3004 } else if (cmd
== 'Z' || cmd
== 'z') {
3008 // move current point to first point
3009 if ((cast(NSVG
.Command
)p
.stream
[0]) != NSVG
.Command
.MoveTo
) assert(0, "NanoVega.SVG: invalid path");
3014 nsvg__addPath(p
, closedFlag
);
3016 // start new subpath
3018 nsvg__moveTo(p
, cpx
, cpy
);
3025 if (p
.nsflts
) nsvg__addPath(p
, closedFlag
);
3031 void nsvg__parseRect (Parser
* p
, AttrList attr
) {
3036 float rx
= -1.0f; // marks not set
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) {
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
);
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);
3084 void nsvg__parseCircle (Parser
* p
, AttrList attr
) {
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
)));
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);
3112 void nsvg__parseEllipse (Parser
* p
, AttrList attr
) {
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) {
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);
3142 void nsvg__parseLine (Parser
* p
, AttrList attr
) {
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
));
3159 nsvg__moveTo(p
, x1
, y1
);
3160 nsvg__lineTo(p
, x2
, y2
);
3162 nsvg__addPath(p
, 0);
3167 void nsvg__parsePoly (Parser
* p
, AttrList attr
, bool closeFlag
) {
3168 float[2] args
= void;
3169 int nargs
, npts
= 0;
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];
3180 auto skl
= nsvg__getNextPathItem(s
, item
[]);
3181 if (skl
< s
.length
) s
= s
[skl
..$]; else s
= s
[$..$];
3182 args
[nargs
++] = nsvg__atof(item
[]);
3184 if (npts
== 0) nsvg__moveTo(p
, args
[0], args
[1]); else nsvg__lineTo(p
, args
[0], args
[1]);
3193 nsvg__addPath(p
, closeFlag
);
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
;
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
;
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
;
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
;
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") {
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") {
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
;
3285 void nsvg__parseGradientStop (Parser
* p
, AttrList attr
) {
3286 import core
.stdc
.stdlib
: realloc
;
3288 Attrib
* curAttr
= nsvg__getAttr(p
);
3290 NSVG
.GradientStop
* stop
;
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.
3301 if (grad
is null) return;
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");
3308 idx
= grad
.nstops
-1;
3309 foreach (int i
; 0..grad
.nstops
-1) {
3310 if (curAttr
.stopOffset
< grad
.stops
[i
].offset
) {
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"); }
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
);
3345 nsvg__parseAttribs(p
, attr
);
3346 } else if (el
== "path") {
3347 if (p
.pathFlag
) return; // do not allow nested paths
3350 nsvg__parsePath(p
, attr
);
3352 } else if (el
== "rect") {
3354 nsvg__parseRect(p
, attr
);
3356 } else if (el
== "circle") {
3358 nsvg__parseCircle(p
, attr
);
3360 } else if (el
== "ellipse") {
3362 nsvg__parseEllipse(p
, attr
);
3364 } else if (el
== "line") {
3366 nsvg__parseLine(p
, attr
);
3368 } else if (el
== "polyline") {
3370 nsvg__parsePoly(p
, attr
, 0);
3372 } else if (el
== "polygon") {
3374 nsvg__parsePoly(p
, attr
, 1);
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") {
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
;
3406 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
3407 if (!s
.startsWith("<![CDATA[")) break;
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;
3417 if (s
.length
> 1 && s
.ptr
[0] == '/' && s
.ptr
[1] == '*') {
3420 while (s
.length
> 1 && !(s
.ptr
[0] == '*' && s
.ptr
[1] == '/')) s
= s
[1..$];
3421 if (s
.length
<= 2) break;
3424 } else if (s
.ptr
[0] <= ' ') {
3425 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
3428 //version(nanosvg_debug_styles) { import std.stdio; writeln("::: ", s.quote); }
3429 if (s
.ptr
[0] == '{') {
3431 while (pos
< s
.length
&& s
.ptr
[pos
] != '}') {
3432 if (s
.length
-pos
> 1 && s
.ptr
[pos
] == '/' && s
.ptr
[pos
+1] == '*') {
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; }
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
];
3447 if (s
.length
-pos
< 1) break;
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
); }
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
);
3462 p
.styles
[p
.styleCount
-1].name
= tk
;
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
) {
3472 shape
= p
.image
.shapes
;
3473 if (shape
is null) {
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
;
3493 return (container
-content
)*0.5f;
3496 void nsvg__scaleGradient (NSVG
.Gradient
* grad
, float tx
, float ty
, float sx
, float sy
) {
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
) {
3508 float tx
= void, ty
= void, sx
= void, sy
= void, us
= void, avgs
= void;
3509 float[4] bounds
= void;
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
;
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
;
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
;
3539 sx
= p
.viewWidth
> 0 ? p
.image
.width
/p
.viewWidth
: 0;
3540 sy
= p
.viewHeight
> 0 ? p
.image
.height
/p
.viewHeight
: 0;
3542 us
= 1.0f/nsvg__convertToPixels(p
, nsvg__coord(1.0f, nsvg__parseUnits(units
)), 0.0f, 1.0f);
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
;
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;
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
;
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) {
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;
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
);
3630 nsvg__scaleToViewbox(p
, units
);
3635 nsvg__deleteParser(p
);
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) {
3648 nsvg__deletePaths(shape
.paths
);
3649 nsvg__deletePaint(&shape
.fill
);
3650 nsvg__deletePaint(&shape
.stroke
);
3654 memset(image
, 0, (*image
).sizeof
);
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;
3667 scope(exit
) if (data
!is null) free(data
);
3669 if (filename
.length
== 0) return null;
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
]);
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;
3695 auto left
= cast(uint)size
;
3697 auto rd
= fread(dptr
, 1, left
, fl
);
3698 if (rd
== 0) return null; // unexpected EOF or reading error, it doesn't matter
3703 return nsvgParse(data
[0..cast(uint)size
], units
, dpi
, canvaswdt
, canvashgt
);
3704 } catch (Exception e
) {
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;
3720 scope(exit
) if (data
is null) free(data
);
3725 if (pp
>= sz
) return null;
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
) {
3742 // ////////////////////////////////////////////////////////////////////////// //
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;
3754 float x0
= 0, y0
= 0, x1
= 0, y1
= 0;
3761 float dx
= 0, dy
= 0;
3763 float dmx
= 0, dmy
= 0;
3767 struct NSVGactiveEdge
{
3771 NSVGactiveEdge
*next
;
3774 struct NSVGmemPage
{
3775 ubyte[NSVG__MEMPAGE_SIZE
] mem
;
3780 struct NSVGcachedPaint
{
3787 struct NSVGrasterizerS
{
3788 float px
= 0, py
= 0;
3805 NSVGactiveEdge
* freelist
;
3807 NSVGmemPage
* curpage
;
3813 int width
, height
, stride
;
3818 public NSVGrasterizer
nsvgCreateRasterizer () {
3819 NSVGrasterizer r
= xalloc
!NSVGrasterizerS
;
3820 if (r
is null) goto error
;
3833 public void kill (NSVGrasterizer r
) {
3836 if (r
is null) return;
3839 while (p
!is null) {
3840 NSVGmemPage
* next
= 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
);
3853 NSVGmemPage
* nsvg__nextPage (NSVGrasterizer r
, NSVGmemPage
* cur
) {
3856 // If using existing chain, return the next page in chain
3857 if (cur
!is null && cur
.next
!is null) return cur
.next
;
3860 newp
= xalloc
!NSVGmemPage
;
3861 if (newp
is null) return null;
3863 // Add to linked list
3872 void nsvg__resetPool (NSVGrasterizer r
) {
3873 NSVGmemPage
* p
= r
.pages
;
3874 while (p
!is null) {
3878 r
.curpage
= r
.pages
;
3881 ubyte* nsvg__alloc (NSVGrasterizer r
, int size
) {
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
;
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
;
3903 if (r
.npoints
> 0) {
3904 pt
= r
.points
+(r
.npoints
-1);
3905 if (nsvg__ptEquals(pt
.x
, pt
.y
, x
, y
, r
.distTol
)) {
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
;
3920 pt
.flags
= cast(ubyte)flags
;
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
;
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
) {
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
];
3978 float nsvg__normalize (float *x
, float* y
) {
3979 immutable float d
= sqrtf((*x
)*(*x
)+(*y
)*(*y
));
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)
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
);
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
);
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
);
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);
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)
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
;
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);
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
)) {
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.
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.
4114 // Double the stepsize.
4118 d
= ddx
*ddx
+ddy
*ddy
+dddx
*dddx
+dddy
*dddy
;
4121 // Forward differencing.
4130 nsvg__addPathPoint(r
, px
, py
, (t
> 0 ? type
: 0));
4132 // Advance along the curve.
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
) {
4143 if (path
.empty
) continue;
4146 path
.startPoint(&x0
, &y0
);
4147 nsvg__addPathPoint(r
, x0
*scale
, y0
*scale
, 0);
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
,
4159 nsvg__addPathPoint(r
, x0
*scale
, y0
*scale
, 0);
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);
4168 nsvg__addPathPoint(r, path.pts[0]*scale, path.pts[1]*scale, 0);
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;
4179 PtFlagsCorner
= 0x01,
4180 PtFlagsBevel
= 0x02,
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
);
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
);
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
);
4251 } else if (i
== ncap
-1) {
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
);
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
) {
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
);
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
);
4335 if (n
> ncap
) n
= ncap
;
4342 for (i
= 0; i
< n
; i
++) {
4343 float u
= i
/cast(float)(n
-1);
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
);
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;
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
;
4386 // Build stroke edges
4389 p0
= &points
[npoints
-1];
4402 nsvg__initClosed(&left
, &right
, p0
, p1
, lineWidth
);
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
);
4425 nsvg__miterJoin(r
, &left
, &right
, p0
, p1
, lineWidth
);
4427 nsvg__straightJoin(r
, &left
, &right
, p1
, lineWidth
);
4434 nsvg__addEdge(r
, firstLeft
.x
, firstLeft
.y
, left
.x
, left
.y
);
4435 nsvg__addEdge(r
, right
.x
, right
.y
, firstRight
.x
, firstRight
.y
);
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
) {
4454 p0
= r
.points
+(r
.npoints
-1);
4456 for (i
= 0; i
< r
.npoints
; i
++) {
4457 // Calculate segment direction and length
4460 p0
.len
= nsvg__normalize(&p0
.dx
, &p0
.dy
);
4466 p0
= r
.points
+(r
.npoints
-1);
4468 for (j
= 0; j
< r
.npoints
; j
++) {
4469 float dlx0
, dly0
, dlx1
, dly1
, dmr2
, cross
;
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
;
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
;
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
;
4506 void nsvg__flattenShapeStroke (NSVGrasterizer r
, const(NSVG
.Shape
)* shape
, float scale
) {
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
) {
4522 path
.startPoint(&x0
, &y0
);
4523 nsvg__addPathPoint(r
, x0
*scale
, y0
*scale
, PtFlagsCorner
);
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
,
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];
4550 if (nsvg__ptEquals(p0
.x
, p0
.y
, p1
.x
, p1
.y
, r
.distTol
)) {
4552 p0
= &r
.points
[r
.npoints
-1];
4556 if (shape
.strokeDashCount
> 0) {
4557 int idash
= 0, dashState
= 1;
4558 float totalDist
= 0, dashLen
, allDashLen
, dashOffset
;
4561 if (closed
) nsvg__appendPathPoint(r
, r
.points
[0]);
4563 // Duplicate points . points2.
4564 nsvg__duplicatePoints(r
);
4568 nsvg__appendPathPoint(r
, cur
);
4570 // Figure out dash offset.
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
);
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
;
4608 cur
.flags
= PtFlagsCorner
;
4611 nsvg__appendPathPoint(r
, cur
);
4615 nsvg__appendPathPoint(r
, cur
);
4619 // Stroke any leftover path
4620 if (r
.npoints
> 1 && dashState
) nsvg__expandStroke(r
, r
.points
, r
.npoints
, 0, lineJoin
, lineCap
, lineWidth
);
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;
4637 static NSVGactiveEdge
* nsvg__addActive (NSVGrasterizer r
, const(NSVGedge
)* e
, float startPoint
) {
4640 if (r
.freelist
!is null) {
4641 // Restore from freelist.
4643 r
.freelist
= z
.next
;
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
4654 z
.dx
= cast(int)(-floorf(NSVG__FIX
*-dxdy
));
4656 z
.dx
= cast(int)floorf(NSVG__FIX
*dxdy
);
4657 z
.x
= cast(int)floorf(NSVG__FIX
*(e
.x0
+dxdy
*(startPoint
-e
.y0
)));
4666 void nsvg__freeActive (NSVGrasterizer r
, NSVGactiveEdge
* z
) {
4667 z
.next
= r
.freelist
;
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) {
4678 // x0, x1 are the same pixel, so compute combined coverage
4679 scanline
[i
] += cast(ubyte)((x1
-x0
)*maxWeight
>>NSVG__FIXSHIFT
);
4681 if (i
>= 0) // add antialiasing for x0
4682 scanline
[i
] += cast(ubyte)(((NSVG__FIX
-(x0
&NSVG__FIXMASK
))*maxWeight
)>>NSVG__FIXSHIFT
);
4686 if (j
< len
) // add antialiasing for x1
4687 scanline
[j
] += cast(ubyte)(((x1
&NSVG__FIXMASK
)*maxWeight
)>>NSVG__FIXSHIFT
);
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
4703 if (fillRule
== NSVG
.FillRule
.NonZero
) {
4705 while (e
!is null) {
4707 // if we're currently at zero, we need to record the edge start point
4708 x0
= e
.x
; w
+= e
.dir
;
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
);
4716 } else if (fillRule
== NSVG
.FillRule
.EvenOdd
) {
4718 while (e
!is null) {
4720 // if we're currently at zero, we need to record the edge start point
4723 int x1
= e
.x
; w
= 0;
4724 nsvg__fillScanline(scanline
, len
, x0
, x1
, maxWeight
, xmin
, xmax
);
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);
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
) {
4764 int a
= nsvg__div255(cast(int)cover
[0]*ca
);
4767 r
= nsvg__div255(cr
*a
);
4768 g
= nsvg__div255(cg
*a
);
4769 b
= nsvg__div255(cb
*a
);
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
;
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;
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)];
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
);
4809 int r
= nsvg__div255(cr
*a
);
4810 int g
= nsvg__div255(cg
*a
);
4811 int b
= nsvg__div255(cb
*a
);
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
;
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;
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)];
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
);
4856 int r
= nsvg__div255(cr
*a
);
4857 int g
= nsvg__div255(cg
*a
);
4858 int b
= nsvg__div255(cb
*a
);
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
;
4878 void nsvg__rasterizeSortedEdges (NSVGrasterizer r
, float tx
, float ty
, float scale
, const(NSVGcachedPaint
)* cache
, char fillRule
) {
4879 NSVGactiveEdge
* active
= null;
4882 int maxWeight
= (255/NSVG__SUBSAMPLES
); // weight per vertical scanline
4885 foreach (int y
; 0..r
.height
) {
4886 import core
.stdc
.string
: memset
;
4887 memset(r
.scanline
, 0, r
.width
);
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
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
);
4904 z
.x
+= z
.dx
; // advance to position for current scanline
4905 step
= &((*step
).next
); // advance through list
4909 // resort the list if needed
4913 while (*step
&& (*step
).next
) {
4914 if ((*step
).x
> (*step
).next
.x
) {
4915 NSVGactiveEdge
* t
= *step
;
4916 NSVGactiveEdge
* q
= t
.next
;
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) {
4935 } else if (z
.x
< active
.x
) {
4940 // find thing to insert AFTER
4941 NSVGactiveEdge
* p
= active
;
4942 while (p
.next
&& p
.next
.x
< z
.x
)
4944 // at this point, p.next.x is NOT < z.x
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
);
4957 if (xmin
< 0) xmin
= 0;
4958 if (xmax
> r
.width
-1) xmax
= r
.width
-1;
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
) {
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];
4973 row
[0] = cast(ubyte)(r
*255/a
);
4974 row
[1] = cast(ubyte)(g
*255/a
);
4975 row
[2] = cast(ubyte)(b
*255/a
);
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;
4987 if (x
-1 > 0 && row
[-1] != 0) {
4993 if (x
+1 < w
&& row
[7] != 0) {
4999 if (y
-1 > 0 && row
[-stride
+3] != 0) {
5001 g
+= row
[-stride
+1];
5002 b
+= row
[-stride
+2];
5005 if (y
+1 < h
&& row
[stride
+3] != 0) {
5012 row
[0] = cast(ubyte)(r
/n
);
5013 row
[1] = cast(ubyte)(g
/n
);
5014 row
[2] = cast(ubyte)(b
/n
);
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
);
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
);
5046 //float ua, ub, du, u;
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);
5065 if (count
<= 0) continue;
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
);
5074 //for (i = ib; i < 256; i++) cache.colors[i] = cb;
5075 cache
.colors
[ib
..256] = cb
;
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).
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;
5101 NSVGcachedPaint cache
;
5104 if (stride
<= 0) stride
= w
*4;
5110 if (w
> r
.cscanline
) {
5111 import core
.stdc
.stdlib
: realloc
;
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
5132 nsvg__flattenShape(r
, shape
, scale
);
5134 // Scale and translate edges
5135 for (i
= 0; i
< r
.nedges
; i
++) {
5138 e
.y0
= (ty
+e
.y0
)*NSVG__SUBSAMPLES
;
5140 e
.y1
= (ty
+e
.y1
)*NSVG__SUBSAMPLES
;
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
5158 nsvg__flattenShapeStroke(r
, shape
, scale
);
5160 //dumpEdges(r, "edge.svg");
5162 // Scale and translate edges
5163 for (i
= 0; i
< r
.nedges
; i
++) {
5166 e
.y0
= (ty
+e
.y0
)*NSVG__SUBSAMPLES
;
5168 e
.y1
= (ty
+e
.y1
)*NSVG__SUBSAMPLES
;
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
);
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
) {
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:
5209 extern(C
) inout(void)* memmem (inout(void)* haystack
, usize haystacklen
, inout(void)* needle
, usize needlelen
);
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
);