1 //
2 // Would be nice: way to take output of the canvas to an image file (raster and/or svg)
3 //
4 //
5 // Copyright (c) 2013 Mikko Mononen memon@inside.org
6 //
7 // This software is provided 'as-is', without any express or implied
8 // warranty.  In no event will the authors be held liable for any damages
9 // arising from the use of this software.
10 // Permission is granted to anyone to use this software for any purpose,
11 // including commercial applications, and to alter it and redistribute it
12 // freely, subject to the following restrictions:
13 // 1. The origin of this software must not be misrepresented; you must not
14 //    claim that you wrote the original software. If you use this software
15 //    in a product, an acknowledgment in the product documentation would be
16 //    appreciated but is not required.
17 // 2. Altered source versions must be plainly marked as such, and must not be
18 //    misrepresented as being the original software.
19 // 3. This notice may not be removed or altered from any source distribution.
20 //
21 // Fork developement, feature integration and new bugs:
22 // Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
23 // Contains code from various contributors.
24 /**
25 The NanoVega API is modeled loosely on HTML5 canvas API.
26 If you know canvas, you're up to speed with NanoVega in no time.
27 
28 $(SIDE_BY_SIDE
29 
30 	$(COLUMN
31 
32 		D code with nanovega:
33 
34 		---
35 		import arsd.simpledisplay;
36 
37 		import arsd.nanovega;
38 
39 		void main () {
40 		  NVGContext nvg; // our NanoVega context
41 
42 		  // we need at least OpenGL3 with GLSL to use NanoVega,
43 		  // so let's tell simpledisplay about that
44 		  setOpenGLContextVersion(3, 0);
45 
46 		  // now create OpenGL window
47 		  auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing);
48 
49 		  // we need to destroy NanoVega context on window close
50 		  // stricly speaking, it is not necessary, as nothing fatal
51 		  // will happen if you'll forget it, but let's be polite.
52 		  // note that we cannot do that *after* our window was closed,
53 		  // as we need alive OpenGL context to do proper cleanup.
54 		  sdmain.onClosing = delegate () {
55 		    nvg.kill();
56 		  };
57 
58 		  // this is called just before our window will be shown for the first time.
59 		  // we must create NanoVega context here, as it needs to initialize
60 		  // internal OpenGL subsystem with valid OpenGL context.
61 		  sdmain.visibleForTheFirstTime = delegate () {
62 		    // yes, that's all
63 		    nvg = nvgCreateContext();
64 		    if (nvg is null) assert(0, "cannot initialize NanoVega");
65 		  };
66 
67 		  // this callback will be called when we will need to repaint our window
68 		  sdmain.redrawOpenGlScene = delegate () {
69 		    // fix viewport (we can do this in resize event, or here, it doesn't matter)
70 		    glViewport(0, 0, sdmain.width, sdmain.height);
71 
72 		    // clear window
73 		    glClearColor(0, 0, 0, 0);
74 		    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
75 
76 		    {
77 		      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
78 		      scope(exit) nvg.endFrame(); // and flush render queue on exit
79 
80 		      nvg.beginPath(); // start new path
81 		      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
82 		      // now set filling mode for our rectangle
83 		      // you can create colors using HTML syntax, or with convenient constants
84 		      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
85 		      // now fill our rect
86 		      nvg.fill();
87 		      // and draw a nice outline
88 		      nvg.strokeColor = NVGColor.white;
89 		      nvg.strokeWidth = 2;
90 		      nvg.stroke();
91 		      // that's all, folks!
92 		    }
93 		  };
94 
95 		  sdmain.eventLoop(0, // no pulse timer required
96 		    delegate (KeyEvent event) {
97 		      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
98 		    },
99 		  );
100 
101 		  flushGui(); // let OS do it's cleanup
102 		}
103 		---
104 	)
105 
106 	$(COLUMN
107 		Javascript code with HTML5 Canvas
108 
109 		```html
110 		<!DOCTYPE html>
111 		<html>
112 		<head>
113 			<title>NanoVega Simple Sample (HTML5 Translation)</title>
114 			<style>
115 				body { background-color: black; }
116 			</style>
117 		</head>
118 		<body>
119 			<canvas id="my-canvas" width="800" height="600"></canvas>
120 		<script>
121 			var canvas = document.getElementById("my-canvas");
122 			var context = canvas.getContext("2d");
123 
124 			context.beginPath();
125 
126 			context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60);
127 
128 			var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60);
129 			gradient.addColorStop(0, "#f70");
130 			gradient.addColorStop(1, "green");
131 
132 			context.fillStyle = gradient;
133 			context.fill();
134 			context.closePath();
135 			context.strokeStyle = "white";
136 			context.lineWidth = 2;
137 			context.stroke();
138 		</script>
139 		</body>
140 		</html>
141 		```
142 	)
143 )
144 
145 $(TIP
146     This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType.
147     Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler.
148 )
149 
150 Creating drawing context
151 ========================
152 
153 The drawing context is created using platform specific constructor function.
154 
155   ---
156   NVGContext vg = nvgCreateContext();
157   ---
158 
159 $(WARNING You must use created context ONLY in that thread where you created it.
160           There is no way to "transfer" context between threads. Trying to do so
161           will lead to UB.)
162 
163 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
164           do so will lead to UB.)
165 
166 
167 Drawing shapes with NanoVega
168 ============================
169 
170 Drawing a simple shape using NanoVega consists of four steps:
171 $(LIST
172   * begin a new shape,
173   * define the path to draw,
174   * set fill or stroke,
175   * and finally fill or stroke the path.
176 )
177 
178   ---
179   vg.beginPath();
180   vg.rect(100, 100, 120, 30);
181   vg.fillColor(nvgRGBA(255, 192, 0, 255));
182   vg.fill();
183   ---
184 
185 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
186 There are number of number of functions to define the path to draw, such as rectangle,
187 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
188 arcTo API to compose the paths step by step.
189 
190 
191 Understanding Composite Paths
192 =============================
193 
194 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
195 that is path consisting from multiple paths defining holes and fills, is a bit more
196 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
197 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
198 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
199 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
200 
201   ---
202   vg.beginPath();
203   vg.rect(100, 100, 120, 30);
204   vg.circle(120, 120, 5);
205   vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
206   vg.fillColor(nvgRGBA(255, 192, 0, 255));
207   vg.fill();
208   ---
209 
210 
211 Rendering is wrong, what to do?
212 ===============================
213 
214 $(LIST
215   * make sure you have created NanoVega context using [nvgCreateContext] call
216   * make sure you have initialised OpenGL with $(B stencil buffer)
217   * make sure you have cleared stencil buffer
218   * make sure all rendering calls happen between [beginFrame] and [endFrame]
219   * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
220 )
221 
222 
223 OpenGL state touched by the backend
224 ===================================
225 
226 The OpenGL back-end touches following states:
227 
228 When textures are uploaded or updated, the following pixel store is set to defaults:
229 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
230 Texture binding is also affected. Texture updates can happen when the user loads images,
231 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
232 and [endFrame].
233 
234 The data for the whole frame is buffered and flushed in [endFrame].
235 The following code illustrates the OpenGL state touched by the rendering code:
236 
237   ---
238   glUseProgram(prog);
239   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
240   glEnable(GL_CULL_FACE);
241   glCullFace(GL_BACK);
242   glFrontFace(GL_CCW);
243   glEnable(GL_BLEND);
244   glDisable(GL_DEPTH_TEST);
245   glDisable(GL_SCISSOR_TEST);
246   glDisable(GL_COLOR_LOGIC_OP);
247   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
248   glStencilMask(0xffffffff);
249   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
250   glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
251   glActiveTexture(GL_TEXTURE1);
252   glActiveTexture(GL_TEXTURE0);
253   glBindBuffer(GL_UNIFORM_BUFFER, buf);
254   glBindVertexArray(arr);
255   glBindBuffer(GL_ARRAY_BUFFER, buf);
256   glBindTexture(GL_TEXTURE_2D, tex);
257   glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
258   ---
259 
260   Symbol_groups:
261 
262   context_management =
263     ## Context Management
264 
265     Functions to create and destory NanoVega context.
266 
267   frame_management =
268     ## Frame Management
269 
270     To start drawing with NanoVega context, you have to "begin frame", and then
271     "end frame" to flush your rendering commands to GPU.
272 
273   composite_operation =
274     ## Composite Operation
275 
276     The composite operations in NanoVega are modeled after HTML Canvas API, and
277     the blend func is based on OpenGL (see corresponding manuals for more info).
278     The colors in the blending state have premultiplied alpha.
279 
280   color_utils =
281     ## Color Utils
282 
283     Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
284 
285   matrices =
286     ## Matrices and Transformations
287 
288     The paths, gradients, patterns and scissor region are transformed by an transformation
289     matrix at the time when they are passed to the API.
290     The current transformation matrix is an affine matrix:
291 
292     ----------------------
293       [sx kx tx]
294       [ky sy ty]
295       [ 0  0  1]
296     ----------------------
297 
298     Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
299     The last row is assumed to be (0, 0, 1) and is not stored.
300 
301     Apart from [resetTransform], each transformation function first creates
302     specific transformation matrix and pre-multiplies the current transformation by it.
303 
304     Current coordinate system (transformation) can be saved and restored using [save] and [restore].
305 
306     The following functions can be used to make calculations on 2x3 transformation matrices.
307     A 2x3 matrix is represented as float[6].
308 
309   state_handling =
310     ## State Handling
311 
312     NanoVega contains state which represents how paths will be rendered.
313     The state contains transform, fill and stroke styles, text and font styles,
314     and scissor clipping.
315 
316   render_styles =
317     ## Render Styles
318 
319     Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
320     Solid color is simply defined as a color value, different kinds of paints can be created
321     using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
322 
323     Current render style can be saved and restored using [save] and [restore].
324 
325     Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
326     and use `integerCoord+0.5f` as pixel coordinates.
327 
328   render_transformations =
329     ## Render Transformations
330 
331     Transformation matrix management for the current rendering style. Transformations are applied in
332     backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
333     it's origin, and then translated to the destination point.
334 
335   scissoring =
336     ## Scissoring
337 
338     Scissoring allows you to clip the rendering into a rectangle. This is useful for various
339     user interface cases like rendering a text edit or a timeline.
340 
341   images =
342     ## Images
343 
344     NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
345     In addition you can upload your own image.
346     The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
347 
348     If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
349     [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
350 
351   paints =
352     ## Paints
353 
354     NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
355     These can be used as paints for strokes and fills.
356 
357   gpu_affine =
358     ## Render-Time Affine Transformations
359 
360     It is possible to set affine transformation matrix for GPU. That matrix will
361     be applied by the shader code. This can be used to quickly translate and rotate
362     saved paths. Call this $(B only) between [beginFrame] and [endFrame].
363 
364     Note that [beginFrame] resets this matrix to identity one.
365 
366     $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
367 
368   paths =
369     ## Paths
370 
371     Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
372     Then you define one or more paths and sub-paths which describe the shape. The are functions
373     to draw common shapes like rectangles and circles, and lower level step-by-step functions,
374     which allow to define a path curve by curve.
375 
376     NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
377     winding and holes should have counter clockwise order. To specify winding of a path you can
378     call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
379 
380     Finally you can fill the path using current fill style by calling [fill], and stroke it
381     with current stroke style by calling [stroke].
382 
383     The curve segments and sub-paths are transformed by the current transform.
384 
385   picking_api =
386     ## Picking API
387 
388     This is picking API that works directly on paths, without rasterizing them first.
389 
390     [beginFrame] resets picking state. Then you can create paths as usual, but
391     there is a possibility to perform hit checks $(B before) rasterizing a path.
392     Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
393     immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
394     before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
395     effects, for example.
396 
397     Also note that picking API is ignoring GPU affine transformation matrix.
398     You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
399 
400     $(WARNING Picking API completely ignores clipping. If you want to check for
401               clip regions, you have to manually register them as fill/stroke paths,
402               and perform the necessary logic. See [hitTestForId] function.)
403 
404   clipping =
405     ## Clipping with paths
406 
407     If scissoring is not enough for you, you can clip rendering with arbitrary path,
408     or with combination of paths. Clip region is saved by [save] and restored by
409     [restore] NanoVega functions. You can combine clip paths with various logic
410     operations, see [NVGClipMode].
411 
412     Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
413     is created as if there was no scissor set). Actual rendering is affected by
414     scissors, though.
415 
416   text_api =
417     ## Text
418 
419     NanoVega allows you to load .ttf files and use the font to render text.
420     You have to load some font, and set proper font size before doing anything
421     with text, as there is no "default" font provided by NanoVega. Also, don't
422     forget to check return value of `createFont()`, 'cause NanoVega won't fail
423     if it cannot load font, it will silently try to render nothing.
424 
425     The appearance of the text can be defined by setting the current text style
426     and by specifying the fill color. Common text and font settings such as
427     font size, letter spacing and text align are supported. Font blur allows you
428     to create simple text effects such as drop shadows.
429 
430     At render time the font face can be set based on the font handles or name.
431 
432     Font measure functions return values in local space, the calculations are
433     carried in the same resolution as the final rendering. This is done because
434     the text glyph positions are snapped to the nearest pixels sharp rendering.
435 
436     The local space means that values are not rotated or scale as per the current
437     transformation. For example if you set font size to 12, which would mean that
438     line height is 16, then regardless of the current scaling and rotation, the
439     returned line height is always 16. Some measures may vary because of the scaling
440     since aforementioned pixel snapping.
441 
442     While this may sound a little odd, the setup allows you to always render the
443     same way regardless of scaling. I.e. following works regardless of scaling:
444 
445     ----------------------
446        string txt = "Text me up.";
447        vg.textBounds(x, y, txt, bounds);
448        vg.beginPath();
449        vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
450        vg.fill();
451     ----------------------
452 
453     Note: currently only solid color fill is supported for text.
454 
455   font_stash =
456     ## Low-Level Font Engine (FontStash)
457 
458     FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
459     You don't need any graphics context to use FontStash, so you can do things like text
460     layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
461     to create new FontStash, copy fonts from NanoVega context into it, and use that new
462     FontStash to do some UI layouting, for example. Also note that you can get text metrics
463     without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
464     you don't need to waste CPU and memory resources to render unneeded images into font atlas,
465     and you can layout alot of text very fast.
466 
467     Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
468     word "fons" in it, this is not a typo, and it should not read "font" intead.
469 
470     TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
471  */
472 module arsd.nanovega;
473 private:
474 
475 version(aliced) {
476   import iv.meta;
477   import iv.vfs;
478 } else {
479   private alias usize = size_t;
480   // i fear phobos!
481   private template Unqual(T) {
482          static if (is(T U ==          immutable U)) alias Unqual = U;
483     else static if (is(T U == shared inout const U)) alias Unqual = U;
484     else static if (is(T U == shared inout       U)) alias Unqual = U;
485     else static if (is(T U == shared       const U)) alias Unqual = U;
486     else static if (is(T U == shared             U)) alias Unqual = U;
487     else static if (is(T U ==        inout const U)) alias Unqual = U;
488     else static if (is(T U ==        inout       U)) alias Unqual = U;
489     else static if (is(T U ==              const U)) alias Unqual = U;
490     else alias Unqual = T;
491   }
492   private template isAnyCharType(T, bool unqual=false) {
493     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
494     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
495   }
496   private template isWideCharType(T, bool unqual=false) {
497     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
498     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
499   }
500 }
501 version(nanovg_disable_vfs) {
502   enum NanoVegaHasIVVFS = false;
503 } else {
504   static if (is(typeof((){import iv.vfs;}))) {
505     enum NanoVegaHasIVVFS = true;
506     import iv.vfs;
507   } else {
508     enum NanoVegaHasIVVFS = false;
509   }
510 }
511 
512 // ////////////////////////////////////////////////////////////////////////// //
513 // engine
514 // ////////////////////////////////////////////////////////////////////////// //
515 import core.stdc.stdlib : malloc, realloc, free;
516 import core.stdc..string : memset, memcpy, strlen;
517 import std.math : PI;
518 
519 //version = nanovg_force_stb_ttf;
520 
521 version(Posix) {
522   version = nanovg_use_freetype;
523 } else {
524   version = nanovg_disable_fontconfig;
525 }
526 
527 version (bindbc) {
528   version = nanovg_builtin_fontconfig_bindings;
529   version = nanovg_bindbc_opengl_bindings;
530   version = nanovg_bindbc_freetype_bindings;
531   version(BindFT_Dynamic)
532     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
533   else {
534     version(BindFT_Static) {}
535     else
536       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
537   }
538 } else version(aliced) {
539   version = nanovg_default_no_font_aa;
540   version = nanovg_builtin_fontconfig_bindings;
541   version = nanovg_builtin_freetype_bindings;
542   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
543 } else {
544   version = nanovg_builtin_fontconfig_bindings;
545   version = nanovg_builtin_freetype_bindings;
546   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
547 }
548 
549 version(nanovg_disable_fontconfig) {
550   public enum NanoVegaHasFontConfig = false;
551 } else {
552   public enum NanoVegaHasFontConfig = true;
553   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
554 }
555 
556 //version = nanovg_bench_flatten;
557 
558 /++
559 	Annotation to indicate the marked function is compatible with [arsd.script].
560 
561 
562 	Any function that takes a [Color] argument will be passed a string instead.
563 
564 	Scriptable Functions
565 	====================
566 
567 	$(UDA_USES)
568 
569 	$(ALWAYS_DOCUMENT)
570 +/
571 private enum scriptable = "arsd_jsvar_compatible";
572 
573 public:
574 alias NVG_PI = PI;
575 
576 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
577 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
578 
579 static if (NanoVegaHasArsdColor) private import arsd.color;
580 static if (NanoVegaHasArsdImage) {
581   private import arsd.image;
582 } else {
583   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
584   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
585   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
586   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
587   void stbi_image_free (void* retval_from_stbi_load) {}
588 }
589 
590 version(nanovg_default_no_font_aa) {
591   __gshared bool NVG_INVERT_FONT_AA = false;
592 } else {
593   __gshared bool NVG_INVERT_FONT_AA = true;
594 }
595 
596 
597 /// this is branchless for ints on x86, and even for longs on x86_64
598 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
599   static if (__VERSION__ > 2067) pragma(inline, true);
600   static if (T.sizeof == 2 || T.sizeof == 4) {
601     static if (__traits(isUnsigned, T)) {
602       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
603     } else {
604       n &= -cast(int)(n >= 0);
605       return cast(ubyte)(n|((255-cast(int)n)>>31));
606     }
607   } else static if (T.sizeof == 1) {
608     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
609     return cast(ubyte)n;
610   } else static if (T.sizeof == 8) {
611     static if (__traits(isUnsigned, T)) {
612       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
613     } else {
614       n &= -cast(long)(n >= 0);
615       return cast(ubyte)(n|((255-cast(long)n)>>63));
616     }
617   } else {
618     static assert(false, "clampToByte: integer too big");
619   }
620 }
621 
622 
623 /// NanoVega RGBA color
624 /// Group: color_utils
625 public align(1) struct NVGColor {
626 align(1):
627 public:
628   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
629 
630 public:
631   @property string toString () const @safe { import std..string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
632 
633 public:
634   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
635   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
636 
637   enum aliceblue = NVGColor(240, 248, 255);
638   enum antiquewhite = NVGColor(250, 235, 215);
639   enum aqua = NVGColor(0, 255, 255);
640   enum aquamarine = NVGColor(127, 255, 212);
641   enum azure = NVGColor(240, 255, 255);
642   enum beige = NVGColor(245, 245, 220);
643   enum bisque = NVGColor(255, 228, 196);
644   enum black = NVGColor(0, 0, 0); // basic color
645   enum blanchedalmond = NVGColor(255, 235, 205);
646   enum blue = NVGColor(0, 0, 255); // basic color
647   enum blueviolet = NVGColor(138, 43, 226);
648   enum brown = NVGColor(165, 42, 42);
649   enum burlywood = NVGColor(222, 184, 135);
650   enum cadetblue = NVGColor(95, 158, 160);
651   enum chartreuse = NVGColor(127, 255, 0);
652   enum chocolate = NVGColor(210, 105, 30);
653   enum coral = NVGColor(255, 127, 80);
654   enum cornflowerblue = NVGColor(100, 149, 237);
655   enum cornsilk = NVGColor(255, 248, 220);
656   enum crimson = NVGColor(220, 20, 60);
657   enum cyan = NVGColor(0, 255, 255); // basic color
658   enum darkblue = NVGColor(0, 0, 139);
659   enum darkcyan = NVGColor(0, 139, 139);
660   enum darkgoldenrod = NVGColor(184, 134, 11);
661   enum darkgray = NVGColor(169, 169, 169);
662   enum darkgreen = NVGColor(0, 100, 0);
663   enum darkgrey = NVGColor(169, 169, 169);
664   enum darkkhaki = NVGColor(189, 183, 107);
665   enum darkmagenta = NVGColor(139, 0, 139);
666   enum darkolivegreen = NVGColor(85, 107, 47);
667   enum darkorange = NVGColor(255, 140, 0);
668   enum darkorchid = NVGColor(153, 50, 204);
669   enum darkred = NVGColor(139, 0, 0);
670   enum darksalmon = NVGColor(233, 150, 122);
671   enum darkseagreen = NVGColor(143, 188, 143);
672   enum darkslateblue = NVGColor(72, 61, 139);
673   enum darkslategray = NVGColor(47, 79, 79);
674   enum darkslategrey = NVGColor(47, 79, 79);
675   enum darkturquoise = NVGColor(0, 206, 209);
676   enum darkviolet = NVGColor(148, 0, 211);
677   enum deeppink = NVGColor(255, 20, 147);
678   enum deepskyblue = NVGColor(0, 191, 255);
679   enum dimgray = NVGColor(105, 105, 105);
680   enum dimgrey = NVGColor(105, 105, 105);
681   enum dodgerblue = NVGColor(30, 144, 255);
682   enum firebrick = NVGColor(178, 34, 34);
683   enum floralwhite = NVGColor(255, 250, 240);
684   enum forestgreen = NVGColor(34, 139, 34);
685   enum fuchsia = NVGColor(255, 0, 255);
686   enum gainsboro = NVGColor(220, 220, 220);
687   enum ghostwhite = NVGColor(248, 248, 255);
688   enum gold = NVGColor(255, 215, 0);
689   enum goldenrod = NVGColor(218, 165, 32);
690   enum gray = NVGColor(128, 128, 128); // basic color
691   enum green = NVGColor(0, 128, 0); // basic color
692   enum greenyellow = NVGColor(173, 255, 47);
693   enum grey = NVGColor(128, 128, 128); // basic color
694   enum honeydew = NVGColor(240, 255, 240);
695   enum hotpink = NVGColor(255, 105, 180);
696   enum indianred = NVGColor(205, 92, 92);
697   enum indigo = NVGColor(75, 0, 130);
698   enum ivory = NVGColor(255, 255, 240);
699   enum khaki = NVGColor(240, 230, 140);
700   enum lavender = NVGColor(230, 230, 250);
701   enum lavenderblush = NVGColor(255, 240, 245);
702   enum lawngreen = NVGColor(124, 252, 0);
703   enum lemonchiffon = NVGColor(255, 250, 205);
704   enum lightblue = NVGColor(173, 216, 230);
705   enum lightcoral = NVGColor(240, 128, 128);
706   enum lightcyan = NVGColor(224, 255, 255);
707   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
708   enum lightgray = NVGColor(211, 211, 211);
709   enum lightgreen = NVGColor(144, 238, 144);
710   enum lightgrey = NVGColor(211, 211, 211);
711   enum lightpink = NVGColor(255, 182, 193);
712   enum lightsalmon = NVGColor(255, 160, 122);
713   enum lightseagreen = NVGColor(32, 178, 170);
714   enum lightskyblue = NVGColor(135, 206, 250);
715   enum lightslategray = NVGColor(119, 136, 153);
716   enum lightslategrey = NVGColor(119, 136, 153);
717   enum lightsteelblue = NVGColor(176, 196, 222);
718   enum lightyellow = NVGColor(255, 255, 224);
719   enum lime = NVGColor(0, 255, 0);
720   enum limegreen = NVGColor(50, 205, 50);
721   enum linen = NVGColor(250, 240, 230);
722   enum magenta = NVGColor(255, 0, 255); // basic color
723   enum maroon = NVGColor(128, 0, 0);
724   enum mediumaquamarine = NVGColor(102, 205, 170);
725   enum mediumblue = NVGColor(0, 0, 205);
726   enum mediumorchid = NVGColor(186, 85, 211);
727   enum mediumpurple = NVGColor(147, 112, 219);
728   enum mediumseagreen = NVGColor(60, 179, 113);
729   enum mediumslateblue = NVGColor(123, 104, 238);
730   enum mediumspringgreen = NVGColor(0, 250, 154);
731   enum mediumturquoise = NVGColor(72, 209, 204);
732   enum mediumvioletred = NVGColor(199, 21, 133);
733   enum midnightblue = NVGColor(25, 25, 112);
734   enum mintcream = NVGColor(245, 255, 250);
735   enum mistyrose = NVGColor(255, 228, 225);
736   enum moccasin = NVGColor(255, 228, 181);
737   enum navajowhite = NVGColor(255, 222, 173);
738   enum navy = NVGColor(0, 0, 128);
739   enum oldlace = NVGColor(253, 245, 230);
740   enum olive = NVGColor(128, 128, 0);
741   enum olivedrab = NVGColor(107, 142, 35);
742   enum orange = NVGColor(255, 165, 0);
743   enum orangered = NVGColor(255, 69, 0);
744   enum orchid = NVGColor(218, 112, 214);
745   enum palegoldenrod = NVGColor(238, 232, 170);
746   enum palegreen = NVGColor(152, 251, 152);
747   enum paleturquoise = NVGColor(175, 238, 238);
748   enum palevioletred = NVGColor(219, 112, 147);
749   enum papayawhip = NVGColor(255, 239, 213);
750   enum peachpuff = NVGColor(255, 218, 185);
751   enum peru = NVGColor(205, 133, 63);
752   enum pink = NVGColor(255, 192, 203);
753   enum plum = NVGColor(221, 160, 221);
754   enum powderblue = NVGColor(176, 224, 230);
755   enum purple = NVGColor(128, 0, 128);
756   enum red = NVGColor(255, 0, 0); // basic color
757   enum rosybrown = NVGColor(188, 143, 143);
758   enum royalblue = NVGColor(65, 105, 225);
759   enum saddlebrown = NVGColor(139, 69, 19);
760   enum salmon = NVGColor(250, 128, 114);
761   enum sandybrown = NVGColor(244, 164, 96);
762   enum seagreen = NVGColor(46, 139, 87);
763   enum seashell = NVGColor(255, 245, 238);
764   enum sienna = NVGColor(160, 82, 45);
765   enum silver = NVGColor(192, 192, 192);
766   enum skyblue = NVGColor(135, 206, 235);
767   enum slateblue = NVGColor(106, 90, 205);
768   enum slategray = NVGColor(112, 128, 144);
769   enum slategrey = NVGColor(112, 128, 144);
770   enum snow = NVGColor(255, 250, 250);
771   enum springgreen = NVGColor(0, 255, 127);
772   enum steelblue = NVGColor(70, 130, 180);
773   enum tan = NVGColor(210, 180, 140);
774   enum teal = NVGColor(0, 128, 128);
775   enum thistle = NVGColor(216, 191, 216);
776   enum tomato = NVGColor(255, 99, 71);
777   enum turquoise = NVGColor(64, 224, 208);
778   enum violet = NVGColor(238, 130, 238);
779   enum wheat = NVGColor(245, 222, 179);
780   enum white = NVGColor(255, 255, 255); // basic color
781   enum whitesmoke = NVGColor(245, 245, 245);
782   enum yellow = NVGColor(255, 255, 0); // basic color
783   enum yellowgreen = NVGColor(154, 205, 50);
784 
785 nothrow @safe @nogc:
786 public:
787   ///
788   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
789     pragma(inline, true);
790     r = ar/255.0f;
791     g = ag/255.0f;
792     b = ab/255.0f;
793     a = aa/255.0f;
794   }
795 
796   ///
797   this (float ar, float ag, float ab, float aa=1.0f) pure {
798     pragma(inline, true);
799     r = ar;
800     g = ag;
801     b = ab;
802     a = aa;
803   }
804 
805   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
806   this (uint c) pure {
807     pragma(inline, true);
808     r = (c&0xff)/255.0f;
809     g = ((c>>8)&0xff)/255.0f;
810     b = ((c>>16)&0xff)/255.0f;
811     a = ((c>>24)&0xff)/255.0f;
812   }
813 
814   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
815   this (const(char)[] srgb) {
816     static int c2d (char ch) pure nothrow @safe @nogc {
817       pragma(inline, true);
818       return
819         ch >= '0' && ch <= '9' ? ch-'0' :
820         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
821         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
822         -1;
823     }
824     int[8] digs;
825     int dc = -1;
826     foreach (immutable char ch; srgb) {
827       if (ch <= ' ') continue;
828       if (ch == '#') {
829         if (dc != -1) { dc = -1; break; }
830         dc = 0;
831       } else {
832         if (dc >= digs.length) { dc = -1; break; }
833         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
834       }
835     }
836     switch (dc) {
837       case 3: // rgb
838         a = 1.0f;
839         r = digs[0]/15.0f;
840         g = digs[1]/15.0f;
841         b = digs[2]/15.0f;
842         break;
843       case 4: // argb
844         a = digs[0]/15.0f;
845         r = digs[1]/15.0f;
846         g = digs[2]/15.0f;
847         b = digs[3]/15.0f;
848         break;
849       case 6: // rrggbb
850         a = 1.0f;
851         r = (digs[0]*16+digs[1])/255.0f;
852         g = (digs[2]*16+digs[3])/255.0f;
853         b = (digs[4]*16+digs[5])/255.0f;
854         break;
855       case 8: // aarrggbb
856         a = (digs[0]*16+digs[1])/255.0f;
857         r = (digs[2]*16+digs[3])/255.0f;
858         g = (digs[4]*16+digs[5])/255.0f;
859         b = (digs[6]*16+digs[7])/255.0f;
860         break;
861       default:
862         break;
863     }
864   }
865 
866   /// Is this color completely opaque?
867   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
868   /// Is this color completely transparent?
869   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
870 
871   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
872   @property uint asUint () const pure {
873     pragma(inline, true);
874     return
875       cast(uint)(r*255)|
876       (cast(uint)(g*255)<<8)|
877       (cast(uint)(b*255)<<16)|
878       (cast(uint)(a*255)<<24);
879   }
880 
881   alias asUintABGR = asUint; /// Ditto.
882 
883   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
884   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
885 
886   alias fromUintABGR = fromUint; /// Ditto.
887 
888   /// AARRGGBB
889   @property uint asUintARGB () const pure {
890     pragma(inline, true);
891     return
892       cast(uint)(b*255)|
893       (cast(uint)(g*255)<<8)|
894       (cast(uint)(r*255)<<16)|
895       (cast(uint)(a*255)<<24);
896   }
897 
898   /// AARRGGBB
899   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
900 
901   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
902   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
903   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
904   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
905 
906   ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc {
907     if (tint.a == 0) return this;
908     foreach (immutable idx, ref float v; rgba[0..4]) {
909       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
910     }
911     return this;
912   }
913 
914   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
915   static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
916 
917   static if (NanoVegaHasArsdColor) {
918     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
919     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
920     ///
921     this (in Color c) {
922       version(aliced) pragma(inline, true);
923       r = c.r/255.0f;
924       g = c.g/255.0f;
925       b = c.b/255.0f;
926       a = c.a/255.0f;
927     }
928   }
929 }
930 
931 
932 /// NanoVega A-HSL color
933 /// Group: color_utils
934 public align(1) struct NVGHSL {
935 align(1):
936   float h=0, s=0, l=1, a=1; ///
937 
938   string toString () const { import std.format : format; return (a != 1 ? "HSL(%s,%s,%s,%d)".format(h, s, l, a) : "HSL(%s,%s,%s)".format(h, s, l)); }
939 
940 nothrow @safe @nogc:
941 public:
942   ///
943   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
944 
945   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
946 
947   // taken from Adam's arsd.color
948   /** Converts an RGB color into an HSL triplet.
949    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
950    * which is more sensitive to green than red and more to red than blue.
951    * If it is false, it just does average of the rgb. */
952   static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure {
953     NVGHSL res;
954     res.a = c.a;
955     float r1 = c.r;
956     float g1 = c.g;
957     float b1 = c.b;
958 
959     float maxColor = r1;
960     if (g1 > maxColor) maxColor = g1;
961     if (b1 > maxColor) maxColor = b1;
962     float minColor = r1;
963     if (g1 < minColor) minColor = g1;
964     if (b1 < minColor) minColor = b1;
965 
966     res.l = (maxColor+minColor)/2;
967     if (useWeightedLightness) {
968       // the colors don't affect the eye equally
969       // this is a little more accurate than plain HSL numbers
970       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
971     }
972     if (maxColor != minColor) {
973       if (res.l < 0.5) {
974         res.s = (maxColor-minColor)/(maxColor+minColor);
975       } else {
976         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
977       }
978       if (r1 == maxColor) {
979         res.h = (g1-b1)/(maxColor-minColor);
980       } else if(g1 == maxColor) {
981         res.h = 2.0+(b1-r1)/(maxColor-minColor);
982       } else {
983         res.h = 4.0+(r1-g1)/(maxColor-minColor);
984       }
985     }
986 
987     res.h = res.h*60;
988     if (res.h < 0) res.h += 360;
989     res.h /= 360;
990 
991     return res;
992   }
993 }
994 
995 
996 //version = nanovega_debug_image_manager;
997 //version = nanovega_debug_image_manager_rc;
998 
999 /** NanoVega image handle.
1000  *
1001  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1002  *
1003  * Group: images
1004  */
1005 struct NVGImage {
1006 	enum isOpaqueStruct = true;
1007 private:
1008   NVGContext ctx;
1009   int id; // backend image id
1010 
1011 public:
1012   ///
1013   this() (in auto ref NVGImage src) nothrow @trusted @nogc {
1014     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; if (src.id != 0) printf("NVGImage %p created from %p (imgid=%d)\n", &this, src, src.id); }
1015     if (src.id > 0 && src.ctx !is null) {
1016       ctx = cast(NVGContext)src.ctx;
1017       id = src.id;
1018       ctx.nvg__imageIncRef(id);
1019     }
1020   }
1021 
1022   ///
1023   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1024 
1025   ///
1026   this (this) nothrow @trusted @nogc {
1027     version(aliced) pragma(inline, true);
1028     if (id > 0 && ctx !is null) {
1029       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1030       ctx.nvg__imageIncRef(id);
1031     }
1032   }
1033 
1034   ///
1035   void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc {
1036     if (src.id <= 0 || src.ctx is null) {
1037       clear();
1038     } else {
1039       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p (imgid=%d) assigned from %p (imgid=%d)\n", &this, id, &src, src.id); }
1040       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1041       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1042       ctx = cast(NVGContext)src.ctx;
1043       id = src.id;
1044     }
1045   }
1046 
1047   /// Free this image.
1048   void clear () nothrow @trusted @nogc {
1049     if (id > 0 && ctx !is null) {
1050       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1051       ctx.nvg__imageDecRef(id);
1052     }
1053     id = 0;
1054     ctx = null;
1055   }
1056 
1057   /// Is this image valid?
1058   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1059 
1060   /// Is the given image valid and comes from the same context?
1061   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1062 
1063   /// Returns image width, or zero for invalid image.
1064   int width () const nothrow @trusted @nogc {
1065     int w = 0;
1066     if (valid) {
1067       int h = void;
1068       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1069     }
1070     return w;
1071   }
1072 
1073   /// Returns image height, or zero for invalid image.
1074   int height () const nothrow @trusted @nogc {
1075     int h = 0;
1076     if (valid) {
1077       int w = void;
1078       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1079     }
1080     return h;
1081   }
1082 }
1083 
1084 
1085 /// Paint parameters for various fills. Don't change anything here!
1086 /// Group: render_styles
1087 public struct NVGPaint {
1088   enum isOpaqueStruct = true;
1089 
1090   NVGMatrix xform;
1091   float[2] extent = 0.0f;
1092   float radius = 0.0f;
1093   float feather = 0.0f;
1094   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1095   NVGColor middleColor;
1096   NVGColor outerColor;
1097   float midp = -1; // middle stop for 3-color gradient
1098   NVGImage image;
1099   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1100 
1101   this() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1102     xform = p.xform;
1103     extent[] = p.extent[];
1104     radius = p.radius;
1105     feather = p.feather;
1106     innerColor = p.innerColor;
1107     middleColor = p.middleColor;
1108     midp = p.midp;
1109     outerColor = p.outerColor;
1110     image = p.image;
1111     simpleColor = p.simpleColor;
1112   }
1113 
1114   void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1115     xform = p.xform;
1116     extent[] = p.extent[];
1117     radius = p.radius;
1118     feather = p.feather;
1119     innerColor = p.innerColor;
1120     middleColor = p.middleColor;
1121     midp = p.midp;
1122     outerColor = p.outerColor;
1123     image = p.image;
1124     simpleColor = p.simpleColor;
1125   }
1126 
1127   void clear () nothrow @trusted @nogc {
1128     version(aliced) pragma(inline, true);
1129     import core.stdc..string : memset;
1130     image.clear();
1131     memset(&this, 0, this.sizeof);
1132     simpleColor = true;
1133   }
1134 }
1135 
1136 /// Path winding.
1137 /// Group: paths
1138 public enum NVGWinding {
1139   CCW = 1, /// Winding for solid shapes
1140   CW = 2,  /// Winding for holes
1141 }
1142 
1143 /// Path solidity.
1144 /// Group: paths
1145 public enum NVGSolidity {
1146   Solid = 1, /// Solid shape (CCW winding).
1147   Hole = 2, /// Hole (CW winding).
1148 }
1149 
1150 /// Line cap style.
1151 /// Group: render_styles
1152 public enum NVGLineCap {
1153   Butt, ///
1154   Round, ///
1155   Square, ///
1156   Bevel, ///
1157   Miter, ///
1158 }
1159 
1160 /// Text align.
1161 /// Group: text_api
1162 public align(1) struct NVGTextAlign {
1163 align(1):
1164   /// Horizontal align.
1165   enum H : ubyte {
1166     Left   = 0, /// Default, align text horizontally to left.
1167     Center = 1, /// Align text horizontally to center.
1168     Right  = 2, /// Align text horizontally to right.
1169   }
1170 
1171   /// Vertical align.
1172   enum V : ubyte {
1173     Baseline = 0, /// Default, align text vertically to baseline.
1174     Top      = 1, /// Align text vertically to top.
1175     Middle   = 2, /// Align text vertically to middle.
1176     Bottom   = 3, /// Align text vertically to bottom.
1177   }
1178 
1179 pure nothrow @safe @nogc:
1180 public:
1181   this (H h) { pragma(inline, true); value = h; } ///
1182   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1183   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1184   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1185   void reset () { pragma(inline, true); value = 0; } ///
1186   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1187   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1188 @property:
1189   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1190   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1191   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1192   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1193   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1194   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1195   //
1196   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1197   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1198   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1199   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1200   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1201   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1202   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1203   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1204   //
1205   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1206   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1207   //
1208   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1209   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1210   //
1211 private:
1212   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1213 }
1214 
1215 /// Blending type.
1216 /// Group: composite_operation
1217 public enum NVGBlendFactor {
1218   Zero = 1<<0, ///
1219   One = 1<<1, ///
1220   SrcColor = 1<<2, ///
1221   OneMinusSrcColor = 1<<3, ///
1222   DstColor = 1<<4, ///
1223   OneMinusDstColor = 1<<5, ///
1224   SrcAlpha = 1<<6, ///
1225   OneMinusSrcAlpha = 1<<7, ///
1226   DstAlpha = 1<<8, ///
1227   OneMinusDstAlpha = 1<<9, ///
1228   SrcAlphaSaturate = 1<<10, ///
1229 }
1230 
1231 /// Composite operation (HTML5-alike).
1232 /// Group: composite_operation
1233 public enum NVGCompositeOperation {
1234   SourceOver, ///
1235   SourceIn, ///
1236   SourceOut, ///
1237   SourceAtop, ///
1238   DestinationOver, ///
1239   DestinationIn, ///
1240   DestinationOut, ///
1241   DestinationAtop, ///
1242   Lighter, ///
1243   Copy, ///
1244   Xor, ///
1245 }
1246 
1247 /// Composite operation state.
1248 /// Group: composite_operation
1249 public struct NVGCompositeOperationState {
1250   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1251   NVGBlendFactor srcRGB; ///
1252   NVGBlendFactor dstRGB; ///
1253   NVGBlendFactor srcAlpha; ///
1254   NVGBlendFactor dstAlpha; ///
1255 }
1256 
1257 /// Mask combining more
1258 /// Group: clipping
1259 public enum NVGClipMode {
1260   None, /// normal rendering (i.e. render path instead of modifying clip region)
1261   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1262   Or, /// new mask will be added to the current one (logical `OR` operation);
1263   Xor, /// new mask will be logically `XOR`ed with the current one
1264   Sub, /// "subtract" current path from mask
1265   Replace, /// replace current mask
1266   Add = Or, /// Synonym
1267 }
1268 
1269 /// Glyph position info.
1270 /// Group: text_api
1271 public struct NVGGlyphPosition {
1272   usize strpos;     /// Position of the glyph in the input string.
1273   float x;          /// The x-coordinate of the logical glyph position.
1274   float minx, maxx; /// The bounds of the glyph shape.
1275 }
1276 
1277 /// Text row storage.
1278 /// Group: text_api
1279 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1280   alias CharType = CT;
1281   const(CT)[] s;
1282   int start;        /// Index in the input text where the row starts.
1283   int end;          /// Index in the input text where the row ends (one past the last character).
1284   float width;      /// Logical width of the row.
1285   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1286   /// Get rest of the string.
1287   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1288   /// Get current row.
1289   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1290   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1291   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1292 }
1293 
1294 /// Image creation flags.
1295 /// Group: images
1296 public enum NVGImageFlag : uint {
1297   None            =    0, /// Nothing special.
1298   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1299   RepeatX         = 1<<1, /// Repeat image in X direction.
1300   RepeatY         = 1<<2, /// Repeat image in Y direction.
1301   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1302   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1303   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1304   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1305   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1306   Nearest = NoFiltering,  /// compatibility with original NanoVG
1307   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1308 }
1309 
1310 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1311 
1312 
1313 // ////////////////////////////////////////////////////////////////////////// //
1314 private:
1315 
1316 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1317   import core.stdc.stdlib : malloc;
1318   import core.stdc..string : memcpy;
1319   if (count == 0) return null;
1320   T* res = cast(T*)malloc(T.sizeof*count);
1321   if (res is null) assert(0, "NanoVega: out of memory");
1322   memcpy(res, ptr, T.sizeof*count);
1323   return res;
1324 }
1325 
1326 // Internal Render API
1327 enum NVGtexture {
1328   Alpha = 0x01,
1329   RGBA  = 0x02,
1330 }
1331 
1332 struct NVGscissor {
1333   NVGMatrix xform;
1334   float[2] extent = -1.0f;
1335 }
1336 
1337 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1338 public struct NVGVertex {
1339   float x, y, u, v;
1340 }
1341 
1342 struct NVGpath {
1343   int first;
1344   int count;
1345   bool closed;
1346   int nbevel;
1347   NVGVertex* fill;
1348   int nfill;
1349   NVGVertex* stroke;
1350   int nstroke;
1351   NVGWinding mWinding;
1352   bool convex;
1353   bool cloned;
1354 
1355   @disable this (this); // no copies
1356   void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); }
1357 
1358   void clear () nothrow @trusted @nogc {
1359     import core.stdc.stdlib : free;
1360     import core.stdc..string : memset;
1361     if (cloned) {
1362       if (stroke !is null && stroke !is fill) free(stroke);
1363       if (fill !is null) free(fill);
1364     }
1365     memset(&this, 0, this.sizeof);
1366   }
1367 
1368   // won't clear current path
1369   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1370     import core.stdc..string : memcpy;
1371     assert(src !is null);
1372     memcpy(&this, src, NVGpath.sizeof);
1373     this.fill = xdup(src.fill, src.nfill);
1374     if (src.stroke is src.fill) {
1375       this.stroke = this.fill;
1376     } else {
1377       this.stroke = xdup(src.stroke, src.nstroke);
1378     }
1379     this.cloned = true;
1380   }
1381 
1382   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1383     pragma(inline, true);
1384     return (nfill > 0 ? fill[0..nfill] : null);
1385   }
1386 
1387   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1388     pragma(inline, true);
1389     return (nstroke > 0 ? stroke[0..nstroke] : null);
1390   }
1391 
1392   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1393   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1394 }
1395 
1396 
1397 struct NVGparams {
1398   void* userPtr;
1399   bool edgeAntiAlias;
1400   bool fontAA;
1401   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1402   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1403   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1404   bool function (void* uptr, int image) nothrow @trusted @nogc renderDeleteTexture; // this basically does decref; also, it should be thread-safe, and postpone real deletion to next `renderViewport()` call
1405   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1406   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1407   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1408   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1409   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1410   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1411   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1412   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1413   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, float fringe, const(float)* bounds, const(NVGpath)* paths, int npaths, bool evenOdd) nothrow @trusted @nogc renderFill;
1414   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const(NVGpath)* paths, int npaths) nothrow @trusted @nogc renderStroke;
1415   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1416   void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1417   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1418 }
1419 
1420 // ////////////////////////////////////////////////////////////////////////// //
1421 private:
1422 
1423 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1424 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1425 enum NVG_MAX_FONTIMAGES      = 4;
1426 
1427 enum NVG_INIT_COMMANDS_SIZE = 256;
1428 enum NVG_INIT_POINTS_SIZE   = 128;
1429 enum NVG_INIT_PATHS_SIZE    = 16;
1430 enum NVG_INIT_VERTS_SIZE    = 256;
1431 enum NVG_MAX_STATES         = 32;
1432 
1433 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1434 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1435 
1436 enum Command {
1437   MoveTo = 0,
1438   LineTo = 1,
1439   BezierTo = 2,
1440   Close = 3,
1441   Winding = 4,
1442 }
1443 
1444 enum PointFlag : int {
1445   Corner = 0x01,
1446   Left = 0x02,
1447   Bevel = 0x04,
1448   InnerBevelPR = 0x08,
1449 }
1450 
1451 struct NVGstate {
1452   NVGCompositeOperationState compositeOperation;
1453   bool shapeAntiAlias = true;
1454   NVGPaint fill;
1455   NVGPaint stroke;
1456   float strokeWidth = 1.0f;
1457   float miterLimit = 10.0f;
1458   NVGLineCap lineJoin = NVGLineCap.Miter;
1459   NVGLineCap lineCap = NVGLineCap.Butt;
1460   float alpha = 1.0f;
1461   NVGMatrix xform;
1462   NVGscissor scissor;
1463   float fontSize = 16.0f;
1464   float letterSpacing = 0.0f;
1465   float lineHeight = 1.0f;
1466   float fontBlur = 0.0f;
1467   NVGTextAlign textAlign;
1468   int fontId = 0;
1469   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1470   // dashing
1471   enum MaxDashes = 32; // max 16 dashes
1472   float[MaxDashes] dashes;
1473   uint dashCount = 0;
1474   uint lastFlattenDashCount = 0;
1475   float dashStart = 0;
1476   float totalDashLen;
1477   bool firstDashIsGap = false;
1478   // dasher state for flattener
1479   bool dasherActive = false;
1480 
1481   void clearPaint () nothrow @trusted @nogc {
1482     fill.clear();
1483     stroke.clear();
1484   }
1485 }
1486 
1487 struct NVGpoint {
1488   float x, y;
1489   float dx, dy;
1490   float len;
1491   float dmx, dmy;
1492   ubyte flags;
1493 }
1494 
1495 struct NVGpathCache {
1496   NVGpoint* points;
1497   int npoints;
1498   int cpoints;
1499   NVGpath* paths;
1500   int npaths;
1501   int cpaths;
1502   NVGVertex* verts;
1503   int nverts;
1504   int cverts;
1505   float[4] bounds;
1506   // this is required for saved paths
1507   bool strokeReady;
1508   bool fillReady;
1509   float strokeAlphaMul;
1510   float strokeWidth;
1511   float fringeWidth;
1512   bool evenOddMode;
1513   NVGClipMode clipmode;
1514   // non-saved path will not have this
1515   float* commands;
1516   int ncommands;
1517 
1518   @disable this (this); // no copies
1519   void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1520 
1521   // won't clear current path
1522   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1523     import core.stdc.stdlib : malloc;
1524     import core.stdc..string : memcpy, memset;
1525     assert(src !is null);
1526     memcpy(&this, src, NVGpathCache.sizeof);
1527     this.points = xdup(src.points, src.npoints);
1528     this.cpoints = src.npoints;
1529     this.verts = xdup(src.verts, src.nverts);
1530     this.cverts = src.nverts;
1531     this.commands = xdup(src.commands, src.ncommands);
1532     if (src.npaths > 0) {
1533       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1534       memset(this.paths, 0, npaths*NVGpath.sizeof);
1535       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1536       this.cpaths = src.npaths;
1537     } else {
1538       this.npaths = this.cpaths = 0;
1539     }
1540   }
1541 
1542   void clear () nothrow @trusted @nogc {
1543     import core.stdc.stdlib : free;
1544     import core.stdc..string : memset;
1545     if (paths !is null) {
1546       foreach (ref p; paths[0..npaths]) p.clear();
1547       free(paths);
1548     }
1549     if (points !is null) free(points);
1550     if (verts !is null) free(verts);
1551     if (commands !is null) free(commands);
1552     memset(&this, 0, this.sizeof);
1553   }
1554 }
1555 
1556 /// Pointer to opaque NanoVega context structure.
1557 /// Group: context_management
1558 public alias NVGContext = NVGcontextinternal*;
1559 
1560 /// FontStash context
1561 /// Group: font_stash
1562 public alias FONSContext = FONScontextInternal*;
1563 
1564 /// Returns FontStash context of the given NanoVega context.
1565 /// Group: font_stash
1566 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1567 
1568 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1569 /// Group: font_stash
1570 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1571   pragma(inline, true);
1572   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1573 }
1574 
1575 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1576 /// Returns `false` if FontStash or NanoVega context is not active.
1577 /// Group: font_stash
1578 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1579   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1580   NVGstate* state = nvg__getState(ctx);
1581   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1582   stash.size = state.fontSize*scale;
1583   stash.spacing = state.letterSpacing*scale;
1584   stash.blur = state.fontBlur*scale;
1585   stash.textAlign = state.textAlign;
1586   stash.fontId = state.fontId;
1587   return true;
1588 }
1589 
1590 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1591 /// Returns `false` if FontStash or NanoVega context is not active.
1592 /// Group: font_stash
1593 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1594   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1595   NVGstate* state = nvg__getState(ctx);
1596   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1597   state.fontSize = stash.size;
1598   state.letterSpacing = stash.spacing;
1599   state.fontBlur = stash.blur;
1600   state.textAlign = stash.textAlign;
1601   state.fontId = stash.fontId;
1602   return true;
1603 }
1604 
1605 /** Bezier curve rasterizer.
1606  *
1607  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1608  * It doesn't really matter in practice.
1609  *
1610  * AFD tesselator is somewhat slower, but does cusps better.
1611  *
1612  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1613  * never notice any visial difference (and this code is not really debugged), so you probably should
1614  * not use it. It is there for further experiments.
1615  */
1616 public enum NVGTesselation {
1617   DeCasteljau, /// default: standard well-known tesselation algorithm
1618   AFD, /// adaptive forward differencing
1619   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1620 }
1621 
1622 /// Default tesselator for Bezier curves.
1623 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1624 
1625 
1626 // some public info
1627 
1628 /// valid only inside [beginFrame]/[endFrame]
1629 /// Group: context_management
1630 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1631 
1632 /// valid only inside [beginFrame]/[endFrame]
1633 /// Group: context_management
1634 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1635 
1636 /// valid only inside [beginFrame]/[endFrame]
1637 /// Group: context_management
1638 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1639 
1640 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1641 /// Group: context_management
1642 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1643 
1644 // path autoregistration
1645 
1646 /// [pickid] to stop autoregistration.
1647 /// Group: context_management
1648 public enum NVGNoPick = -1;
1649 
1650 /// >=0: this pickid will be assigned to all filled/stroked paths
1651 /// Group: context_management
1652 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1653 
1654 /// >=0: this pickid will be assigned to all filled/stroked paths
1655 /// Group: context_management
1656 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1657 
1658 /// pick autoregistration mode; see [NVGPickKind]
1659 /// Group: context_management
1660 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1661 
1662 /// pick autoregistration mode; see [NVGPickKind]
1663 /// Group: context_management
1664 public void pickmode (NVGContext ctx, uint v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickRegistered = (ctx.pathPickRegistered&0xffff_0000u)|(v&NVGPickKind.All); }
1665 
1666 // tesselator options
1667 
1668 /// Get current Bezier tesselation mode. See [NVGTesselation].
1669 /// Group: context_management
1670 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1671 
1672 /// Set current Bezier tesselation mode. See [NVGTesselation].
1673 /// Group: context_management
1674 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1675 
1676 
1677 private struct NVGcontextinternal {
1678 private:
1679   NVGparams params;
1680   float* commands;
1681   int ccommands;
1682   int ncommands;
1683   float commandx, commandy;
1684   NVGstate[NVG_MAX_STATES] states;
1685   int nstates;
1686   NVGpathCache* cache;
1687   public float tessTol;
1688   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1689   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1690   float distTol;
1691   public float fringeWidth;
1692   float devicePxRatio;
1693   FONSContext fs;
1694   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1695   int fontImageIdx;
1696   int drawCallCount;
1697   int fillTriCount;
1698   int strokeTriCount;
1699   int textTriCount;
1700   NVGTesselation tesselatortype;
1701   // picking API
1702   NVGpickScene* pickScene;
1703   int pathPickId; // >=0: register all paths for picking using this id
1704   uint pathPickRegistered; // if [pathPickId] >= 0, this is used to avoid double-registration (see [NVGPickKind]); hi 16 bit is check flags, lo 16 bit is mode
1705   // path recording
1706   NVGPathSet recset;
1707   int recstart; // used to cancel recording
1708   bool recblockdraw;
1709   // internals
1710   NVGMatrix gpuAffine;
1711   int mWidth, mHeight;
1712   // image manager
1713   shared int imageCount; // number of alive images in this context
1714   bool contextAlive; // context can be dead, but still contain some images
1715 
1716   @disable this (this); // no copies
1717   void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1718 
1719   // debug feature
1720   public @property int getImageCount () nothrow @trusted @nogc {
1721     import core.atomic;
1722     return atomicLoad(imageCount);
1723   }
1724 }
1725 
1726 /** Returns number of tesselated pathes in context.
1727  *
1728  * Tesselated pathes are either triangle strips (for strokes), or
1729  * triangle fans (for fills). Note that NanoVega can generate some
1730  * surprising pathes (like fringe stroke for antialiasing, for example).
1731  *
1732  * One render path can contain vertices both for fill, and for stroke triangles.
1733  */
1734 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1735   pragma(inline, true);
1736   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1737 }
1738 
1739 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1740  *
1741  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1742  *           (except the calls to render path accessors), and using it in such
1743  *           case is UB. So copy vertices to freshly allocated array if you want
1744  *           to keep them for further processing.)
1745  */
1746 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1747   pragma(inline, true);
1748   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1749 }
1750 
1751 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1752  *
1753  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1754  *           (except the calls to render path accessors), and using it in such
1755  *           case is UB. So copy vertices to freshly allocated array if you want
1756  *           to keep them for further processing.)
1757  */
1758 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1759   pragma(inline, true);
1760   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1761 
1762 }
1763 
1764 /// Returns winding for the given render path.
1765 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1766   pragma(inline, true);
1767   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1768 
1769 }
1770 
1771 /// Returns "complex path" flag for the given render path.
1772 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1773   pragma(inline, true);
1774   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1775 
1776 }
1777 
1778 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1779   if (ctx !is null && imgid > 0) {
1780     import core.atomic : atomicOp;
1781     atomicOp!"+="(ctx.imageCount, 1);
1782     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1783     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1784   }
1785 }
1786 
1787 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1788   if (ctx !is null && imgid > 0) {
1789     import core.atomic : atomicOp;
1790     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1791     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1792     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1793     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1794     version(nanovega_debug_image_manager) if (!ctx.contextAlive) { import core.stdc.stdio; printf("image[--]ref: zombie context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1795     if (!ctx.contextAlive && icnt == 0) {
1796       // it is finally safe to free context memory
1797       import core.stdc.stdlib : free;
1798       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1799       free(ctx);
1800     }
1801   }
1802 }
1803 
1804 
1805 public import core.stdc.math :
1806   nvg__sqrtf = sqrtf,
1807   nvg__modf = fmodf,
1808   nvg__sinf = sinf,
1809   nvg__cosf = cosf,
1810   nvg__tanf = tanf,
1811   nvg__atan2f = atan2f,
1812   nvg__acosf = acosf,
1813   nvg__ceilf = ceilf;
1814 
1815 version(Windows) {
1816   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1817 } else {
1818   public import core.stdc.math : nvg__lrintf = lrintf;
1819 }
1820 
1821 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1822 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1823 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1824 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1825 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1826 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1827 
1828 //public import core.stdc.math : nvg__absf = fabsf;
1829 public import core.math : nvg__absf = fabs;
1830 
1831 
1832 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1833   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1834   if (d > 1e-6f) {
1835     immutable float id = 1.0f/d;
1836     *x *= id;
1837     *y *= id;
1838   }
1839   return d;
1840 }
1841 
1842 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1843   if (c !is null) {
1844     c.clear();
1845     free(c);
1846   }
1847 }
1848 
1849 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1850   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1851   if (c is null) goto error;
1852   memset(c, 0, NVGpathCache.sizeof);
1853 
1854   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1855   if (c.points is null) goto error;
1856   assert(c.npoints == 0);
1857   c.cpoints = NVG_INIT_POINTS_SIZE;
1858 
1859   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1860   if (c.paths is null) goto error;
1861   assert(c.npaths == 0);
1862   c.cpaths = NVG_INIT_PATHS_SIZE;
1863 
1864   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1865   if (c.verts is null) goto error;
1866   assert(c.nverts == 0);
1867   c.cverts = NVG_INIT_VERTS_SIZE;
1868 
1869   return c;
1870 
1871 error:
1872   nvg__deletePathCache(c);
1873   return null;
1874 }
1875 
1876 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1877   ctx.tessTol = 0.25f/ratio;
1878   ctx.distTol = 0.01f/ratio;
1879   ctx.fringeWidth = 1.0f/ratio;
1880   ctx.devicePxRatio = ratio;
1881 }
1882 
1883 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1884   NVGCompositeOperationState state;
1885   NVGBlendFactor sfactor, dfactor;
1886 
1887        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1888   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1889   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1890   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1891   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1892   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1893   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1894   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1895   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1896   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1897   else if (op == NVGCompositeOperation.Xor) {
1898     state.simple = false;
1899     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1900     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1901     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1902     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1903     return state;
1904   }
1905   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1906 
1907   state.simple = true;
1908   state.srcAlpha = sfactor;
1909   state.dstAlpha = dfactor;
1910   return state;
1911 }
1912 
1913 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1914   pragma(inline, true);
1915   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1916   return &ctx.states.ptr[ctx.nstates-1];
1917 }
1918 
1919 // Constructor called by the render back-end.
1920 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1921   FONSParams fontParams;
1922   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1923   if (ctx is null) goto error;
1924   memset(ctx, 0, NVGcontextinternal.sizeof);
1925 
1926   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1927   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1928 
1929   ctx.contextAlive = true;
1930 
1931   ctx.params = *params;
1932   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1933 
1934   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1935   if (ctx.commands is null) goto error;
1936   ctx.ncommands = 0;
1937   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1938 
1939   ctx.cache = nvg__allocPathCache();
1940   if (ctx.cache is null) goto error;
1941 
1942   ctx.save();
1943   ctx.reset();
1944 
1945   nvg__setDevicePixelRatio(ctx, 1.0f);
1946   ctx.mWidth = ctx.mHeight = 0;
1947 
1948   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1949 
1950   // init font rendering
1951   memset(&fontParams, 0, fontParams.sizeof);
1952   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1953   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1954   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1955   fontParams.renderCreate = null;
1956   fontParams.renderUpdate = null;
1957   fontParams.renderDelete = null;
1958   fontParams.userPtr = null;
1959   ctx.fs = FONSContext.create(fontParams);
1960   if (ctx.fs is null) goto error;
1961 
1962   // create font texture
1963   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
1964   if (ctx.fontImages[0].id == 0) goto error;
1965   ctx.fontImages[0].ctx = ctx;
1966   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
1967   ctx.fontImageIdx = 0;
1968 
1969   ctx.pathPickId = -1;
1970   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
1971 
1972   return ctx;
1973 
1974 error:
1975   ctx.deleteInternal();
1976   return null;
1977 }
1978 
1979 // Called by render backend.
1980 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
1981   return &ctx.params;
1982 }
1983 
1984 // Destructor called by the render back-end.
1985 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
1986   if (ctx is null) return;
1987   if (ctx.contextAlive) {
1988     if (ctx.commands !is null) free(ctx.commands);
1989     nvg__deletePathCache(ctx.cache);
1990 
1991     if (ctx.fs) ctx.fs.kill();
1992 
1993     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
1994 
1995     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
1996 
1997     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
1998 
1999     ctx.contextAlive = false;
2000 
2001     import core.atomic : atomicLoad;
2002     if (atomicLoad(ctx.imageCount) == 0) {
2003       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2004       free(ctx);
2005     } else {
2006       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2007     }
2008   }
2009 }
2010 
2011 /// Delete NanoVega context.
2012 /// Group: context_management
2013 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2014   if (ctx !is null) {
2015     ctx.deleteInternal();
2016     ctx = null;
2017   }
2018 }
2019 
2020 /// Returns `true` if the given context is not `null` and can be used for painting.
2021 /// Group: context_management
2022 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2023 
2024 
2025 // ////////////////////////////////////////////////////////////////////////// //
2026 // Frame Management
2027 
2028 /** Begin drawing a new frame.
2029  *
2030  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2031  *
2032  * [beginFrame] defines the size of the window to render to in relation currently
2033  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2034  * control the rendering on Hi-DPI devices.
2035  *
2036  * For example, GLFW returns two dimension for an opened window: window size and
2037  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2038  * devicePixelRatio to: `windowWidth/windowHeight`.
2039  *
2040  * Default ratio is `1`.
2041  *
2042  * Note that fractional ratio can (and will) distort your fonts and images.
2043  *
2044  * This call also resets pick marks (see picking API for non-rasterized paths),
2045  * path recording, and GPU affine transformatin matrix.
2046  *
2047  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2048  *
2049  * Group: frame_management
2050  */
2051 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2052   import std.math : isNaN;
2053   /*
2054   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2055          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2056          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2057   */
2058   if (ctx.nstates > 0) ctx.cancelFrame();
2059 
2060   if (windowWidth < 1) windowWidth = 1;
2061   if (windowHeight < 1) windowHeight = 1;
2062 
2063   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2064 
2065   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2066   ctx.nstates = 0;
2067   ctx.save();
2068   ctx.reset();
2069 
2070   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2071 
2072   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2073   ctx.mWidth = windowWidth;
2074   ctx.mHeight = windowHeight;
2075 
2076   ctx.recset = null;
2077   ctx.recstart = -1;
2078 
2079   ctx.pathPickId = NVGNoPick;
2080   ctx.pathPickRegistered = 0;
2081 
2082   ctx.drawCallCount = 0;
2083   ctx.fillTriCount = 0;
2084   ctx.strokeTriCount = 0;
2085   ctx.textTriCount = 0;
2086 
2087   ctx.ncommands = 0;
2088   ctx.pathPickRegistered = 0;
2089   nvg__clearPathCache(ctx);
2090 
2091   ctx.gpuAffine = NVGMatrix.Identity;
2092 
2093   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2094 }
2095 
2096 /// Cancels drawing the current frame. Cancels path recording.
2097 /// Group: frame_management
2098 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2099   ctx.cancelRecording();
2100   //ctx.mWidth = 0;
2101   //ctx.mHeight = 0;
2102   // cancel render queue
2103   ctx.params.renderCancel(ctx.params.userPtr);
2104   // clear saved states (this may free some textures)
2105   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2106   ctx.nstates = 0;
2107 }
2108 
2109 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2110 /// Group: frame_management
2111 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2112   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2113   ctx.stopRecording();
2114   //ctx.mWidth = 0;
2115   //ctx.mHeight = 0;
2116   // flush render queue
2117   NVGstate* state = nvg__getState(ctx);
2118   ctx.params.renderFlush(ctx.params.userPtr);
2119   if (ctx.fontImageIdx != 0) {
2120     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2121     int j = 0, iw, ih;
2122     // delete images that smaller than current one
2123     if (!fontImage.valid) return;
2124     ctx.imageSize(fontImage, iw, ih);
2125     foreach (int i; 0..ctx.fontImageIdx) {
2126       if (ctx.fontImages[i].valid) {
2127         int nw, nh;
2128         ctx.imageSize(ctx.fontImages[i], nw, nh);
2129         if (nw < iw || nh < ih) {
2130           ctx.deleteImage(ctx.fontImages[i]);
2131         } else {
2132           ctx.fontImages[j++] = ctx.fontImages[i];
2133         }
2134       }
2135     }
2136     // make current font image to first
2137     ctx.fontImages[j++] = ctx.fontImages[0];
2138     ctx.fontImages[0] = fontImage;
2139     ctx.fontImageIdx = 0;
2140     // clear all images after j
2141     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2142   }
2143   // clear saved states (this may free some textures)
2144   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2145   ctx.nstates = 0;
2146 }
2147 
2148 
2149 // ////////////////////////////////////////////////////////////////////////// //
2150 // Recording and Replaying Pathes
2151 
2152 // Saved path set.
2153 // Group: path_recording
2154 public alias NVGPathSet = NVGPathSetS*;
2155 
2156 
2157 //TODO: save scissor info?
2158 struct NVGPathSetS {
2159 private:
2160   // either path cache, or text item
2161   static struct Node {
2162     NVGPaint paint;
2163     NVGpathCache* path;
2164   }
2165 
2166 private:
2167   Node* nodes;
2168   int nnodes, cnodes;
2169   NVGpickScene* pickscene;
2170   //int npickscenes, cpickscenes;
2171   NVGContext svctx; // used to do some sanity checks, and to free resources
2172 
2173 private:
2174   Node* allocNode () nothrow @trusted @nogc {
2175     import core.stdc..string : memset;
2176     // grow buffer if necessary
2177     if (nnodes+1 > cnodes) {
2178       import core.stdc.stdlib : realloc;
2179       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2180       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2181       if (nodes is null) assert(0, "NanoVega: out of memory");
2182       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2183       cnodes = newsz;
2184     }
2185     assert(nnodes < cnodes);
2186     memset(nodes+nnodes, 0, Node.sizeof);
2187     return &nodes[nnodes++];
2188   }
2189 
2190   Node* allocPathNode () nothrow @trusted @nogc {
2191     import core.stdc.stdlib : malloc;
2192     import core.stdc..string : memset;
2193     auto node = allocNode();
2194     // allocate path cache
2195     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2196     if (pc is null) assert(0, "NanoVega: out of memory");
2197     node.path = pc;
2198     return node;
2199   }
2200 
2201   void clearNode (int idx) nothrow @trusted @nogc {
2202     if (idx < 0 || idx >= nnodes) return;
2203     Node* node = &nodes[idx];
2204     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2205     if (node.path !is null) node.path.clear();
2206   }
2207 
2208 private:
2209   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2210     NVGpickScene* ps = ctx.pickScene;
2211     if (ps is null) return; // nothing to do
2212     if (ps.npaths == 0) return; // pick scene is empty
2213     ctx.pickScene = null;
2214     pickscene = ps;
2215   }
2216 
2217   void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc {
2218     NVGstate* state = nvg__getState(ctx);
2219     foreach (ref node; nodes[0..nnodes]) {
2220       if (auto cc = node.path) {
2221         if (cc.npaths <= 0) continue;
2222 
2223         if (cc.fillReady) {
2224           NVGPaint fillPaint = node.paint;
2225 
2226           // apply global alpha
2227           fillPaint.innerColor.a *= state.alpha;
2228           fillPaint.middleColor.a *= state.alpha;
2229           fillPaint.outerColor.a *= state.alpha;
2230 
2231           fillPaint.innerColor.applyTint(fillTint);
2232           fillPaint.middleColor.applyTint(fillTint);
2233           fillPaint.outerColor.applyTint(fillTint);
2234 
2235           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2236 
2237           // count triangles
2238           foreach (int i; 0..cc.npaths) {
2239             NVGpath* path = &cc.paths[i];
2240             ctx.fillTriCount += path.nfill-2;
2241             ctx.fillTriCount += path.nstroke-2;
2242             ctx.drawCallCount += 2;
2243           }
2244         }
2245 
2246         if (cc.strokeReady) {
2247           NVGPaint strokePaint = node.paint;
2248 
2249           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2250           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2251           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2252 
2253           // apply global alpha
2254           strokePaint.innerColor.a *= state.alpha;
2255           strokePaint.middleColor.a *= state.alpha;
2256           strokePaint.outerColor.a *= state.alpha;
2257 
2258           strokePaint.innerColor.applyTint(strokeTint);
2259           strokePaint.middleColor.applyTint(strokeTint);
2260           strokePaint.outerColor.applyTint(strokeTint);
2261 
2262           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2263 
2264           // count triangles
2265           foreach (int i; 0..cc.npaths) {
2266             NVGpath* path = &cc.paths[i];
2267             ctx.strokeTriCount += path.nstroke-2;
2268             ++ctx.drawCallCount;
2269           }
2270         }
2271       }
2272     }
2273   }
2274 
2275 public:
2276   @disable this (this); // no copies
2277   void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2278 
2279   // pick test
2280   // Call delegate [dg] for each path under the specified position (in no particular order).
2281   // Returns the id of the path for which delegate [dg] returned true or -1.
2282   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2283   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2284     if (pickscene is null) return -1;
2285 
2286     NVGpickScene* ps = pickscene;
2287     int levelwidth = 1<<(ps.nlevels-1);
2288     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2289     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2290     int npicked = 0;
2291 
2292     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2293       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2294       while (pp !is null) {
2295         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2296           int hit = 0;
2297           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2298           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2299           if (hit) {
2300             static if (IsGoodHitTestDG!DG) {
2301               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2302                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2303               } else {
2304                 dg(pp.id, cast(int)pp.order);
2305               }
2306             } else {
2307               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2308                 if (dg(pp)) return pp.id;
2309               } else {
2310                 dg(pp);
2311               }
2312             }
2313           }
2314         }
2315         pp = pp.next;
2316       }
2317       cellx >>= 1;
2318       celly >>= 1;
2319       levelwidth >>= 1;
2320     }
2321 
2322     return -1;
2323   }
2324 
2325   // Fills ids with a list of the top most hit ids under the specified position.
2326   // Returns the slice of [ids].
2327   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2328     if (pickscene is null || ids.length == 0) return ids[0..0];
2329 
2330     int npicked = 0;
2331     NVGpickScene* ps = pickscene;
2332 
2333     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2334       if (npicked == ps.cpicked) {
2335         int cpicked = ps.cpicked+ps.cpicked;
2336         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2337         if (picked is null) return true; // abort
2338         ps.cpicked = cpicked;
2339         ps.picked = picked;
2340       }
2341       ps.picked[npicked] = pp;
2342       ++npicked;
2343       return false; // go on
2344     });
2345 
2346     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2347 
2348     assert(npicked >= 0);
2349     if (npicked > ids.length) npicked = cast(int)ids.length;
2350     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2351 
2352     return ids[0..npicked];
2353   }
2354 
2355   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2356   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2357     if (pickscene is null) return -1;
2358 
2359     int bestOrder = -1;
2360     int bestID = -1;
2361 
2362     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2363       if (pp.order > bestOrder) {
2364         bestOrder = pp.order;
2365         bestID = pp.id;
2366       }
2367     });
2368 
2369     return bestID;
2370   }
2371 }
2372 
2373 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2374 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc {
2375   if (ctx is null || svp is null) return;
2376   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2377   if (ctx.ncommands == 0) {
2378     assert(ctx.cache.npaths == 0);
2379     return;
2380   }
2381   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2382 
2383   // tesselate current path
2384   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2385   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2386 
2387   auto node = svp.allocPathNode();
2388   NVGpathCache* cc = node.path;
2389   cc.copyFrom(ctx.cache);
2390   node.paint = paint;
2391   // copy path commands (we may need 'em for picking)
2392   version(all) {
2393     cc.ncommands = ctx.ncommands;
2394     if (cc.ncommands) {
2395       import core.stdc.stdlib : malloc;
2396       import core.stdc..string : memcpy;
2397       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2398       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2399       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2400     } else {
2401       cc.commands = null;
2402     }
2403   }
2404 }
2405 
2406 // Create new empty path set.
2407 // Group: path_recording
2408 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2409   import core.stdc.stdlib : malloc;
2410   import core.stdc..string : memset;
2411   if (ctx is null) return null;
2412   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2413   if (res is null) assert(0, "NanoVega: out of memory");
2414   memset(res, 0, NVGPathSetS.sizeof);
2415   res.svctx = ctx;
2416   return res;
2417 }
2418 
2419 // Is the given path set empty? Empty path set can be `null`.
2420 // Group: path_recording
2421 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2422 
2423 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2424 // Group: path_recording
2425 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2426   if (svp !is null) {
2427     import core.stdc.stdlib : free;
2428     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2429     svp.nnodes = 0;
2430   }
2431 }
2432 
2433 // Destroy path set (frees all allocated memory).
2434 // Group: path_recording
2435 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2436   if (svp !is null) {
2437     import core.stdc.stdlib : free;
2438     svp.clear();
2439     if (svp.nodes !is null) free(svp.nodes);
2440     free(svp);
2441     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2442     svp = null;
2443   }
2444 }
2445 
2446 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2447 // Group: path_recording
2448 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2449   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2450   ctx.stopRecording();
2451   ctx.recset = svp;
2452   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2453   ctx.recblockdraw = false;
2454 }
2455 
2456 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2457  *
2458  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2459  * Commiting or cancelling will re-enable rendering.
2460  * You can call this with `null` svp to block rendering without recording any paths.
2461  *
2462  * Group: path_recording
2463  */
2464 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2465   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2466   ctx.stopRecording();
2467   ctx.recset = svp;
2468   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2469   ctx.recblockdraw = true;
2470 }
2471 
2472 // Commit recorded paths. It is safe to call this when recording is not started.
2473 // Group: path_recording
2474 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2475   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2476   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2477   ctx.recset = null;
2478   ctx.recstart = -1;
2479   ctx.recblockdraw = false;
2480 }
2481 
2482 // Cancel path recording.
2483 // Group: path_recording
2484 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2485   if (ctx.recset !is null) {
2486     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2487     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2488     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2489     ctx.recset.nnodes = ctx.recstart;
2490     ctx.recset = null;
2491     ctx.recstart = -1;
2492   }
2493   ctx.recblockdraw = false;
2494 }
2495 
2496 /* Replay saved path set.
2497  *
2498  * Replaying record while you're recording another one is undefined behavior.
2499  *
2500  * Group: path_recording
2501  */
2502 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2503   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2504   svp.replay(ctx, fillTint, strokeTint);
2505 }
2506 
2507 /// Ditto.
2508 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2509 
2510 /// Ditto.
2511 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2512 
2513 
2514 // ////////////////////////////////////////////////////////////////////////// //
2515 // Composite operation
2516 
2517 /// Sets the composite operation.
2518 /// Group: composite_operation
2519 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2520   NVGstate* state = nvg__getState(ctx);
2521   state.compositeOperation = nvg__compositeOperationState(op);
2522 }
2523 
2524 /// Sets the composite operation with custom pixel arithmetic.
2525 /// Group: composite_operation
2526 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2527   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2528 }
2529 
2530 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2531 /// Group: composite_operation
2532 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2533   NVGCompositeOperationState op;
2534   op.simple = false;
2535   op.srcRGB = srcRGB;
2536   op.dstRGB = dstRGB;
2537   op.srcAlpha = srcAlpha;
2538   op.dstAlpha = dstAlpha;
2539   NVGstate* state = nvg__getState(ctx);
2540   state.compositeOperation = op;
2541 }
2542 
2543 
2544 // ////////////////////////////////////////////////////////////////////////// //
2545 // Color utils
2546 
2547 /// Returns a color value from string form.
2548 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2549 /// Group: color_utils
2550 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2551 
2552 /// Ditto.
2553 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2554 
2555 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2556 /// Group: color_utils
2557 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2558 
2559 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2560 /// Group: color_utils
2561 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2562 
2563 /// Returns a color value from red, green, blue and alpha values.
2564 /// Group: color_utils
2565 public NVGColor nvgRGBA (int r, int g, int b, int a=255) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), nvgClampToByte(a)); }
2566 
2567 /// Returns a color value from red, green, blue and alpha values.
2568 /// Group: color_utils
2569 public NVGColor nvgRGBAf (float r, float g, float b, float a=1.0f) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, a); }
2570 
2571 /// Returns new color with transparency (alpha) set to [a].
2572 /// Group: color_utils
2573 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2574   pragma(inline, true);
2575   c.a = a/255.0f;
2576   return c;
2577 }
2578 
2579 /// Ditto.
2580 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2581   pragma(inline, true);
2582   c.a = a;
2583   return c;
2584 }
2585 
2586 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2587 /// Group: color_utils
2588 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2589   NVGColor cint = void;
2590   u = nvg__clamp(u, 0.0f, 1.0f);
2591   float oneminu = 1.0f-u;
2592   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2593   return cint;
2594 }
2595 
2596 /* see below
2597 public NVGColor nvgHSL() (float h, float s, float l) {
2598   //pragma(inline, true); // alas
2599   return nvgHSLA(h, s, l, 255);
2600 }
2601 */
2602 
2603 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2604   if (h < 0) h += 1;
2605   if (h > 1) h -= 1;
2606   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2607   if (h < 3.0f/6.0f) return m2;
2608   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2609   return m1;
2610 }
2611 
2612 /// Returns color value specified by hue, saturation and lightness.
2613 /// HSL values are all in range [0..1], alpha will be set to 255.
2614 /// Group: color_utils
2615 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2616 
2617 /// Returns color value specified by hue, saturation and lightness and alpha.
2618 /// HSL values are all in range [0..1], alpha in range [0..255].
2619 /// Group: color_utils
2620 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2621   pragma(inline, true);
2622   NVGColor col = void;
2623   h = nvg__modf(h, 1.0f);
2624   if (h < 0.0f) h += 1.0f;
2625   s = nvg__clamp(s, 0.0f, 1.0f);
2626   l = nvg__clamp(l, 0.0f, 1.0f);
2627   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2628   immutable float m1 = 2*l-m2;
2629   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2630   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2631   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2632   col.a = a/255.0f;
2633   return col;
2634 }
2635 
2636 /// Returns color value specified by hue, saturation and lightness and alpha.
2637 /// HSL values and alpha are all in range [0..1].
2638 /// Group: color_utils
2639 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2640   // sorry for copypasta, it is for inliner
2641   static if (__VERSION__ >= 2072) pragma(inline, true);
2642   NVGColor col = void;
2643   h = nvg__modf(h, 1.0f);
2644   if (h < 0.0f) h += 1.0f;
2645   s = nvg__clamp(s, 0.0f, 1.0f);
2646   l = nvg__clamp(l, 0.0f, 1.0f);
2647   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2648   immutable m1 = 2*l-m2;
2649   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2650   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2651   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2652   col.a = a;
2653   return col;
2654 }
2655 
2656 
2657 // ////////////////////////////////////////////////////////////////////////// //
2658 // Matrices and Transformations
2659 
2660 /** Matrix class.
2661  *
2662  * Group: matrices
2663  */
2664 public align(1) struct NVGMatrix {
2665 align(1):
2666 private:
2667   static immutable float[6] IdentityMat = [
2668     1.0f, 0.0f,
2669     0.0f, 1.0f,
2670     0.0f, 0.0f,
2671   ];
2672 
2673 public:
2674   /// Matrix values. Initial value is identity matrix.
2675   float[6] mat = [
2676     1.0f, 0.0f,
2677     0.0f, 1.0f,
2678     0.0f, 0.0f,
2679   ];
2680 
2681 public nothrow @trusted @nogc:
2682   /// Create Matrix with the given values.
2683   this (const(float)[] amat...) {
2684     pragma(inline, true);
2685     if (amat.length >= 6) {
2686       mat.ptr[0..6] = amat.ptr[0..6];
2687     } else {
2688       mat.ptr[0..6] = 0;
2689       mat.ptr[0..amat.length] = amat[];
2690     }
2691   }
2692 
2693   /// Can be used to check validity of [inverted] result
2694   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2695 
2696   /// Returns `true` if this matrix is identity matrix.
2697   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2698 
2699   /// Returns new inverse matrix.
2700   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2701   NVGMatrix inverted () const {
2702     NVGMatrix res = this;
2703     res.invert;
2704     return res;
2705   }
2706 
2707   /// Inverts this matrix.
2708   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2709   ref NVGMatrix invert () return {
2710     float[6] inv = void;
2711     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2712     if (det > -1e-6 && det < 1e-6) {
2713       inv[] = float.nan;
2714     } else {
2715       immutable double invdet = 1.0/det;
2716       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2717       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2718       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2719       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2720       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2721       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2722     }
2723     mat.ptr[0..6] = inv.ptr[0..6];
2724     return this;
2725   }
2726 
2727   /// Sets this matrix to identity matrix.
2728   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2729 
2730   /// Translate this matrix.
2731   ref NVGMatrix translate (in float tx, in float ty) return {
2732     version(aliced) pragma(inline, true);
2733     return this.mul(Translated(tx, ty));
2734   }
2735 
2736   /// Scale this matrix.
2737   ref NVGMatrix scale (in float sx, in float sy) return {
2738     version(aliced) pragma(inline, true);
2739     return this.mul(Scaled(sx, sy));
2740   }
2741 
2742   /// Rotate this matrix.
2743   ref NVGMatrix rotate (in float a) return {
2744     version(aliced) pragma(inline, true);
2745     return this.mul(Rotated(a));
2746   }
2747 
2748   /// Skew this matrix by X axis.
2749   ref NVGMatrix skewX (in float a) return {
2750     version(aliced) pragma(inline, true);
2751     return this.mul(SkewedX(a));
2752   }
2753 
2754   /// Skew this matrix by Y axis.
2755   ref NVGMatrix skewY (in float a) return {
2756     version(aliced) pragma(inline, true);
2757     return this.mul(SkewedY(a));
2758   }
2759 
2760   /// Skew this matrix by both axes.
2761   ref NVGMatrix skewY (in float ax, in float ay) return {
2762     version(aliced) pragma(inline, true);
2763     return this.mul(SkewedXY(ax, ay));
2764   }
2765 
2766   /// Transform point with this matrix. `null` destinations are allowed.
2767   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2768   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2769     version(aliced) pragma(inline, true);
2770     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2771     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2772   }
2773 
2774   /// Transform point with this matrix.
2775   void point (ref float x, ref float y) nothrow @trusted @nogc {
2776     version(aliced) pragma(inline, true);
2777     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2778     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2779     x = nx;
2780     y = ny;
2781   }
2782 
2783   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2784   ref NVGMatrix mul() (in auto ref NVGMatrix s) {
2785     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2786     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2787     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2788     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2789     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2790     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2791     mat.ptr[0] = t0;
2792     mat.ptr[2] = t2;
2793     mat.ptr[4] = t4;
2794     return this;
2795   }
2796 
2797   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2798   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2799   /// Group: matrices
2800   ref NVGMatrix premul() (in auto ref NVGMatrix s) {
2801     NVGMatrix s2 = s;
2802     s2.mul(this);
2803     mat[] = s2.mat[];
2804     return this;
2805   }
2806 
2807   /// Multiply this matrix by [s], return result as new matrix.
2808   /// Performs operations in this left-to-right order.
2809   NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const {
2810     version(aliced) pragma(inline, true);
2811     NVGMatrix res = this;
2812     res.mul(s);
2813     return res;
2814   }
2815 
2816   /// Multiply this matrix by [s].
2817   /// Performs operations in this left-to-right order.
2818   ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) {
2819     version(aliced) pragma(inline, true);
2820     return this.mul(s);
2821   }
2822 
2823   float scaleX () const { pragma(inline, true); return nvg__sqrtf(mat.ptr[0]*mat.ptr[0]+mat.ptr[2]*mat.ptr[2]); } /// Returns x scaling of this matrix.
2824   float scaleY () const { pragma(inline, true); return nvg__sqrtf(mat.ptr[1]*mat.ptr[1]+mat.ptr[3]*mat.ptr[3]); } /// Returns y scaling of this matrix.
2825   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2826   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2827   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2828 
2829   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2830   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2831   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2832   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2833   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2834 
2835   /// Utility function to be used in `setXXX()`.
2836   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2837   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2838     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2839     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2840     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2841     mat.ptr[4] = tx; mat.ptr[5] = ty;
2842     return this;
2843   }
2844 
2845   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2846   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2847     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2848     mat.ptr[0] = cs; mat.ptr[1] = sn;
2849     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2850     mat.ptr[4] = tx; mat.ptr[5] = ty;
2851     return this;
2852   }
2853 
2854   /// Returns new identity matrix.
2855   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2856 
2857   /// Returns new translation matrix.
2858   static NVGMatrix Translated (in float tx, in float ty) {
2859     version(aliced) pragma(inline, true);
2860     NVGMatrix res = void;
2861     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2862     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2863     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2864     return res;
2865   }
2866 
2867   /// Returns new scaling matrix.
2868   static NVGMatrix Scaled (in float sx, in float sy) {
2869     version(aliced) pragma(inline, true);
2870     NVGMatrix res = void;
2871     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2872     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2873     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2874     return res;
2875   }
2876 
2877   /// Returns new rotation matrix. Angle is specified in radians.
2878   static NVGMatrix Rotated (in float a) {
2879     version(aliced) pragma(inline, true);
2880     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2881     NVGMatrix res = void;
2882     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2883     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2884     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2885     return res;
2886   }
2887 
2888   /// Returns new x-skewing matrix. Angle is specified in radians.
2889   static NVGMatrix SkewedX (in float a) {
2890     version(aliced) pragma(inline, true);
2891     NVGMatrix res = void;
2892     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2893     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2894     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2895     return res;
2896   }
2897 
2898   /// Returns new y-skewing matrix. Angle is specified in radians.
2899   static NVGMatrix SkewedY (in float a) {
2900     version(aliced) pragma(inline, true);
2901     NVGMatrix res = void;
2902     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2903     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2904     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2905     return res;
2906   }
2907 
2908   /// Returns new xy-skewing matrix. Angles are specified in radians.
2909   static NVGMatrix SkewedXY (in float ax, in float ay) {
2910     version(aliced) pragma(inline, true);
2911     NVGMatrix res = void;
2912     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2913     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2914     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2915     return res;
2916   }
2917 
2918   /// Utility function to be used in `setXXX()`.
2919   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2920   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2921     NVGMatrix res = void;
2922     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2923     return res;
2924   }
2925 
2926   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2927   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2928     NVGMatrix res = void;
2929     res.rotateTransform(a, tx, ty);
2930     return res;
2931   }
2932 }
2933 
2934 
2935 /// Converts degrees to radians.
2936 /// Group: matrices
2937 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2938 
2939 /// Converts radians to degrees.
2940 /// Group: matrices
2941 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2942 
2943 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2944 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2945 
2946 
2947 // ////////////////////////////////////////////////////////////////////////// //
2948 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc {
2949   p.clear();
2950   p.xform.identity;
2951   p.radius = 0.0f;
2952   p.feather = 1.0f;
2953   p.innerColor = p.middleColor = p.outerColor = color;
2954   p.midp = -1;
2955   p.simpleColor = true;
2956 }
2957 
2958 
2959 // ////////////////////////////////////////////////////////////////////////// //
2960 // State handling
2961 
2962 version(nanovega_debug_clipping) {
2963   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
2964   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
2965 }
2966 
2967 /** Pushes and saves the current render state into a state stack.
2968  * A matching [restore] must be used to restore the state.
2969  * Returns `false` if state stack overflowed.
2970  *
2971  * Group: state_handling
2972  */
2973 public bool save (NVGContext ctx) nothrow @trusted @nogc {
2974   if (ctx.nstates >= NVG_MAX_STATES) return false;
2975   if (ctx.nstates > 0) {
2976     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
2977     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
2978     ctx.params.renderPushClip(ctx.params.userPtr);
2979   }
2980   ++ctx.nstates;
2981   return true;
2982 }
2983 
2984 /// Pops and restores current render state.
2985 /// Group: state_handling
2986 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
2987   if (ctx.nstates <= 1) return false;
2988   ctx.states[ctx.nstates-1].clearPaint();
2989   ctx.params.renderPopClip(ctx.params.userPtr);
2990   --ctx.nstates;
2991   return true;
2992 }
2993 
2994 /// Resets current render state to default values. Does not affect the render state stack.
2995 /// Group: state_handling
2996 public void reset (NVGContext ctx) nothrow @trusted @nogc {
2997   NVGstate* state = nvg__getState(ctx);
2998   state.clearPaint();
2999 
3000   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3001   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3002   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3003   state.shapeAntiAlias = true;
3004   state.strokeWidth = 1.0f;
3005   state.miterLimit = 10.0f;
3006   state.lineCap = NVGLineCap.Butt;
3007   state.lineJoin = NVGLineCap.Miter;
3008   state.alpha = 1.0f;
3009   state.xform.identity;
3010 
3011   state.scissor.extent[] = -1.0f;
3012 
3013   state.fontSize = 16.0f;
3014   state.letterSpacing = 0.0f;
3015   state.lineHeight = 1.0f;
3016   state.fontBlur = 0.0f;
3017   state.textAlign.reset;
3018   state.fontId = 0;
3019   state.evenOddMode = false;
3020   state.dashCount = 0;
3021   state.lastFlattenDashCount = 0;
3022   state.dashStart = 0;
3023   state.firstDashIsGap = false;
3024   state.dasherActive = false;
3025 
3026   ctx.params.renderResetClip(ctx.params.userPtr);
3027 }
3028 
3029 /** Returns `true` if we have any room in state stack.
3030  * It is guaranteed to have at least 32 stack slots.
3031  *
3032  * Group: state_handling
3033  */
3034 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3035 
3036 /** Returns `true` if we have any saved state.
3037  *
3038  * Group: state_handling
3039  */
3040 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3041 
3042 /// Returns `true` if rendering is currently blocked.
3043 /// Group: state_handling
3044 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3045 
3046 /// Blocks/unblocks rendering
3047 /// Group: state_handling
3048 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3049 
3050 /// Blocks/unblocks rendering; returns previous state.
3051 /// Group: state_handling
3052 public bool setRenderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) { bool res = ctx.recblockdraw; ctx.recblockdraw = v; return res; } else return false; }
3053 
3054 
3055 // ////////////////////////////////////////////////////////////////////////// //
3056 // Render styles
3057 
3058 /// Sets filling mode to "even-odd".
3059 /// Group: render_styles
3060 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3061   NVGstate* state = nvg__getState(ctx);
3062   state.evenOddMode = true;
3063 }
3064 
3065 /// Sets filling mode to "non-zero" (this is default mode).
3066 /// Group: render_styles
3067 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3068   NVGstate* state = nvg__getState(ctx);
3069   state.evenOddMode = false;
3070 }
3071 
3072 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3073 /// Group: render_styles
3074 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3075   NVGstate* state = nvg__getState(ctx);
3076   state.shapeAntiAlias = enabled;
3077 }
3078 
3079 /// Sets the stroke width of the stroke style.
3080 /// Group: render_styles
3081 @scriptable
3082 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3083   NVGstate* state = nvg__getState(ctx);
3084   state.strokeWidth = width;
3085 }
3086 
3087 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3088 /// Group: render_styles
3089 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3090   NVGstate* state = nvg__getState(ctx);
3091   state.miterLimit = limit;
3092 }
3093 
3094 /// Sets how the end of the line (cap) is drawn,
3095 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3096 /// Group: render_styles
3097 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3098   NVGstate* state = nvg__getState(ctx);
3099   state.lineCap = cap;
3100 }
3101 
3102 /// Sets how sharp path corners are drawn.
3103 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3104 /// Group: render_styles
3105 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3106   NVGstate* state = nvg__getState(ctx);
3107   state.lineJoin = join;
3108 }
3109 
3110 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3111 /// Current limit is 16 pairs.
3112 /// Resets dash start to zero.
3113 /// Group: render_styles
3114 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3115   NVGstate* state = nvg__getState(ctx);
3116   state.dashCount = 0;
3117   state.dashStart = 0;
3118   state.firstDashIsGap = false;
3119   if (dashdata.length >= 2) {
3120     bool curFIsGap = true; // trick
3121     foreach (immutable idx, float f; dashdata) {
3122       curFIsGap = !curFIsGap;
3123       if (f < 0.01f) continue; // skip it
3124       if (idx == 0) {
3125         // register first dash
3126         state.firstDashIsGap = curFIsGap;
3127         state.dashes.ptr[state.dashCount++] = f;
3128       } else {
3129         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3130           // oops, continuation
3131           state.dashes[state.dashCount-1] += f;
3132         } else {
3133           if (state.dashCount == state.dashes.length) break;
3134           state.dashes[state.dashCount++] = f;
3135         }
3136       }
3137     }
3138     if (state.dashCount&1) {
3139       if (state.dashCount == 1) {
3140         state.dashCount = 0;
3141       } else {
3142         assert(state.dashCount < state.dashes.length);
3143         state.dashes[state.dashCount++] = 0;
3144       }
3145     }
3146     // calculate total dash path length
3147     state.totalDashLen = 0;
3148     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3149     if (state.totalDashLen < 0.01f) {
3150       state.dashCount = 0; // nothing to do
3151     } else {
3152       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3153     }
3154   }
3155 }
3156 
3157 public alias lineDash = setLineDash; /// Ditto.
3158 
3159 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3160 /// Current limit is 16 pairs.
3161 /// Group: render_styles
3162 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3163   NVGstate* state = nvg__getState(ctx);
3164   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3165     state.lastFlattenDashCount = uint.max; // force re-flattening
3166   }
3167   state.dashStart = dashStart;
3168 }
3169 
3170 public alias lineDashStart = setLineDashStart; /// Ditto.
3171 
3172 /// Sets the transparency applied to all rendered shapes.
3173 /// Already transparent paths will get proportionally more transparent as well.
3174 /// Group: render_styles
3175 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3176   NVGstate* state = nvg__getState(ctx);
3177   state.alpha = alpha;
3178 }
3179 
3180 private void strokeColor() {}
3181 
3182 static if (NanoVegaHasArsdColor) {
3183 /// Sets current stroke style to a solid color.
3184 /// Group: render_styles
3185 @scriptable
3186 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3187   NVGstate* state = nvg__getState(ctx);
3188   nvg__setPaintColor(state.stroke, NVGColor(color));
3189 }
3190 }
3191 
3192 /// Sets current stroke style to a solid color.
3193 /// Group: render_styles
3194 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3195   NVGstate* state = nvg__getState(ctx);
3196   nvg__setPaintColor(state.stroke, color);
3197 }
3198 
3199 @scriptable
3200 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3201 	strokePaint(ctx, *paint);
3202 }
3203 
3204 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3205 /// Group: render_styles
3206 @scriptable
3207 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3208   NVGstate* state = nvg__getState(ctx);
3209   state.stroke = paint;
3210   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3211   state.stroke.xform.mul(state.xform);
3212 }
3213 
3214 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3215 // for scriptable reflection. it just needs to be declared first among the overloads
3216 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3217 
3218 static if (NanoVegaHasArsdColor) {
3219 /// Sets current fill style to a solid color.
3220 /// Group: render_styles
3221 @scriptable
3222 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3223   NVGstate* state = nvg__getState(ctx);
3224   nvg__setPaintColor(state.fill, NVGColor(color));
3225 }
3226 }
3227 
3228 /// Sets current fill style to a solid color.
3229 /// Group: render_styles
3230 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3231   NVGstate* state = nvg__getState(ctx);
3232   nvg__setPaintColor(state.fill, color);
3233 
3234 }
3235 
3236 @scriptable // kinda a hack for bug 16206 but also because jsvar deals in opaque NVGPaint* instead of auto refs (which it doesn't  know how to reflect on)
3237 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3238 	fillPaint(ctx, *paint);
3239 }
3240 
3241 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3242 /// Group: render_styles
3243 @scriptable
3244 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3245   NVGstate* state = nvg__getState(ctx);
3246   state.fill = paint;
3247   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3248   state.fill.xform.mul(state.xform);
3249 }
3250 
3251 /// Sets current fill style to a multistop linear gradient.
3252 /// Group: render_styles
3253 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3254   if (!lgs.valid) {
3255     NVGPaint p = void;
3256     memset(&p, 0, p.sizeof);
3257     nvg__setPaintColor(p, NVGColor.red);
3258     ctx.fillPaint = p;
3259   } else if (lgs.midp >= -1) {
3260     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3261     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3262   } else {
3263     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3264   }
3265 }
3266 
3267 /// Returns current transformation matrix.
3268 /// Group: render_transformations
3269 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3270   NVGstate* state = nvg__getState(ctx);
3271   return state.xform;
3272 }
3273 
3274 /// Sets current transformation matrix.
3275 /// Group: render_transformations
3276 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc {
3277   NVGstate* state = nvg__getState(ctx);
3278   state.xform = m;
3279 }
3280 
3281 /// Resets current transform to an identity matrix.
3282 /// Group: render_transformations
3283 @scriptable
3284 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3285   NVGstate* state = nvg__getState(ctx);
3286   state.xform.identity;
3287 }
3288 
3289 /// Premultiplies current coordinate system by specified matrix.
3290 /// Group: render_transformations
3291 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc {
3292   NVGstate* state = nvg__getState(ctx);
3293   //nvgTransformPremultiply(state.xform[], t[]);
3294   state.xform *= mt;
3295 }
3296 
3297 /// Translates current coordinate system.
3298 /// Group: render_transformations
3299 @scriptable
3300 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3301   NVGstate* state = nvg__getState(ctx);
3302   //NVGMatrix t = void;
3303   //nvgTransformTranslate(t[], x, y);
3304   //nvgTransformPremultiply(state.xform[], t[]);
3305   state.xform.premul(NVGMatrix.Translated(x, y));
3306 }
3307 
3308 /// Rotates current coordinate system. Angle is specified in radians.
3309 /// Group: render_transformations
3310 @scriptable
3311 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3312   NVGstate* state = nvg__getState(ctx);
3313   //NVGMatrix t = void;
3314   //nvgTransformRotate(t[], angle);
3315   //nvgTransformPremultiply(state.xform[], t[]);
3316   state.xform.premul(NVGMatrix.Rotated(angle));
3317 }
3318 
3319 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3320 /// Group: render_transformations
3321 @scriptable
3322 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3323   NVGstate* state = nvg__getState(ctx);
3324   //NVGMatrix t = void;
3325   //nvgTransformSkewX(t[], angle);
3326   //nvgTransformPremultiply(state.xform[], t[]);
3327   state.xform.premul(NVGMatrix.SkewedX(angle));
3328 }
3329 
3330 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3331 /// Group: render_transformations
3332 @scriptable
3333 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3334   NVGstate* state = nvg__getState(ctx);
3335   //NVGMatrix t = void;
3336   //nvgTransformSkewY(t[], angle);
3337   //nvgTransformPremultiply(state.xform[], t[]);
3338   state.xform.premul(NVGMatrix.SkewedY(angle));
3339 }
3340 
3341 /// Scales the current coordinate system.
3342 /// Group: render_transformations
3343 @scriptable
3344 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3345   NVGstate* state = nvg__getState(ctx);
3346   //NVGMatrix t = void;
3347   //nvgTransformScale(t[], x, y);
3348   //nvgTransformPremultiply(state.xform[], t[]);
3349   state.xform.premul(NVGMatrix.Scaled(x, y));
3350 }
3351 
3352 
3353 // ////////////////////////////////////////////////////////////////////////// //
3354 // Images
3355 
3356 /// Creates image by loading it from the disk from specified file name.
3357 /// Returns handle to the image or 0 on error.
3358 /// Group: images
3359 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3360   static if (NanoVegaHasArsdImage) {
3361     import arsd.image;
3362     // do we have new arsd API to load images?
3363     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3364       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3365     }
3366     try {
3367       auto oimg = MemoryImage.fromImageFile(filename);
3368       if (auto img = cast(TrueColorImage)oimg) {
3369         scope(exit) oimg.clearInternal();
3370         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3371       } else {
3372         TrueColorImage img = oimg.getAsTrueColorImage;
3373         scope(exit) img.clearInternal();
3374         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3375         oimg = null;
3376         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3377       }
3378     } catch (Exception) {}
3379     return NVGImage.init;
3380   } else {
3381     import std.internal.cstring;
3382     ubyte* img;
3383     int w, h, n;
3384     stbi_set_unpremultiply_on_load(1);
3385     stbi_convert_iphone_png_to_rgb(1);
3386     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3387     if (img is null) {
3388       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3389       return NVGImage.init;
3390     }
3391     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3392     stbi_image_free(img);
3393     return image;
3394   }
3395 }
3396 
3397 static if (NanoVegaHasArsdImage) {
3398   /// Creates image by loading it from the specified memory image.
3399   /// Returns handle to the image or 0 on error.
3400   /// Group: images
3401   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3402     if (img is null) return NVGImage.init;
3403     if (auto tc = cast(TrueColorImage)img) {
3404       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3405     } else {
3406       auto tc = img.getAsTrueColorImage;
3407       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3408       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3409     }
3410   }
3411 } else {
3412   /// Creates image by loading it from the specified chunk of memory.
3413   /// Returns handle to the image or 0 on error.
3414   /// Group: images
3415   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3416     int w, h, n, image;
3417     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3418     if (img is null) {
3419       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3420       return NVGImage.init;
3421     }
3422     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3423     stbi_image_free(img);
3424     return image;
3425   }
3426 }
3427 
3428 /// Creates image from specified image data.
3429 /// Returns handle to the image or 0 on error.
3430 /// Group: images
3431 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3432   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3433   uint imageFlags = 0;
3434   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3435   NVGImage res;
3436   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3437   if (res.id > 0) {
3438     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3439     res.ctx = ctx;
3440     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3441   }
3442   return res;
3443 }
3444 
3445 /// Updates image data specified by image handle.
3446 /// Group: images
3447 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3448   if (image.valid) {
3449     int w, h;
3450     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3451     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3452     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3453   }
3454 }
3455 
3456 /// Returns the dimensions of a created image.
3457 /// Group: images
3458 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3459   if (image.valid) {
3460     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3461     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3462   }
3463 }
3464 
3465 /// Deletes created image.
3466 /// Group: images
3467 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3468   if (ctx is null || !image.valid) return;
3469   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3470   image.clear();
3471 }
3472 
3473 
3474 // ////////////////////////////////////////////////////////////////////////// //
3475 // Paints
3476 
3477 private void linearGradient() {} // hack for dmd bug
3478 
3479 static if (NanoVegaHasArsdColor) {
3480 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3481  * of the linear gradient, icol specifies the start color and ocol the end color.
3482  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3483  *
3484  * Group: paints
3485  */
3486 @scriptable
3487 public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, in Color icol, in Color ocol) nothrow @trusted @nogc {
3488   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3489 }
3490 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3491  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3492  * range `(0..1)`, and ocol the end color.
3493  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3494  *
3495  * Group: paints
3496  */
3497 public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, in Color icol, in float midp, in Color mcol, in Color ocol) nothrow @trusted @nogc {
3498   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3499 }
3500 }
3501 
3502 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3503  * of the linear gradient, icol specifies the start color and ocol the end color.
3504  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3505  *
3506  * Group: paints
3507  */
3508 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3509   enum large = 1e5f;
3510 
3511   NVGPaint p = void;
3512   memset(&p, 0, p.sizeof);
3513   p.simpleColor = false;
3514 
3515   // Calculate transform aligned to the line
3516   float dx = ex-sx;
3517   float dy = ey-sy;
3518   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3519   if (d > 0.0001f) {
3520     dx /= d;
3521     dy /= d;
3522   } else {
3523     dx = 0;
3524     dy = 1;
3525   }
3526 
3527   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3528   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3529   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3530 
3531   p.extent.ptr[0] = large;
3532   p.extent.ptr[1] = large+d*0.5f;
3533 
3534   p.radius = 0.0f;
3535 
3536   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3537 
3538   p.innerColor = p.middleColor = icol;
3539   p.outerColor = ocol;
3540   p.midp = -1;
3541 
3542   return p;
3543 }
3544 
3545 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3546  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3547  * range `(0..1)`, and ocol the end color.
3548  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3549  *
3550  * Group: paints
3551  */
3552 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in float midp, in auto ref NVGColor mcol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3553   enum large = 1e5f;
3554 
3555   NVGPaint p = void;
3556   memset(&p, 0, p.sizeof);
3557   p.simpleColor = false;
3558 
3559   // Calculate transform aligned to the line
3560   float dx = ex-sx;
3561   float dy = ey-sy;
3562   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3563   if (d > 0.0001f) {
3564     dx /= d;
3565     dy /= d;
3566   } else {
3567     dx = 0;
3568     dy = 1;
3569   }
3570 
3571   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3572   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3573   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3574 
3575   p.extent.ptr[0] = large;
3576   p.extent.ptr[1] = large+d*0.5f;
3577 
3578   p.radius = 0.0f;
3579 
3580   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3581 
3582   if (midp <= 0) {
3583     p.innerColor = p.middleColor = mcol;
3584     p.midp = -1;
3585   } else if (midp > 1) {
3586     p.innerColor = p.middleColor = icol;
3587     p.midp = -1;
3588   } else {
3589     p.innerColor = icol;
3590     p.middleColor = mcol;
3591     p.midp = midp;
3592   }
3593   p.outerColor = ocol;
3594 
3595   return p;
3596 }
3597 
3598 static if (NanoVegaHasArsdColor) {
3599 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3600  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3601  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3602  *
3603  * Group: paints
3604  */
3605 public NVGPaint radialGradient (NVGContext ctx, in float cx, in float cy, in float inr, in float outr, in Color icol, in Color ocol) nothrow @trusted @nogc {
3606   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3607 }
3608 }
3609 
3610 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3611  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3612  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3613  *
3614  * Group: paints
3615  */
3616 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3617   immutable float r = (inr+outr)*0.5f;
3618   immutable float f = (outr-inr);
3619 
3620   NVGPaint p = void;
3621   memset(&p, 0, p.sizeof);
3622   p.simpleColor = false;
3623 
3624   p.xform.identity;
3625   p.xform.mat.ptr[4] = cx;
3626   p.xform.mat.ptr[5] = cy;
3627 
3628   p.extent.ptr[0] = r;
3629   p.extent.ptr[1] = r;
3630 
3631   p.radius = r;
3632 
3633   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3634 
3635   p.innerColor = p.middleColor = icol;
3636   p.outerColor = ocol;
3637   p.midp = -1;
3638 
3639   return p;
3640 }
3641 
3642 static if (NanoVegaHasArsdColor) {
3643 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3644  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3645  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3646  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3647  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3648  *
3649  * Group: paints
3650  */
3651 public NVGPaint boxGradient (NVGContext ctx, in float x, in float y, in float w, in float h, in float r, in float f, in Color icol, in Color ocol) nothrow @trusted @nogc {
3652   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3653 }
3654 }
3655 
3656 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3657  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3658  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3659  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3660  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3661  *
3662  * Group: paints
3663  */
3664 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3665   NVGPaint p = void;
3666   memset(&p, 0, p.sizeof);
3667   p.simpleColor = false;
3668 
3669   p.xform.identity;
3670   p.xform.mat.ptr[4] = x+w*0.5f;
3671   p.xform.mat.ptr[5] = y+h*0.5f;
3672 
3673   p.extent.ptr[0] = w*0.5f;
3674   p.extent.ptr[1] = h*0.5f;
3675 
3676   p.radius = r;
3677 
3678   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3679 
3680   p.innerColor = p.middleColor = icol;
3681   p.outerColor = ocol;
3682   p.midp = -1;
3683 
3684   return p;
3685 }
3686 
3687 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3688  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3689  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3690  *
3691  * Group: paints
3692  */
3693 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc {
3694   NVGPaint p = void;
3695   memset(&p, 0, p.sizeof);
3696   p.simpleColor = false;
3697 
3698   p.xform.identity.rotate(angle);
3699   p.xform.mat.ptr[4] = cx;
3700   p.xform.mat.ptr[5] = cy;
3701 
3702   p.extent.ptr[0] = w;
3703   p.extent.ptr[1] = h;
3704 
3705   p.image = image;
3706 
3707   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3708   p.midp = -1;
3709 
3710   return p;
3711 }
3712 
3713 /// Linear gradient with multiple stops.
3714 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3715 /// Group: paints
3716 public struct NVGLGS {
3717 private:
3718   NVGColor ic, mc, oc; // inner, middle, out
3719   float midp;
3720   NVGImage imgid;
3721   // [imagePattern] arguments
3722   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3723   public float angle; ///
3724 
3725 public:
3726   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3727   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3728 }
3729 
3730 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3731  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3732  *
3733  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3734  * Group: paints
3735  */
3736 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3737   if (!lgs.valid) {
3738     NVGPaint p = void;
3739     memset(&p, 0, p.sizeof);
3740     nvg__setPaintColor(p, NVGColor.red);
3741     return p;
3742   } else if (lgs.midp >= -1) {
3743     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3744   } else {
3745     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3746   }
3747 }
3748 
3749 /// Gradient Stop Point.
3750 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3751 /// Group: paints
3752 public struct NVGGradientStop {
3753   float offset = 0; /// [0..1]
3754   NVGColor color; ///
3755 
3756   this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3757   static if (NanoVegaHasArsdColor) {
3758     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3759   }
3760 }
3761 
3762 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3763 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3764 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3765 /// Group: paints
3766 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3767   // based on the code by Jorge Acereda <jacereda@gmail.com>
3768   enum NVG_GRADIENT_SAMPLES = 1024;
3769   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3770     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3771     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3772     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3773     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3774     uint sc = 0xffffffffU;
3775     uint sh = 24;
3776     uint r = cast(uint)(s0.color.rgba[0]*sc);
3777     uint g = cast(uint)(s0.color.rgba[1]*sc);
3778     uint b = cast(uint)(s0.color.rgba[2]*sc);
3779     uint a = cast(uint)(s0.color.rgba[3]*sc);
3780     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3781     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3782     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3783     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3784     dst += s;
3785     foreach (immutable _; s..e) {
3786       version(BigEndian) {
3787         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3788       } else {
3789         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3790       }
3791       r += dr;
3792       g += dg;
3793       b += db;
3794       a += da;
3795     }
3796   }
3797 
3798   NVGLGS res;
3799   res.cx = sx;
3800   res.cy = sy;
3801 
3802   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3803     // create simple linear gradient
3804     res.ic = res.mc = stops.ptr[0].color;
3805     res.oc = stops.ptr[1].color;
3806     res.midp = -1;
3807     res.dimx = ex;
3808     res.dimy = ey;
3809   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3810     // create simple linear gradient with middle stop
3811     res.ic = stops.ptr[0].color;
3812     res.mc = stops.ptr[1].color;
3813     res.oc = stops.ptr[2].color;
3814     res.midp = stops.ptr[1].offset;
3815     res.dimx = ex;
3816     res.dimy = ey;
3817   } else {
3818     // create image gradient
3819     uint[NVG_GRADIENT_SAMPLES] data = void;
3820     immutable float w = ex-sx;
3821     immutable float h = ey-sy;
3822     res.dimx = nvg__sqrtf(w*w+h*h);
3823     res.dimy = 1; //???
3824 
3825     res.angle =
3826       (/*nvg__absf(h) < 0.0001 ? 0 :
3827        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3828        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3829 
3830     if (stops.length > 0) {
3831       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3832       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3833       if (stops.length > 64) stops = stops[0..64];
3834       if (stops.length) {
3835         s0.color = stops[0].color;
3836         s1.color = stops[$-1].color;
3837       }
3838       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3839       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3840       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3841       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3842     }
3843   }
3844   return res;
3845 }
3846 
3847 
3848 // ////////////////////////////////////////////////////////////////////////// //
3849 // Scissoring
3850 
3851 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3852 /// Group: scissoring
3853 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3854   NVGstate* state = nvg__getState(ctx);
3855 
3856   w = nvg__max(0.0f, w);
3857   h = nvg__max(0.0f, h);
3858 
3859   state.scissor.xform.identity;
3860   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3861   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3862   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3863   state.scissor.xform.mul(state.xform);
3864 
3865   state.scissor.extent.ptr[0] = w*0.5f;
3866   state.scissor.extent.ptr[1] = h*0.5f;
3867 }
3868 
3869 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3870 /// Arguments: [x, y, w, h]*
3871 /// Group: scissoring
3872 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3873   enum ArgC = 4;
3874   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3875   if (args.length < ArgC) return;
3876   NVGstate* state = nvg__getState(ctx);
3877   const(float)* aptr = args.ptr;
3878   foreach (immutable idx; 0..args.length/ArgC) {
3879     immutable x = *aptr++;
3880     immutable y = *aptr++;
3881     immutable w = nvg__max(0.0f, *aptr++);
3882     immutable h = nvg__max(0.0f, *aptr++);
3883 
3884     state.scissor.xform.identity;
3885     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3886     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3887     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3888     state.scissor.xform.mul(state.xform);
3889 
3890     state.scissor.extent.ptr[0] = w*0.5f;
3891     state.scissor.extent.ptr[1] = h*0.5f;
3892   }
3893 }
3894 
3895 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3896   immutable float minx = nvg__max(ax, bx);
3897   immutable float miny = nvg__max(ay, by);
3898   immutable float maxx = nvg__min(ax+aw, bx+bw);
3899   immutable float maxy = nvg__min(ay+ah, by+bh);
3900   dst[0] = minx;
3901   dst[1] = miny;
3902   dst[2] = nvg__max(0.0f, maxx-minx);
3903   dst[3] = nvg__max(0.0f, maxy-miny);
3904 }
3905 
3906 /** Intersects current scissor rectangle with the specified rectangle.
3907  * The scissor rectangle is transformed by the current transform.
3908  * Note: in case the rotation of previous scissor rect differs from
3909  * the current one, the intersection will be done between the specified
3910  * rectangle and the previous scissor rectangle transformed in the current
3911  * transform space. The resulting shape is always rectangle.
3912  *
3913  * Group: scissoring
3914  */
3915 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3916   NVGstate* state = nvg__getState(ctx);
3917 
3918   // If no previous scissor has been set, set the scissor as current scissor.
3919   if (state.scissor.extent.ptr[0] < 0) {
3920     ctx.scissor(x, y, w, h);
3921     return;
3922   }
3923 
3924   NVGMatrix pxform = void;
3925   NVGMatrix invxorm = void;
3926   float[4] rect = void;
3927 
3928   // Transform the current scissor rect into current transform space.
3929   // If there is difference in rotation, this will be approximation.
3930   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3931   pxform = state.scissor.xform;
3932   immutable float ex = state.scissor.extent.ptr[0];
3933   immutable float ey = state.scissor.extent.ptr[1];
3934   //nvgTransformInverse(invxorm[], state.xform[]);
3935   invxorm = state.xform.inverted;
3936   //nvgTransformMultiply(pxform[], invxorm[]);
3937   pxform.mul(invxorm);
3938   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3939   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3940 
3941   // Intersect rects.
3942   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3943 
3944   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3945   ctx.scissor(rect.ptr[0..4]);
3946 }
3947 
3948 /** Intersects current scissor rectangle with the specified rectangle.
3949  * The scissor rectangle is transformed by the current transform.
3950  * Note: in case the rotation of previous scissor rect differs from
3951  * the current one, the intersection will be done between the specified
3952  * rectangle and the previous scissor rectangle transformed in the current
3953  * transform space. The resulting shape is always rectangle.
3954  *
3955  * Arguments: [x, y, w, h]*
3956  *
3957  * Group: scissoring
3958  */
3959 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3960   enum ArgC = 4;
3961   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
3962   if (args.length < ArgC) return;
3963   const(float)* aptr = args.ptr;
3964   foreach (immutable idx; 0..args.length/ArgC) {
3965     immutable x = *aptr++;
3966     immutable y = *aptr++;
3967     immutable w = *aptr++;
3968     immutable h = *aptr++;
3969     ctx.intersectScissor(x, y, w, h);
3970   }
3971 }
3972 
3973 /// Reset and disables scissoring.
3974 /// Group: scissoring
3975 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
3976   NVGstate* state = nvg__getState(ctx);
3977   state.scissor.xform.mat[] = 0.0f;
3978   state.scissor.extent[] = -1.0f;
3979 }
3980 
3981 
3982 // ////////////////////////////////////////////////////////////////////////// //
3983 // Render-Time Affine Transformations
3984 
3985 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
3986 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
3987 /// Group: gpu_affine
3988 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc {
3989   ctx.gpuAffine = mat;
3990   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
3991 }
3992 
3993 /// Get current GPU affine transformatin matrix.
3994 /// Group: gpu_affine
3995 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
3996   pragma(inline, true);
3997   return ctx.gpuAffine;
3998 }
3999 
4000 /// "Untransform" point using current GPU affine matrix.
4001 /// Group: gpu_affine
4002 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4003   if (ctx.gpuAffine.isIdentity) {
4004     if (dx !is null) *dx = x;
4005     if (dy !is null) *dy = y;
4006   } else {
4007     // inverse GPU transformation
4008     NVGMatrix igpu = ctx.gpuAffine.inverted;
4009     igpu.point(dx, dy, x, y);
4010   }
4011 }
4012 
4013 
4014 // ////////////////////////////////////////////////////////////////////////// //
4015 // rasterization (tesselation) code
4016 
4017 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4018   //pragma(inline, true);
4019   immutable float dx = x2-x1;
4020   immutable float dy = y2-y1;
4021   return dx*dx+dy*dy < tol*tol;
4022 }
4023 
4024 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4025   immutable float pqx = qx-px;
4026   immutable float pqy = qy-py;
4027   float dx = x-px;
4028   float dy = y-py;
4029   immutable float d = pqx*pqx+pqy*pqy;
4030   float t = pqx*dx+pqy*dy;
4031   if (d > 0) t /= d;
4032   if (t < 0) t = 0; else if (t > 1) t = 1;
4033   dx = px+t*pqx-x;
4034   dy = py+t*pqy-y;
4035   return dx*dx+dy*dy;
4036 }
4037 
4038 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4039   int nvals = cast(int)vals.length;
4040   static if (useCommand) {
4041     enum addon = 1;
4042   } else {
4043     enum addon = 0;
4044     if (nvals == 0) return; // nothing to do
4045   }
4046 
4047   NVGstate* state = nvg__getState(ctx);
4048 
4049   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4050     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4051     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4052     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4053     if (commands is null) assert(0, "NanoVega: out of memory");
4054     ctx.commands = commands;
4055     ctx.ccommands = ccommands;
4056     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4057   }
4058 
4059   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4060 
4061   if (acmd != Command.Close && acmd != Command.Winding) {
4062     //assert(nvals+addon >= 3);
4063     ctx.commandx = vals.ptr[nvals-2];
4064     ctx.commandy = vals.ptr[nvals-1];
4065   }
4066 
4067   // copy commands
4068   float* vp = ctx.commands+ctx.ncommands;
4069   static if (useCommand) {
4070     vp[0] = cast(float)acmd;
4071     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4072   } else {
4073     memcpy(vp, vals.ptr, nvals*float.sizeof);
4074   }
4075   ctx.ncommands += nvals+addon;
4076 
4077   // transform commands
4078   int i = nvals+addon;
4079   while (i > 0) {
4080     int nlen = 1;
4081     final switch (cast(Command)(*vp)) {
4082       case Command.MoveTo:
4083       case Command.LineTo:
4084         assert(i >= 3);
4085         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4086         nlen = 3;
4087         break;
4088       case Command.BezierTo:
4089         assert(i >= 7);
4090         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4091         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4092         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4093         nlen = 7;
4094         break;
4095       case Command.Close:
4096         nlen = 1;
4097         break;
4098       case Command.Winding:
4099         nlen = 2;
4100         break;
4101     }
4102     assert(nlen > 0 && nlen <= i);
4103     i -= nlen;
4104     vp += nlen;
4105   }
4106 }
4107 
4108 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4109   // no need to clear paths, as data is not copied there
4110   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4111   ctx.cache.npoints = 0;
4112   ctx.cache.npaths = 0;
4113   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4114   ctx.cache.clipmode = NVGClipMode.None;
4115 }
4116 
4117 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4118   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4119 }
4120 
4121 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4122   import core.stdc.stdlib : realloc;
4123   import core.stdc..string : memset;
4124 
4125   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4126     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4127     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4128     if (paths is null) assert(0, "NanoVega: out of memory");
4129     ctx.cache.paths = paths;
4130     ctx.cache.cpaths = cpaths;
4131   }
4132 
4133   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4134   memset(path, 0, NVGpath.sizeof);
4135   path.first = ctx.cache.npoints;
4136   path.mWinding = NVGWinding.CCW;
4137 }
4138 
4139 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4140   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4141 }
4142 
4143 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4144   NVGpath* path = nvg__lastPath(ctx);
4145   if (path is null) return;
4146 
4147   if (path.count > 0 && ctx.cache.npoints > 0) {
4148     NVGpoint* pt = nvg__lastPoint(ctx);
4149     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4150       pt.flags |= flags;
4151       return;
4152     }
4153   }
4154 
4155   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4156     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4157     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4158     if (points is null) return;
4159     ctx.cache.points = points;
4160     ctx.cache.cpoints = cpoints;
4161   }
4162 
4163   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4164   memset(pt, 0, (*pt).sizeof);
4165   pt.x = x;
4166   pt.y = y;
4167   pt.flags = cast(ubyte)flags;
4168 
4169   ++ctx.cache.npoints;
4170   ++path.count;
4171 }
4172 
4173 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4174   NVGpath* path = nvg__lastPath(ctx);
4175   if (path is null) return;
4176   path.closed = true;
4177 }
4178 
4179 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4180   NVGpath* path = nvg__lastPath(ctx);
4181   if (path is null) return;
4182   path.mWinding = winding;
4183 }
4184 
4185 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4186   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4187   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4188   return (sx+sy)*0.5f;
4189 }
4190 
4191 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4192   if (nverts > ctx.cache.cverts) {
4193     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4194     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4195     if (verts is null) return null;
4196     ctx.cache.verts = verts;
4197     ctx.cache.cverts = cverts;
4198   }
4199 
4200   return ctx.cache.verts;
4201 }
4202 
4203 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4204   immutable float abx = bx-ax;
4205   immutable float aby = by-ay;
4206   immutable float acx = cx-ax;
4207   immutable float acy = cy-ay;
4208   return acx*aby-abx*acy;
4209 }
4210 
4211 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4212   float area = 0;
4213   foreach (int i; 2..npts) {
4214     NVGpoint* a = &pts[0];
4215     NVGpoint* b = &pts[i-1];
4216     NVGpoint* c = &pts[i];
4217     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4218   }
4219   return area*0.5f;
4220 }
4221 
4222 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4223   NVGpoint tmp = void;
4224   int i = 0, j = npts-1;
4225   while (i < j) {
4226     tmp = pts[i];
4227     pts[i] = pts[j];
4228     pts[j] = tmp;
4229     ++i;
4230     --j;
4231   }
4232 }
4233 
4234 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4235   vtx.x = x;
4236   vtx.y = y;
4237   vtx.u = u;
4238   vtx.v = v;
4239 }
4240 
4241 void nvg__tesselateBezier (NVGContext ctx, 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) nothrow @trusted @nogc {
4242   if (level > 10) return;
4243 
4244   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4245   /*
4246   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4247     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4248       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4249       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4250     }
4251     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4252       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4253       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4254       return;
4255     }
4256   }
4257   */
4258 
4259   immutable float x12 = (x1+x2)*0.5f;
4260   immutable float y12 = (y1+y2)*0.5f;
4261   immutable float x23 = (x2+x3)*0.5f;
4262   immutable float y23 = (y2+y3)*0.5f;
4263   immutable float x34 = (x3+x4)*0.5f;
4264   immutable float y34 = (y3+y4)*0.5f;
4265   immutable float x123 = (x12+x23)*0.5f;
4266   immutable float y123 = (y12+y23)*0.5f;
4267 
4268   immutable float dx = x4-x1;
4269   immutable float dy = y4-y1;
4270   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4271   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4272 
4273   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4274     nvg__addPoint(ctx, x4, y4, type);
4275     return;
4276   }
4277 
4278   immutable float x234 = (x23+x34)*0.5f;
4279   immutable float y234 = (y23+y34)*0.5f;
4280   immutable float x1234 = (x123+x234)*0.5f;
4281   immutable float y1234 = (y123+y234)*0.5f;
4282 
4283   // "taxicab" / "manhattan" check for flat curves
4284   if (nvg__absf(x1+x3-x2-x2)+nvg__absf(y1+y3-y2-y2)+nvg__absf(x2+x4-x3-x3)+nvg__absf(y2+y4-y3-y3) < ctx.tessTol/4) {
4285     nvg__addPoint(ctx, x1234, y1234, type);
4286     return;
4287   }
4288 
4289   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4290   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4291 }
4292 
4293 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4294 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4295 void nvg__tesselateBezierMcSeem (NVGContext ctx, 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) nothrow @trusted @nogc {
4296   enum CollinearEPS = 0.00000001f; // 0.00001f;
4297   enum AngleTolEPS = 0.01f;
4298 
4299   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4300     pragma(inline, true);
4301     immutable float dx = x2-x1;
4302     immutable float dy = y2-y1;
4303     return dx*dx+dy*dy;
4304   }
4305 
4306   if (level == 0) {
4307     nvg__addPoint(ctx, x1, y1, 0);
4308     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4309     nvg__addPoint(ctx, x4, y4, type);
4310     return;
4311   }
4312 
4313   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4314 
4315   // calculate all the mid-points of the line segments
4316   immutable float x12 = (x1+x2)*0.5f;
4317   immutable float y12 = (y1+y2)*0.5f;
4318   immutable float x23 = (x2+x3)*0.5f;
4319   immutable float y23 = (y2+y3)*0.5f;
4320   immutable float x34 = (x3+x4)*0.5f;
4321   immutable float y34 = (y3+y4)*0.5f;
4322   immutable float x123 = (x12+x23)*0.5f;
4323   immutable float y123 = (y12+y23)*0.5f;
4324   immutable float x234 = (x23+x34)*0.5f;
4325   immutable float y234 = (y23+y34)*0.5f;
4326   immutable float x1234 = (x123+x234)*0.5f;
4327   immutable float y1234 = (y123+y234)*0.5f;
4328 
4329   // try to approximate the full cubic curve by a single straight line
4330   immutable float dx = x4-x1;
4331   immutable float dy = y4-y1;
4332 
4333   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4334   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4335   //immutable float da1, da2, k;
4336 
4337   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4338     case 0:
4339       // all collinear or p1 == p4
4340       float k = dx*dx+dy*dy;
4341       if (k == 0) {
4342         d2 = distSquared(x1, y1, x2, y2);
4343         d3 = distSquared(x4, y4, x3, y3);
4344       } else {
4345         k = 1.0f/k;
4346         float da1 = x2-x1;
4347         float da2 = y2-y1;
4348         d2 = k*(da1*dx+da2*dy);
4349         da1 = x3-x1;
4350         da2 = y3-y1;
4351         d3 = k*(da1*dx+da2*dy);
4352         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4353           // Simple collinear case, 1---2---3---4
4354           // We can leave just two endpoints
4355           return;
4356         }
4357              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4358         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4359         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4360 
4361              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4362         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4363         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4364       }
4365       if (d2 > d3) {
4366         if (d2 < ctx.tessTol) {
4367           nvg__addPoint(ctx, x2, y2, type);
4368           return;
4369         }
4370       } if (d3 < ctx.tessTol) {
4371         nvg__addPoint(ctx, x3, y3, type);
4372         return;
4373       }
4374       break;
4375     case 1:
4376       // p1,p2,p4 are collinear, p3 is significant
4377       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4378         if (ctx.angleTol < AngleTolEPS) {
4379           nvg__addPoint(ctx, x23, y23, type);
4380           return;
4381         } else {
4382           // angle condition
4383           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4384           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4385           if (da1 < ctx.angleTol) {
4386             nvg__addPoint(ctx, x2, y2, type);
4387             nvg__addPoint(ctx, x3, y3, type);
4388             return;
4389           }
4390           if (ctx.cuspLimit != 0.0) {
4391             if (da1 > ctx.cuspLimit) {
4392               nvg__addPoint(ctx, x3, y3, type);
4393               return;
4394             }
4395           }
4396         }
4397       }
4398       break;
4399     case 2:
4400       // p1,p3,p4 are collinear, p2 is significant
4401       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4402         if (ctx.angleTol < AngleTolEPS) {
4403           nvg__addPoint(ctx, x23, y23, type);
4404           return;
4405         } else {
4406           // angle condition
4407           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4408           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4409           if (da1 < ctx.angleTol) {
4410             nvg__addPoint(ctx, x2, y2, type);
4411             nvg__addPoint(ctx, x3, y3, type);
4412             return;
4413           }
4414           if (ctx.cuspLimit != 0.0) {
4415             if (da1 > ctx.cuspLimit) {
4416               nvg__addPoint(ctx, x2, y2, type);
4417               return;
4418             }
4419           }
4420         }
4421       }
4422       break;
4423     case 3:
4424       // regular case
4425       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4426         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4427         if (ctx.angleTol < AngleTolEPS) {
4428           nvg__addPoint(ctx, x23, y23, type);
4429           return;
4430         } else {
4431           // angle and cusp condition
4432           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4433           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4434           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4435           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4436           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4437           if (da1+da2 < ctx.angleTol) {
4438             // finally we can stop the recursion
4439             nvg__addPoint(ctx, x23, y23, type);
4440             return;
4441           }
4442           if (ctx.cuspLimit != 0.0) {
4443             if (da1 > ctx.cuspLimit) {
4444               nvg__addPoint(ctx, x2, y2, type);
4445               return;
4446             }
4447             if (da2 > ctx.cuspLimit) {
4448               nvg__addPoint(ctx, x3, y3, type);
4449               return;
4450             }
4451           }
4452         }
4453       }
4454       break;
4455   }
4456 
4457   // continue subdivision
4458   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4459   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4460 }
4461 
4462 
4463 // Adaptive forward differencing for bezier tesselation.
4464 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4465 // "Adaptive forward differencing for rendering curves and surfaces."
4466 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4467 // original code by Taylor Holliday <taylor@audulus.com>
4468 void nvg__tesselateBezierAFD (NVGContext ctx, 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 {
4469   enum AFD_ONE = (1<<10);
4470 
4471   // power basis
4472   immutable float ax = -x1+3*x2-3*x3+x4;
4473   immutable float ay = -y1+3*y2-3*y3+y4;
4474   immutable float bx = 3*x1-6*x2+3*x3;
4475   immutable float by = 3*y1-6*y2+3*y3;
4476   immutable float cx = -3*x1+3*x2;
4477   immutable float cy = -3*y1+3*y2;
4478 
4479   // Transform to forward difference basis (stepsize 1)
4480   float px = x1;
4481   float py = y1;
4482   float dx = ax+bx+cx;
4483   float dy = ay+by+cy;
4484   float ddx = 6*ax+2*bx;
4485   float ddy = 6*ay+2*by;
4486   float dddx = 6*ax;
4487   float dddy = 6*ay;
4488 
4489   //printf("dx: %f, dy: %f\n", dx, dy);
4490   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4491   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4492 
4493   int t = 0;
4494   int dt = AFD_ONE;
4495 
4496   immutable float tol = ctx.tessTol*4;
4497 
4498   while (t < AFD_ONE) {
4499     // Flatness measure.
4500     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4501 
4502     // printf("d: %f, th: %f\n", d, th);
4503 
4504     // Go to higher resolution if we're moving a lot or overshooting the end.
4505     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4506       // printf("up\n");
4507 
4508       // Apply L to the curve. Increase curve resolution.
4509       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4510       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4511       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4512       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4513       dddx = (1.0f/8.0f)*dddx;
4514       dddy = (1.0f/8.0f)*dddy;
4515 
4516       // Half the stepsize.
4517       dt >>= 1;
4518 
4519       // Recompute d
4520       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4521     }
4522 
4523     // Go to lower resolution if we're really flat
4524     // and we aren't going to overshoot the end.
4525     // XXX: tol/32 is just a guess for when we are too flat.
4526     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4527       // printf("down\n");
4528 
4529       // Apply L^(-1) to the curve. Decrease curve resolution.
4530       dx = 2*dx+ddx;
4531       dy = 2*dy+ddy;
4532       ddx = 4*ddx+4*dddx;
4533       ddy = 4*ddy+4*dddy;
4534       dddx = 8*dddx;
4535       dddy = 8*dddy;
4536 
4537       // Double the stepsize.
4538       dt <<= 1;
4539 
4540       // Recompute d
4541       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4542     }
4543 
4544     // Forward differencing.
4545     px += dx;
4546     py += dy;
4547     dx += ddx;
4548     dy += ddy;
4549     ddx += dddx;
4550     ddy += dddy;
4551 
4552     // Output a point.
4553     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4554 
4555     // Advance along the curve.
4556     t += dt;
4557 
4558     // Ensure we don't overshoot.
4559     assert(t <= AFD_ONE);
4560   }
4561 }
4562 
4563 
4564 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4565   import core.stdc.stdlib : realloc;
4566   import core.stdc..string : memcpy;
4567 
4568   NVGpathCache* cache = ctx.cache;
4569   if (cache.npaths == 0) return;
4570 
4571   NVGpath* path = nvg__lastPath(ctx);
4572   if (path is null) return;
4573 
4574   NVGstate* state = nvg__getState(ctx);
4575   if (!state.dasherActive) return;
4576 
4577   static NVGpoint* pts = null;
4578   static uint ptsCount = 0;
4579   static uint ptsSize = 0;
4580 
4581   if (path.count < 2) return; // just in case
4582 
4583   // copy path points (reserve one point for closed pathes)
4584   if (ptsSize < path.count+1) {
4585     ptsSize = cast(uint)(path.count+1);
4586     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4587     if (pts is null) assert(0, "NanoVega: out of memory");
4588   }
4589   ptsCount = cast(uint)path.count;
4590   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4591   // add closing point for closed pathes
4592   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4593     pts[ptsCount++] = pts[0];
4594   }
4595 
4596   // remove last path (with its points)
4597   --cache.npaths;
4598   cache.npoints -= path.count;
4599 
4600   // add stroked pathes
4601   const(float)* dashes = state.dashes.ptr;
4602   immutable uint dashCount = state.dashCount;
4603   float currDashStart = 0;
4604   uint currDashIdx = 0;
4605   immutable bool firstIsGap = state.firstDashIsGap;
4606 
4607   // calculate lengthes
4608   {
4609     NVGpoint* v1 = &pts[0];
4610     NVGpoint* v2 = &pts[1];
4611     foreach (immutable _; 0..ptsCount) {
4612       float dx = v2.x-v1.x;
4613       float dy = v2.y-v1.y;
4614       v1.len = nvg__normalize(&dx, &dy);
4615       v1 = v2++;
4616     }
4617   }
4618 
4619   void calcDashStart (float ds) {
4620     if (ds < 0) {
4621       ds = ds%state.totalDashLen;
4622       while (ds < 0) ds += state.totalDashLen;
4623     }
4624     currDashIdx = 0;
4625     currDashStart = 0;
4626     while (ds > 0) {
4627       if (ds > dashes[currDashIdx]) {
4628         ds -= dashes[currDashIdx];
4629         ++currDashIdx;
4630         currDashStart = 0;
4631         if (currDashIdx >= dashCount) currDashIdx = 0;
4632       } else {
4633         currDashStart = ds;
4634         ds = 0;
4635       }
4636     }
4637   }
4638 
4639   calcDashStart(state.dashStart);
4640 
4641   uint srcPointIdx = 1;
4642   const(NVGpoint)* v1 = &pts[0];
4643   const(NVGpoint)* v2 = &pts[1];
4644   float currRest = v1.len;
4645   nvg__addPath(ctx);
4646   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4647 
4648   void fixLastPoint () {
4649     auto lpt = nvg__lastPath(ctx);
4650     if (lpt !is null && lpt.count > 0) {
4651       // fix last point
4652       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4653       // fix first point
4654       NVGpathCache* cache = ctx.cache;
4655       cache.points[lpt.first].flags = PointFlag.Corner;
4656     }
4657   }
4658 
4659   for (;;) {
4660     immutable float dlen = dashes[currDashIdx];
4661     if (dlen == 0) {
4662       ++currDashIdx;
4663       if (currDashIdx >= dashCount) currDashIdx = 0;
4664       continue;
4665     }
4666     immutable float dashRest = dlen-currDashStart;
4667     if ((currDashIdx&1) != firstIsGap) {
4668       // this is "moveto" command, so create new path
4669       fixLastPoint();
4670       nvg__addPath(ctx);
4671     }
4672     if (currRest > dashRest) {
4673       currRest -= dashRest;
4674       ++currDashIdx;
4675       if (currDashIdx >= dashCount) currDashIdx = 0;
4676       currDashStart = 0;
4677       nvg__addPoint(ctx,
4678         v2.x-(v2.x-v1.x)*currRest/v1.len,
4679         v2.y-(v2.y-v1.y)*currRest/v1.len,
4680         PointFlag.Corner
4681       );
4682     } else {
4683       currDashStart += currRest;
4684       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4685       ++srcPointIdx;
4686       v1 = v2;
4687       currRest = v1.len;
4688       if (srcPointIdx >= ptsCount) break;
4689       v2 = &pts[srcPointIdx];
4690     }
4691   }
4692   fixLastPoint();
4693 }
4694 
4695 
4696 version(nanovg_bench_flatten) import iv.timer : Timer;
4697 
4698 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4699   version(nanovg_bench_flatten) {
4700     Timer timer;
4701     char[128] tmbuf;
4702     int bzcount;
4703   }
4704   NVGpathCache* cache = ctx.cache;
4705   NVGstate* state = nvg__getState(ctx);
4706 
4707   // check if we already did flattening
4708   static if (asStroke) {
4709     if (state.dashCount >= 2) {
4710       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4711       state.dasherActive = true;
4712       state.lastFlattenDashCount = state.dashCount;
4713     } else {
4714       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4715       state.dasherActive = false;
4716       state.lastFlattenDashCount = 0;
4717     }
4718   } else {
4719     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4720     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4721     state.dasherActive = false;
4722   }
4723 
4724   // clear path cache
4725   cache.npaths = 0;
4726   cache.npoints = 0;
4727 
4728   // flatten
4729   version(nanovg_bench_flatten) timer.restart();
4730   int i = 0;
4731   while (i < ctx.ncommands) {
4732     final switch (cast(Command)ctx.commands[i]) {
4733       case Command.MoveTo:
4734         //assert(i+3 <= ctx.ncommands);
4735         static if (asStroke) {
4736           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4737         }
4738         nvg__addPath(ctx);
4739         const p = &ctx.commands[i+1];
4740         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4741         i += 3;
4742         break;
4743       case Command.LineTo:
4744         //assert(i+3 <= ctx.ncommands);
4745         const p = &ctx.commands[i+1];
4746         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4747         i += 3;
4748         break;
4749       case Command.BezierTo:
4750         //assert(i+7 <= ctx.ncommands);
4751         const last = nvg__lastPoint(ctx);
4752         if (last !is null) {
4753           const cp1 = &ctx.commands[i+1];
4754           const cp2 = &ctx.commands[i+3];
4755           const p = &ctx.commands[i+5];
4756           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4757             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4758           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4759             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4760           } else {
4761             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4762           }
4763           version(nanovg_bench_flatten) ++bzcount;
4764         }
4765         i += 7;
4766         break;
4767       case Command.Close:
4768         //assert(i+1 <= ctx.ncommands);
4769         nvg__closePath(ctx);
4770         i += 1;
4771         break;
4772       case Command.Winding:
4773         //assert(i+2 <= ctx.ncommands);
4774         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4775         i += 2;
4776         break;
4777     }
4778   }
4779   static if (asStroke) {
4780     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4781   }
4782   version(nanovg_bench_flatten) {{
4783     timer.stop();
4784     auto xb = timer.toBuffer(tmbuf[]);
4785     import core.stdc.stdio : printf;
4786     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4787   }}
4788 
4789   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4790   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4791 
4792   // calculate the direction and length of line segments
4793   version(nanovg_bench_flatten) timer.restart();
4794   foreach (int j; 0..cache.npaths) {
4795     NVGpath* path = &cache.paths[j];
4796     NVGpoint* pts = &cache.points[path.first];
4797 
4798     // if the first and last points are the same, remove the last, mark as closed path
4799     NVGpoint* p0 = &pts[path.count-1];
4800     NVGpoint* p1 = &pts[0];
4801     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4802       --path.count;
4803       p0 = &pts[path.count-1];
4804       path.closed = true;
4805     }
4806 
4807     // enforce winding
4808     if (path.count > 2) {
4809       immutable float area = nvg__polyArea(pts, path.count);
4810       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4811       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4812     }
4813 
4814     foreach (immutable _; 0..path.count) {
4815       // calculate segment direction and length
4816       p0.dx = p1.x-p0.x;
4817       p0.dy = p1.y-p0.y;
4818       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4819       // update bounds
4820       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4821       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4822       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4823       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4824       // advance
4825       p0 = p1++;
4826     }
4827   }
4828   version(nanovg_bench_flatten) {{
4829     timer.stop();
4830     auto xb = timer.toBuffer(tmbuf[]);
4831     import core.stdc.stdio : printf;
4832     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4833   }}
4834 }
4835 
4836 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4837   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4838   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4839 }
4840 
4841 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4842   if (bevel) {
4843     *x0 = p1.x+p0.dy*w;
4844     *y0 = p1.y-p0.dx*w;
4845     *x1 = p1.x+p1.dy*w;
4846     *y1 = p1.y-p1.dx*w;
4847   } else {
4848     *x0 = p1.x+p1.dmx*w;
4849     *y0 = p1.y+p1.dmy*w;
4850     *x1 = p1.x+p1.dmx*w;
4851     *y1 = p1.y+p1.dmy*w;
4852   }
4853 }
4854 
4855 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4856   float dlx0 = p0.dy;
4857   float dly0 = -p0.dx;
4858   float dlx1 = p1.dy;
4859   float dly1 = -p1.dx;
4860   //NVG_NOTUSED(fringe);
4861 
4862   if (p1.flags&PointFlag.Left) {
4863     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4864     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4865     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4866     float a1 = nvg__atan2f(-dly1, -dlx1);
4867     if (a1 > a0) a1 -= NVG_PI*2;
4868 
4869     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4870     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4871 
4872     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4873     for (int i = 0; i < n; ++i) {
4874       float u = i/cast(float)(n-1);
4875       float a = a0+u*(a1-a0);
4876       float rx = p1.x+nvg__cosf(a)*rw;
4877       float ry = p1.y+nvg__sinf(a)*rw;
4878       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4879       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4880     }
4881 
4882     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4883     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4884 
4885   } else {
4886     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4887     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4888     immutable float a0 = nvg__atan2f(dly0, dlx0);
4889     float a1 = nvg__atan2f(dly1, dlx1);
4890     if (a1 < a0) a1 += NVG_PI*2;
4891 
4892     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4893     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4894 
4895     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4896     for (int i = 0; i < n; i++) {
4897       float u = i/cast(float)(n-1);
4898       float a = a0+u*(a1-a0);
4899       float lx = p1.x+nvg__cosf(a)*lw;
4900       float ly = p1.y+nvg__sinf(a)*lw;
4901       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4902       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4903     }
4904 
4905     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4906     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4907 
4908   }
4909   return dst;
4910 }
4911 
4912 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4913   float rx0, ry0, rx1, ry1;
4914   float lx0, ly0, lx1, ly1;
4915   float dlx0 = p0.dy;
4916   float dly0 = -p0.dx;
4917   float dlx1 = p1.dy;
4918   float dly1 = -p1.dx;
4919   //NVG_NOTUSED(fringe);
4920 
4921   if (p1.flags&PointFlag.Left) {
4922     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4923 
4924     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4925     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4926 
4927     if (p1.flags&PointFlag.Bevel) {
4928       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4929       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4930 
4931       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4932       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4933     } else {
4934       rx0 = p1.x-p1.dmx*rw;
4935       ry0 = p1.y-p1.dmy*rw;
4936 
4937       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4938       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4939 
4940       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4941       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4942 
4943       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4944       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4945     }
4946 
4947     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4948     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4949 
4950   } else {
4951     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4952 
4953     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4954     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4955 
4956     if (p1.flags&PointFlag.Bevel) {
4957       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4958       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4959 
4960       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4961       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4962     } else {
4963       lx0 = p1.x+p1.dmx*lw;
4964       ly0 = p1.y+p1.dmy*lw;
4965 
4966       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4967       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4968 
4969       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4970       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4971 
4972       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4973       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4974     }
4975 
4976     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4977     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4978   }
4979 
4980   return dst;
4981 }
4982 
4983 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
4984   immutable float px = p.x-dx*d;
4985   immutable float py = p.y-dy*d;
4986   immutable float dlx = dy;
4987   immutable float dly = -dx;
4988   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
4989   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
4990   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
4991   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
4992   return dst;
4993 }
4994 
4995 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
4996   immutable float px = p.x+dx*d;
4997   immutable float py = p.y+dy*d;
4998   immutable float dlx = dy;
4999   immutable float dly = -dx;
5000   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5001   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5002   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5003   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5004   return dst;
5005 }
5006 
5007 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5008   immutable float px = p.x;
5009   immutable float py = p.y;
5010   immutable float dlx = dy;
5011   immutable float dly = -dx;
5012   //NVG_NOTUSED(aa);
5013   immutable float ncpf = cast(float)(ncap-1);
5014   foreach (int i; 0..ncap) {
5015     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5016     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5017     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5018     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5019   }
5020   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5021   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5022   return dst;
5023 }
5024 
5025 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5026   immutable float px = p.x;
5027   immutable float py = p.y;
5028   immutable float dlx = dy;
5029   immutable float dly = -dx;
5030   //NVG_NOTUSED(aa);
5031   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5032   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5033   immutable float ncpf = cast(float)(ncap-1);
5034   foreach (int i; 0..ncap) {
5035     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5036     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5037     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5038     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5039   }
5040   return dst;
5041 }
5042 
5043 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5044   NVGpathCache* cache = ctx.cache;
5045   float iw = 0.0f;
5046 
5047   if (w > 0.0f) iw = 1.0f/w;
5048 
5049   // Calculate which joins needs extra vertices to append, and gather vertex count.
5050   foreach (int i; 0..cache.npaths) {
5051     NVGpath* path = &cache.paths[i];
5052     NVGpoint* pts = &cache.points[path.first];
5053     NVGpoint* p0 = &pts[path.count-1];
5054     NVGpoint* p1 = &pts[0];
5055     int nleft = 0;
5056 
5057     path.nbevel = 0;
5058 
5059     foreach (int j; 0..path.count) {
5060       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5061       immutable float dlx0 = p0.dy;
5062       immutable float dly0 = -p0.dx;
5063       immutable float dlx1 = p1.dy;
5064       immutable float dly1 = -p1.dx;
5065       // Calculate extrusions
5066       p1.dmx = (dlx0+dlx1)*0.5f;
5067       p1.dmy = (dly0+dly1)*0.5f;
5068       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5069       if (dmr2 > 0.000001f) {
5070         float scale = 1.0f/dmr2;
5071         if (scale > 600.0f) scale = 600.0f;
5072         p1.dmx *= scale;
5073         p1.dmy *= scale;
5074       }
5075 
5076       // Clear flags, but keep the corner.
5077       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5078 
5079       // Keep track of left turns.
5080       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5081       if (cross > 0.0f) {
5082         nleft++;
5083         p1.flags |= PointFlag.Left;
5084       }
5085 
5086       // Calculate if we should use bevel or miter for inner join.
5087       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5088       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5089 
5090       // Check to see if the corner needs to be beveled.
5091       if (p1.flags&PointFlag.Corner) {
5092         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5093           p1.flags |= PointFlag.Bevel;
5094         }
5095       }
5096 
5097       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5098 
5099       p0 = p1++;
5100     }
5101 
5102     path.convex = (nleft == path.count);
5103   }
5104 }
5105 
5106 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5107   NVGpathCache* cache = ctx.cache;
5108   immutable float aa = ctx.fringeWidth;
5109   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5110 
5111   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5112 
5113   // Calculate max vertex usage.
5114   int cverts = 0;
5115   foreach (int i; 0..cache.npaths) {
5116     NVGpath* path = &cache.paths[i];
5117     immutable bool loop = path.closed;
5118     if (lineJoin == NVGLineCap.Round) {
5119       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5120     } else {
5121       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5122     }
5123     if (!loop) {
5124       // space for caps
5125       if (lineCap == NVGLineCap.Round) {
5126         cverts += (ncap*2+2)*2;
5127       } else {
5128         cverts += (3+3)*2;
5129       }
5130     }
5131   }
5132 
5133   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5134   if (verts is null) return;
5135 
5136   foreach (int i; 0..cache.npaths) {
5137     NVGpath* path = &cache.paths[i];
5138     NVGpoint* pts = &cache.points[path.first];
5139     NVGpoint* p0;
5140     NVGpoint* p1;
5141     int s, e;
5142 
5143     path.fill = null;
5144     path.nfill = 0;
5145 
5146     // Calculate fringe or stroke
5147     immutable bool loop = path.closed;
5148     NVGVertex* dst = verts;
5149     path.stroke = dst;
5150 
5151     if (loop) {
5152       // Looping
5153       p0 = &pts[path.count-1];
5154       p1 = &pts[0];
5155       s = 0;
5156       e = path.count;
5157     } else {
5158       // Add cap
5159       p0 = &pts[0];
5160       p1 = &pts[1];
5161       s = 1;
5162       e = path.count-1;
5163     }
5164 
5165     if (!loop) {
5166       // Add cap
5167       float dx = p1.x-p0.x;
5168       float dy = p1.y-p0.y;
5169       nvg__normalize(&dx, &dy);
5170            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5171       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5172       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5173     }
5174 
5175     foreach (int j; s..e) {
5176       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5177         if (lineJoin == NVGLineCap.Round) {
5178           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5179         } else {
5180           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5181         }
5182       } else {
5183         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5184         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5185       }
5186       p0 = p1++;
5187     }
5188 
5189     if (loop) {
5190       // Loop it
5191       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5192       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5193     } else {
5194       // Add cap
5195       float dx = p1.x-p0.x;
5196       float dy = p1.y-p0.y;
5197       nvg__normalize(&dx, &dy);
5198            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5199       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5200       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5201     }
5202 
5203     path.nstroke = cast(int)(dst-verts);
5204 
5205     verts = dst;
5206   }
5207 }
5208 
5209 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5210   NVGpathCache* cache = ctx.cache;
5211   immutable float aa = ctx.fringeWidth;
5212   bool fringe = (w > 0.0f);
5213 
5214   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5215 
5216   // Calculate max vertex usage.
5217   int cverts = 0;
5218   foreach (int i; 0..cache.npaths) {
5219     NVGpath* path = &cache.paths[i];
5220     cverts += path.count+path.nbevel+1;
5221     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5222   }
5223 
5224   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5225   if (verts is null) return;
5226 
5227   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5228 
5229   foreach (int i; 0..cache.npaths) {
5230     NVGpath* path = &cache.paths[i];
5231     NVGpoint* pts = &cache.points[path.first];
5232 
5233     // Calculate shape vertices.
5234     immutable float woff = 0.5f*aa;
5235     NVGVertex* dst = verts;
5236     path.fill = dst;
5237 
5238     if (fringe) {
5239       // Looping
5240       NVGpoint* p0 = &pts[path.count-1];
5241       NVGpoint* p1 = &pts[0];
5242       foreach (int j; 0..path.count) {
5243         if (p1.flags&PointFlag.Bevel) {
5244           immutable float dlx0 = p0.dy;
5245           immutable float dly0 = -p0.dx;
5246           immutable float dlx1 = p1.dy;
5247           immutable float dly1 = -p1.dx;
5248           if (p1.flags&PointFlag.Left) {
5249             immutable float lx = p1.x+p1.dmx*woff;
5250             immutable float ly = p1.y+p1.dmy*woff;
5251             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5252           } else {
5253             immutable float lx0 = p1.x+dlx0*woff;
5254             immutable float ly0 = p1.y+dly0*woff;
5255             immutable float lx1 = p1.x+dlx1*woff;
5256             immutable float ly1 = p1.y+dly1*woff;
5257             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5258             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5259           }
5260         } else {
5261           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5262         }
5263         p0 = p1++;
5264       }
5265     } else {
5266       foreach (int j; 0..path.count) {
5267         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5268         ++dst;
5269       }
5270     }
5271 
5272     path.nfill = cast(int)(dst-verts);
5273     verts = dst;
5274 
5275     // Calculate fringe
5276     if (fringe) {
5277       float lw = w+woff;
5278       immutable float rw = w-woff;
5279       float lu = 0;
5280       immutable float ru = 1;
5281       dst = verts;
5282       path.stroke = dst;
5283 
5284       // Create only half a fringe for convex shapes so that
5285       // the shape can be rendered without stenciling.
5286       if (convex) {
5287         lw = woff; // This should generate the same vertex as fill inset above.
5288         lu = 0.5f; // Set outline fade at middle.
5289       }
5290 
5291       // Looping
5292       NVGpoint* p0 = &pts[path.count-1];
5293       NVGpoint* p1 = &pts[0];
5294 
5295       foreach (int j; 0..path.count) {
5296         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5297           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5298         } else {
5299           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5300           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5301         }
5302         p0 = p1++;
5303       }
5304 
5305       // Loop it
5306       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5307       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5308 
5309       path.nstroke = cast(int)(dst-verts);
5310       verts = dst;
5311     } else {
5312       path.stroke = null;
5313       path.nstroke = 0;
5314     }
5315   }
5316 }
5317 
5318 
5319 // ////////////////////////////////////////////////////////////////////////// //
5320 // Paths
5321 
5322 /// Clears the current path and sub-paths.
5323 /// Group: paths
5324 @scriptable
5325 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5326   ctx.ncommands = 0;
5327   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5328   nvg__clearPathCache(ctx);
5329 }
5330 
5331 public alias newPath = beginPath; /// Ditto.
5332 
5333 /// Starts new sub-path with specified point as first point.
5334 /// Group: paths
5335 @scriptable
5336 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5337   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5338 }
5339 
5340 /// Starts new sub-path with specified point as first point.
5341 /// Arguments: [x, y]*
5342 /// Group: paths
5343 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5344   enum ArgC = 2;
5345   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5346   if (args.length < ArgC) return;
5347   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5348 }
5349 
5350 /// Adds line segment from the last point in the path to the specified point.
5351 /// Group: paths
5352 @scriptable
5353 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5354   nvg__appendCommands(ctx, Command.LineTo, x, y);
5355 }
5356 
5357 /// Adds line segment from the last point in the path to the specified point.
5358 /// Arguments: [x, y]*
5359 /// Group: paths
5360 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5361   enum ArgC = 2;
5362   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5363   if (args.length < ArgC) return;
5364   foreach (immutable idx; 0..args.length/ArgC) {
5365     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5366   }
5367 }
5368 
5369 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5370 /// Group: paths
5371 public void bezierTo (NVGContext ctx, in float c1x, in float c1y, in float c2x, in float c2y, in float x, in float y) nothrow @trusted @nogc {
5372   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5373 }
5374 
5375 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5376 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5377 /// Group: paths
5378 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5379   enum ArgC = 6;
5380   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5381   if (args.length < ArgC) return;
5382   foreach (immutable idx; 0..args.length/ArgC) {
5383     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5384   }
5385 }
5386 
5387 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5388 /// Group: paths
5389 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5390   immutable float x0 = ctx.commandx;
5391   immutable float y0 = ctx.commandy;
5392   nvg__appendCommands(ctx,
5393     Command.BezierTo,
5394     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5395     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5396     x, y,
5397   );
5398 }
5399 
5400 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5401 /// Arguments: [cx, cy, x, y]*
5402 /// Group: paths
5403 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5404   enum ArgC = 4;
5405   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5406   if (args.length < ArgC) return;
5407   const(float)* aptr = args.ptr;
5408   foreach (immutable idx; 0..args.length/ArgC) {
5409     immutable float x0 = ctx.commandx;
5410     immutable float y0 = ctx.commandy;
5411     immutable float cx = *aptr++;
5412     immutable float cy = *aptr++;
5413     immutable float x = *aptr++;
5414     immutable float y = *aptr++;
5415     nvg__appendCommands(ctx,
5416       Command.BezierTo,
5417       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5418       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5419       x, y,
5420     );
5421   }
5422 }
5423 
5424 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5425 /// Group: paths
5426 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5427   if (ctx.ncommands == 0) return;
5428 
5429   immutable float x0 = ctx.commandx;
5430   immutable float y0 = ctx.commandy;
5431 
5432   // handle degenerate cases
5433   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5434       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5435       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5436       radius < ctx.distTol)
5437   {
5438     ctx.lineTo(x1, y1);
5439     return;
5440   }
5441 
5442   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5443   float dx0 = x0-x1;
5444   float dy0 = y0-y1;
5445   float dx1 = x2-x1;
5446   float dy1 = y2-y1;
5447   nvg__normalize(&dx0, &dy0);
5448   nvg__normalize(&dx1, &dy1);
5449   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5450   immutable float d = radius/nvg__tanf(a/2.0f);
5451 
5452   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5453 
5454   if (d > 10000.0f) {
5455     ctx.lineTo(x1, y1);
5456     return;
5457   }
5458 
5459   float cx = void, cy = void, a0 = void, a1 = void;
5460   NVGWinding dir;
5461   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5462     cx = x1+dx0*d+dy0*radius;
5463     cy = y1+dy0*d+-dx0*radius;
5464     a0 = nvg__atan2f(dx0, -dy0);
5465     a1 = nvg__atan2f(-dx1, dy1);
5466     dir = NVGWinding.CW;
5467     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5468   } else {
5469     cx = x1+dx0*d+-dy0*radius;
5470     cy = y1+dy0*d+dx0*radius;
5471     a0 = nvg__atan2f(-dx0, dy0);
5472     a1 = nvg__atan2f(dx1, -dy1);
5473     dir = NVGWinding.CCW;
5474     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5475   }
5476 
5477   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5478 }
5479 
5480 
5481 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5482 /// Arguments: [x1, y1, x2, y2, radius]*
5483 /// Group: paths
5484 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5485   enum ArgC = 5;
5486   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5487   if (args.length < ArgC) return;
5488   if (ctx.ncommands == 0) return;
5489   const(float)* aptr = args.ptr;
5490   foreach (immutable idx; 0..args.length/ArgC) {
5491     immutable float x0 = ctx.commandx;
5492     immutable float y0 = ctx.commandy;
5493     immutable float x1 = *aptr++;
5494     immutable float y1 = *aptr++;
5495     immutable float x2 = *aptr++;
5496     immutable float y2 = *aptr++;
5497     immutable float radius = *aptr++;
5498     ctx.arcTo(x1, y1, x2, y2, radius);
5499   }
5500 }
5501 
5502 /// Closes current sub-path with a line segment.
5503 /// Group: paths
5504 @scriptable
5505 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5506   nvg__appendCommands(ctx, Command.Close);
5507 }
5508 
5509 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5510 /// Group: paths
5511 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5512   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5513 }
5514 
5515 /// Ditto.
5516 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5517   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5518 }
5519 
5520 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5521  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5522  * Angles are specified in radians.
5523  *
5524  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5525  *
5526  * Group: paths
5527  */
5528 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float cx, in float cy, in float r, in float a0, in float a1) nothrow @trusted @nogc {
5529   static assert(mode == "original" || mode == "move" || mode == "line");
5530 
5531   float[3+5*7+100] vals = void;
5532   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5533   static if (mode == "original") {
5534     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5535   } else static if (mode == "move") {
5536     enum move = Command.MoveTo;
5537   } else static if (mode == "line") {
5538     enum move = Command.LineTo;
5539   } else {
5540     static assert(0, "wtf?!");
5541   }
5542 
5543   // Clamp angles
5544   float da = a1-a0;
5545   if (dir == NVGWinding.CW) {
5546     if (nvg__absf(da) >= NVG_PI*2) {
5547       da = NVG_PI*2;
5548     } else {
5549       while (da < 0.0f) da += NVG_PI*2;
5550     }
5551   } else {
5552     if (nvg__absf(da) >= NVG_PI*2) {
5553       da = -NVG_PI*2;
5554     } else {
5555       while (da > 0.0f) da -= NVG_PI*2;
5556     }
5557   }
5558 
5559   // Split arc into max 90 degree segments.
5560   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5561   immutable float hda = (da/cast(float)ndivs)/2.0f;
5562   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5563 
5564   if (dir == NVGWinding.CCW) kappa = -kappa;
5565 
5566   int nvals = 0;
5567   float px = 0, py = 0, ptanx = 0, ptany = 0;
5568   foreach (int i; 0..ndivs+1) {
5569     immutable float a = a0+da*(i/cast(float)ndivs);
5570     immutable float dx = nvg__cosf(a);
5571     immutable float dy = nvg__sinf(a);
5572     immutable float x = cx+dx*r;
5573     immutable float y = cy+dy*r;
5574     immutable float tanx = -dy*r*kappa;
5575     immutable float tany = dx*r*kappa;
5576 
5577     if (i == 0) {
5578       if (vals.length-nvals < 3) {
5579         // flush
5580         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5581         nvals = 0;
5582       }
5583       vals.ptr[nvals++] = cast(float)move;
5584       vals.ptr[nvals++] = x;
5585       vals.ptr[nvals++] = y;
5586     } else {
5587       if (vals.length-nvals < 7) {
5588         // flush
5589         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5590         nvals = 0;
5591       }
5592       vals.ptr[nvals++] = Command.BezierTo;
5593       vals.ptr[nvals++] = px+ptanx;
5594       vals.ptr[nvals++] = py+ptany;
5595       vals.ptr[nvals++] = x-tanx;
5596       vals.ptr[nvals++] = y-tany;
5597       vals.ptr[nvals++] = x;
5598       vals.ptr[nvals++] = y;
5599     }
5600     px = x;
5601     py = y;
5602     ptanx = tanx;
5603     ptany = tany;
5604   }
5605 
5606   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5607 }
5608 
5609 
5610 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5611  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5612  * Angles are specified in radians.
5613  *
5614  * Arguments: [cx, cy, r, a0, a1]*
5615  *
5616  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5617  *
5618  * Group: paths
5619  */
5620 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5621   static assert(mode == "original" || mode == "move" || mode == "line");
5622   enum ArgC = 5;
5623   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5624   if (args.length < ArgC) return;
5625   const(float)* aptr = args.ptr;
5626   foreach (immutable idx; 0..args.length/ArgC) {
5627     immutable cx = *aptr++;
5628     immutable cy = *aptr++;
5629     immutable r = *aptr++;
5630     immutable a0 = *aptr++;
5631     immutable a1 = *aptr++;
5632     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5633   }
5634 }
5635 
5636 /// Creates new rectangle shaped sub-path.
5637 /// Group: paths
5638 @scriptable
5639 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5640   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5641     Command.MoveTo, x, y,
5642     Command.LineTo, x, y+h,
5643     Command.LineTo, x+w, y+h,
5644     Command.LineTo, x+w, y,
5645     Command.Close,
5646   );
5647 }
5648 
5649 /// Creates new rectangle shaped sub-path.
5650 /// Arguments: [x, y, w, h]*
5651 /// Group: paths
5652 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5653   enum ArgC = 4;
5654   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5655   if (args.length < ArgC) return;
5656   const(float)* aptr = args.ptr;
5657   foreach (immutable idx; 0..args.length/ArgC) {
5658     immutable x = *aptr++;
5659     immutable y = *aptr++;
5660     immutable w = *aptr++;
5661     immutable h = *aptr++;
5662     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5663       Command.MoveTo, x, y,
5664       Command.LineTo, x, y+h,
5665       Command.LineTo, x+w, y+h,
5666       Command.LineTo, x+w, y,
5667       Command.Close,
5668     );
5669   }
5670 }
5671 
5672 /// Creates new rounded rectangle shaped sub-path.
5673 /// Group: paths
5674 @scriptable
5675 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5676   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5677 }
5678 
5679 /// Creates new rounded rectangle shaped sub-path.
5680 /// Arguments: [x, y, w, h, radius]*
5681 /// Group: paths
5682 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5683   enum ArgC = 5;
5684   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5685   if (args.length < ArgC) return;
5686   const(float)* aptr = args.ptr;
5687   foreach (immutable idx; 0..args.length/ArgC) {
5688     immutable x = *aptr++;
5689     immutable y = *aptr++;
5690     immutable w = *aptr++;
5691     immutable h = *aptr++;
5692     immutable r = *aptr++;
5693     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5694   }
5695 }
5696 
5697 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5698 /// Group: paths
5699 @scriptable
5700 public void roundedRectEllipse (NVGContext ctx, in float x, in float y, in float w, in float h, in float rw, in float rh) nothrow @trusted @nogc {
5701   if (rw < 0.1f || rh < 0.1f) {
5702     rect(ctx, x, y, w, h);
5703   } else {
5704     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5705       Command.MoveTo, x+rw, y,
5706       Command.LineTo, x+w-rw, y,
5707       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5708       Command.LineTo, x+w, y+h-rh,
5709       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5710       Command.LineTo, x+rw, y+h,
5711       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5712       Command.LineTo, x, y+rh,
5713       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5714       Command.Close,
5715     );
5716   }
5717 }
5718 
5719 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5720 /// Arguments: [x, y, w, h, rw, rh]*
5721 /// Group: paths
5722 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5723   enum ArgC = 6;
5724   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5725   if (args.length < ArgC) return;
5726   const(float)* aptr = args.ptr;
5727   foreach (immutable idx; 0..args.length/ArgC) {
5728     immutable x = *aptr++;
5729     immutable y = *aptr++;
5730     immutable w = *aptr++;
5731     immutable h = *aptr++;
5732     immutable rw = *aptr++;
5733     immutable rh = *aptr++;
5734     if (rw < 0.1f || rh < 0.1f) {
5735       rect(ctx, x, y, w, h);
5736     } else {
5737       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5738         Command.MoveTo, x+rw, y,
5739         Command.LineTo, x+w-rw, y,
5740         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5741         Command.LineTo, x+w, y+h-rh,
5742         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5743         Command.LineTo, x+rw, y+h,
5744         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5745         Command.LineTo, x, y+rh,
5746         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5747         Command.Close,
5748       );
5749     }
5750   }
5751 }
5752 
5753 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5754 /// Group: paths
5755 public void roundedRectVarying (NVGContext ctx, in float x, in float y, in float w, in float h, in float radTopLeft, in float radTopRight, in float radBottomRight, in float radBottomLeft) nothrow @trusted @nogc {
5756   if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5757     ctx.rect(x, y, w, h);
5758   } else {
5759     immutable float halfw = nvg__absf(w)*0.5f;
5760     immutable float halfh = nvg__absf(h)*0.5f;
5761     immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5762     immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5763     immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5764     immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5765     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5766       Command.MoveTo, x, y+ryTL,
5767       Command.LineTo, x, y+h-ryBL,
5768       Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5769       Command.LineTo, x+w-rxBR, y+h,
5770       Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5771       Command.LineTo, x+w, y+ryTR,
5772       Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5773       Command.LineTo, x+rxTL, y,
5774       Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5775       Command.Close,
5776     );
5777   }
5778 }
5779 
5780 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5781 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5782 /// Group: paths
5783 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5784   enum ArgC = 8;
5785   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5786   if (args.length < ArgC) return;
5787   const(float)* aptr = args.ptr;
5788   foreach (immutable idx; 0..args.length/ArgC) {
5789     immutable x = *aptr++;
5790     immutable y = *aptr++;
5791     immutable w = *aptr++;
5792     immutable h = *aptr++;
5793     immutable radTopLeft = *aptr++;
5794     immutable radTopRight = *aptr++;
5795     immutable radBottomRight = *aptr++;
5796     immutable radBottomLeft = *aptr++;
5797     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5798       ctx.rect(x, y, w, h);
5799     } else {
5800       immutable float halfw = nvg__absf(w)*0.5f;
5801       immutable float halfh = nvg__absf(h)*0.5f;
5802       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5803       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5804       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5805       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5806       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5807         Command.MoveTo, x, y+ryTL,
5808         Command.LineTo, x, y+h-ryBL,
5809         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5810         Command.LineTo, x+w-rxBR, y+h,
5811         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5812         Command.LineTo, x+w, y+ryTR,
5813         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5814         Command.LineTo, x+rxTL, y,
5815         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5816         Command.Close,
5817       );
5818     }
5819   }
5820 }
5821 
5822 /// Creates new ellipse shaped sub-path.
5823 /// Group: paths
5824 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5825   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5826     Command.MoveTo, cx-rx, cy,
5827     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5828     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5829     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5830     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5831     Command.Close,
5832   );
5833 }
5834 
5835 /// Creates new ellipse shaped sub-path.
5836 /// Arguments: [cx, cy, rx, ry]*
5837 /// Group: paths
5838 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5839   enum ArgC = 4;
5840   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5841   if (args.length < ArgC) return;
5842   const(float)* aptr = args.ptr;
5843   foreach (immutable idx; 0..args.length/ArgC) {
5844     immutable cx = *aptr++;
5845     immutable cy = *aptr++;
5846     immutable rx = *aptr++;
5847     immutable ry = *aptr++;
5848     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5849       Command.MoveTo, cx-rx, cy,
5850       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5851       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5852       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5853       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5854       Command.Close,
5855     );
5856   }
5857 }
5858 
5859 /// Creates new circle shaped sub-path.
5860 /// Group: paths
5861 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5862   ctx.ellipse(cx, cy, r, r);
5863 }
5864 
5865 /// Creates new circle shaped sub-path.
5866 /// Arguments: [cx, cy, r]*
5867 /// Group: paths
5868 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5869   enum ArgC = 3;
5870   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5871   if (args.length < ArgC) return;
5872   const(float)* aptr = args.ptr;
5873   foreach (immutable idx; 0..args.length/ArgC) {
5874     immutable cx = *aptr++;
5875     immutable cy = *aptr++;
5876     immutable r = *aptr++;
5877     ctx.ellipse(cx, cy, r, r);
5878   }
5879 }
5880 
5881 // Debug function to dump cached path data.
5882 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5883   import core.stdc.stdio : printf;
5884   const(NVGpath)* path;
5885   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5886   for (int i = 0; i < ctx.cache.npaths; ++i) {
5887     path = &ctx.cache.paths[i];
5888     printf("-Path %d\n", i);
5889     if (path.nfill) {
5890       printf("-fill: %d\n", path.nfill);
5891       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5892     }
5893     if (path.nstroke) {
5894       printf("-stroke: %d\n", path.nstroke);
5895       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5896     }
5897   }
5898 }
5899 
5900 // Flatten path, prepare it for fill operation.
5901 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5902   NVGpathCache* cache = ctx.cache;
5903   NVGstate* state = nvg__getState(ctx);
5904 
5905   nvg__flattenPaths!false(ctx);
5906 
5907   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5908     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5909   } else {
5910     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5911   }
5912 
5913   cache.evenOddMode = state.evenOddMode;
5914   cache.fringeWidth = ctx.fringeWidth;
5915   cache.fillReady = true;
5916   cache.strokeReady = false;
5917   cache.clipmode = NVGClipMode.None;
5918 }
5919 
5920 // Flatten path, prepare it for stroke operation.
5921 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5922   NVGstate* state = nvg__getState(ctx);
5923   NVGpathCache* cache = ctx.cache;
5924 
5925   nvg__flattenPaths!true(ctx);
5926 
5927   immutable float scale = nvg__getAverageScale(state.xform);
5928   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5929 
5930   if (strokeWidth < ctx.fringeWidth) {
5931     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5932     // Since coverage is area, scale by alpha*alpha.
5933     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5934     cache.strokeAlphaMul = alpha*alpha;
5935     strokeWidth = ctx.fringeWidth;
5936   } else {
5937     cache.strokeAlphaMul = 1.0f;
5938   }
5939   cache.strokeWidth = strokeWidth;
5940 
5941   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5942     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5943   } else {
5944     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5945   }
5946 
5947   cache.fringeWidth = ctx.fringeWidth;
5948   cache.fillReady = false;
5949   cache.strokeReady = true;
5950   cache.clipmode = NVGClipMode.None;
5951 }
5952 
5953 /// Fills the current path with current fill style.
5954 /// Group: paths
5955 @scriptable
5956 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5957   NVGstate* state = nvg__getState(ctx);
5958 
5959   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5960     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5961     ctx.currFillHitId = ctx.pathPickId;
5962   }
5963 
5964   nvg__prepareFill(ctx);
5965 
5966   // apply global alpha
5967   NVGPaint fillPaint = state.fill;
5968   fillPaint.innerColor.a *= state.alpha;
5969   fillPaint.middleColor.a *= state.alpha;
5970   fillPaint.outerColor.a *= state.alpha;
5971 
5972   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
5973 
5974   if (ctx.recblockdraw) return;
5975 
5976   ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &fillPaint, &state.scissor, ctx.fringeWidth, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode);
5977 
5978   // count triangles
5979   foreach (int i; 0..ctx.cache.npaths) {
5980     NVGpath* path = &ctx.cache.paths[i];
5981     ctx.fillTriCount += path.nfill-2;
5982     ctx.fillTriCount += path.nstroke-2;
5983     ctx.drawCallCount += 2;
5984   }
5985 }
5986 
5987 /// Fills the current path with current stroke style.
5988 /// Group: paths
5989 @scriptable
5990 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
5991   NVGstate* state = nvg__getState(ctx);
5992 
5993   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
5994     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
5995     ctx.currStrokeHitId = ctx.pathPickId;
5996   }
5997 
5998   nvg__prepareStroke(ctx);
5999 
6000   NVGpathCache* cache = ctx.cache;
6001 
6002   NVGPaint strokePaint = state.stroke;
6003   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6004   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6005   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6006 
6007   // apply global alpha
6008   strokePaint.innerColor.a *= state.alpha;
6009   strokePaint.middleColor.a *= state.alpha;
6010   strokePaint.outerColor.a *= state.alpha;
6011 
6012   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6013 
6014   if (ctx.recblockdraw) return;
6015 
6016   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6017 
6018   // count triangles
6019   foreach (int i; 0..ctx.cache.npaths) {
6020     NVGpath* path = &ctx.cache.paths[i];
6021     ctx.strokeTriCount += path.nstroke-2;
6022     ++ctx.drawCallCount;
6023   }
6024 }
6025 
6026 /// Sets current path as clipping region.
6027 /// Group: clipping
6028 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6029   NVGstate* state = nvg__getState(ctx);
6030 
6031   if (aclipmode == NVGClipMode.None) return;
6032   if (ctx.recblockdraw) return; //???
6033 
6034   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6035 
6036   /*
6037   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6038     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6039     ctx.currFillHitId = ctx.pathPickId;
6040   }
6041   */
6042 
6043   nvg__prepareFill(ctx);
6044 
6045   // apply global alpha
6046   NVGPaint fillPaint = state.fill;
6047   fillPaint.innerColor.a *= state.alpha;
6048   fillPaint.middleColor.a *= state.alpha;
6049   fillPaint.outerColor.a *= state.alpha;
6050 
6051   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6052 
6053   ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, aclipmode, &fillPaint, &state.scissor, ctx.fringeWidth, ctx.cache.bounds.ptr, ctx.cache.paths, ctx.cache.npaths, state.evenOddMode);
6054 
6055   // count triangles
6056   foreach (int i; 0..ctx.cache.npaths) {
6057     NVGpath* path = &ctx.cache.paths[i];
6058     ctx.fillTriCount += path.nfill-2;
6059     ctx.fillTriCount += path.nstroke-2;
6060     ctx.drawCallCount += 2;
6061   }
6062 }
6063 
6064 /// Sets current path as clipping region.
6065 /// Group: clipping
6066 public alias clipFill = clip;
6067 
6068 /// Sets current path' stroke as clipping region.
6069 /// Group: clipping
6070 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6071   NVGstate* state = nvg__getState(ctx);
6072 
6073   if (aclipmode == NVGClipMode.None) return;
6074   if (ctx.recblockdraw) return; //???
6075 
6076   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6077 
6078   /*
6079   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6080     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6081     ctx.currStrokeHitId = ctx.pathPickId;
6082   }
6083   */
6084 
6085   nvg__prepareStroke(ctx);
6086 
6087   NVGpathCache* cache = ctx.cache;
6088 
6089   NVGPaint strokePaint = state.stroke;
6090   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6091   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6092   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6093 
6094   // apply global alpha
6095   strokePaint.innerColor.a *= state.alpha;
6096   strokePaint.middleColor.a *= state.alpha;
6097   strokePaint.outerColor.a *= state.alpha;
6098 
6099   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6100 
6101   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6102 
6103   // count triangles
6104   foreach (int i; 0..ctx.cache.npaths) {
6105     NVGpath* path = &ctx.cache.paths[i];
6106     ctx.strokeTriCount += path.nstroke-2;
6107     ++ctx.drawCallCount;
6108   }
6109 }
6110 
6111 
6112 // ////////////////////////////////////////////////////////////////////////// //
6113 // Picking API
6114 
6115 // most of the code is by Michael Wynne <mike@mikesspace.net>
6116 // https://github.com/memononen/nanovg/pull/230
6117 // https://github.com/MikeWW/nanovg
6118 
6119 /// Pick type query. Used in [hitTest] and [hitTestAll].
6120 /// Group: picking_api
6121 public enum NVGPickKind : ubyte {
6122   Fill = 0x01, ///
6123   Stroke = 0x02, ///
6124   All = 0x03, ///
6125 }
6126 
6127 /// Marks the fill of the current path as pickable with the specified id.
6128 /// Note that you can create and mark path without rasterizing it.
6129 /// Group: picking_api
6130 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6131   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6132   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6133   nvg__pickSceneInsert(ps, pp);
6134 }
6135 
6136 public alias currFillPickId = currFillHitId; /// Ditto.
6137 
6138 /// Marks the stroke of the current path as pickable with the specified id.
6139 /// Note that you can create and mark path without rasterizing it.
6140 /// Group: picking_api
6141 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6142   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6143   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6144   nvg__pickSceneInsert(ps, pp);
6145 }
6146 
6147 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6148 
6149 // Marks the saved path set (fill) as pickable with the specified id.
6150 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6151 // Group: picking_api
6152 /+
6153 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6154   if (svp is null) return;
6155   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6156   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6157     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6158     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6159     nvg__pickSceneInsert(ps, pp);
6160   }
6161 }
6162 +/
6163 
6164 // Marks the saved path set (stroke) as pickable with the specified id.
6165 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6166 // Group: picking_api
6167 /+
6168 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6169   if (svp is null) return;
6170   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6171   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6172     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6173     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6174     nvg__pickSceneInsert(ps, pp);
6175   }
6176 }
6177 +/
6178 
6179 private template IsGoodHitTestDG(DG) {
6180   enum IsGoodHitTestDG =
6181     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6182     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6183 }
6184 
6185 private template IsGoodHitTestInternalDG(DG) {
6186   enum IsGoodHitTestInternalDG =
6187     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6188     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6189 }
6190 
6191 /// Call delegate [dg] for each path under the specified position (in no particular order).
6192 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6193 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6194 /// Group: picking_api
6195 public int hitTestDG(bool bestOrder=false, DG) (NVGContext ctx, in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
6196   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6197 
6198   NVGpickScene* ps = ctx.pickScene;
6199   int levelwidth = 1<<(ps.nlevels-1);
6200   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6201   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6202   int npicked = 0;
6203 
6204   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6205   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6206   // checking, so path walking order is not guaranteed.
6207   static if (bestOrder) {
6208     int lastBestOrder = int.min;
6209   }
6210 
6211   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6212   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6213     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6214       //{ import core.stdc.stdio; printf("... pos=(%g,%g); bounds=(%g,%g)-(%g,%g); flags=0x%02x; kind=0x%02x; kpx=0x%02x\n", x, y, pp.bounds[0], pp.bounds[1], pp.bounds[2], pp.bounds[3], pp.flags, kind, kind&pp.flags&3); }
6215       static if (bestOrder) {
6216         // reject earlier paths
6217         if (pp.order <= lastBestOrder) continue; // not interesting
6218       }
6219       immutable uint kpx = kind&pp.flags&3;
6220       if (kpx == 0) continue; // not interesting
6221       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6222       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6223       int hit = 0;
6224       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6225       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6226       if (!hit) continue;
6227       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6228       static if (bestOrder) lastBestOrder = pp.order;
6229       static if (IsGoodHitTestDG!DG) {
6230         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6231           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6232         } else {
6233           dg(pp.id, cast(int)pp.order);
6234         }
6235       } else {
6236         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6237           if (dg(pp)) return pp.id;
6238         } else {
6239           dg(pp);
6240         }
6241       }
6242     }
6243     cellx >>= 1;
6244     celly >>= 1;
6245     levelwidth >>= 1;
6246   }
6247 
6248   return -1;
6249 }
6250 
6251 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6252 /// Returns the slice of [ids].
6253 /// Group: picking_api
6254 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6255   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6256 
6257   int npicked = 0;
6258   NVGpickScene* ps = ctx.pickScene;
6259 
6260   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6261     if (npicked == ps.cpicked) {
6262       int cpicked = ps.cpicked+ps.cpicked;
6263       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6264       if (picked is null) return true; // abort
6265       ps.cpicked = cpicked;
6266       ps.picked = picked;
6267     }
6268     ps.picked[npicked] = pp;
6269     ++npicked;
6270     return false; // go on
6271   });
6272 
6273   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6274 
6275   assert(npicked >= 0);
6276   if (npicked > ids.length) npicked = cast(int)ids.length;
6277   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6278 
6279   return ids[0..npicked];
6280 }
6281 
6282 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6283 /// Group: picking_api
6284 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6285   if (ctx.pickScene is null) return -1;
6286 
6287   int bestOrder = int.min;
6288   int bestID = -1;
6289 
6290   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6291     if (pp.order > bestOrder) {
6292       bestOrder = pp.order;
6293       bestID = pp.id;
6294     }
6295   });
6296 
6297   return bestID;
6298 }
6299 
6300 /// Returns `true` if the path with the given id contains x,y.
6301 /// Group: picking_api
6302 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6303   if (ctx.pickScene is null || id == NVGNoPick) return false;
6304 
6305   bool res = false;
6306 
6307   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6308     if (pp.id == id) {
6309       res = true;
6310       return true; // stop
6311     }
6312     return false; // continue
6313   });
6314 
6315   return res;
6316 }
6317 
6318 /// Returns `true` if the given point is within the fill of the currently defined path.
6319 /// This operation can be done before rasterizing the current path.
6320 /// Group: picking_api
6321 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6322   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6323   int oldnpoints = ps.npoints;
6324   int oldnsegments = ps.nsegments;
6325   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6326   if (pp is null) return false; // oops
6327   scope(exit) {
6328     nvg__freePickPath(ps, pp);
6329     ps.npoints = oldnpoints;
6330     ps.nsegments = oldnsegments;
6331   }
6332   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6333 }
6334 
6335 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6336 
6337 /// Returns `true` if the given point is within the stroke of the currently defined path.
6338 /// This operation can be done before rasterizing the current path.
6339 /// Group: picking_api
6340 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6341   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6342   int oldnpoints = ps.npoints;
6343   int oldnsegments = ps.nsegments;
6344   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6345   if (pp is null) return false; // oops
6346   scope(exit) {
6347     nvg__freePickPath(ps, pp);
6348     ps.npoints = oldnpoints;
6349     ps.nsegments = oldnsegments;
6350   }
6351   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6352 }
6353 
6354 
6355 nothrow @trusted @nogc {
6356 extern(C) {
6357   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6358   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6359 
6360   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6361     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6362   }
6363 }
6364 
6365 enum NVGPickEPS = 0.0001f;
6366 
6367 // Segment flags
6368 enum NVGSegmentFlags {
6369   Corner = 1,
6370   Bevel = 2,
6371   InnerBevel = 4,
6372   Cap = 8,
6373   Endcap = 16,
6374 }
6375 
6376 // Path flags
6377 enum NVGPathFlags : ushort {
6378   Fill = NVGPickKind.Fill,
6379   Stroke = NVGPickKind.Stroke,
6380   Scissor = 0x80,
6381 }
6382 
6383 struct NVGsegment {
6384   int firstPoint; // Index into NVGpickScene.points
6385   short type; // NVG_LINETO or NVG_BEZIERTO
6386   short flags; // Flags relate to the corner between the prev segment and this one.
6387   float[4] bounds;
6388   float[2] startDir; // Direction at t == 0
6389   float[2] endDir; // Direction at t == 1
6390   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6391 }
6392 
6393 struct NVGpickSubPath {
6394   short winding; // TODO: Merge to flag field
6395   bool closed; // TODO: Merge to flag field
6396 
6397   int firstSegment; // Index into NVGpickScene.segments
6398   int nsegments;
6399 
6400   float[4] bounds;
6401 
6402   NVGpickSubPath* next;
6403 }
6404 
6405 struct NVGpickPath {
6406   int id;
6407   short flags;
6408   short order;
6409   float strokeWidth;
6410   float miterLimit;
6411   short lineCap;
6412   short lineJoin;
6413   bool evenOddMode;
6414 
6415   float[4] bounds;
6416   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6417 
6418   NVGpickSubPath* subPaths;
6419   NVGpickPath* next;
6420   NVGpickPath* cellnext;
6421 }
6422 
6423 struct NVGpickScene {
6424   int npaths;
6425 
6426   NVGpickPath* paths; // Linked list of paths
6427   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6428   NVGpickPath* freePaths; // Linked list of free paths
6429 
6430   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6431 
6432   int width;
6433   int height;
6434 
6435   // Points for all path sub paths.
6436   float* points;
6437   int npoints;
6438   int cpoints;
6439 
6440   // Segments for all path sub paths
6441   NVGsegment* segments;
6442   int nsegments;
6443   int csegments;
6444 
6445   // Implicit quadtree
6446   float xdim; // Width / (1 << nlevels)
6447   float ydim; // Height / (1 << nlevels)
6448   int ncells; // Total number of cells in all levels
6449   int nlevels;
6450   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6451 
6452   // Temp storage for picking
6453   int cpicked;
6454   NVGpickPath** picked;
6455 }
6456 
6457 
6458 // bounds utilities
6459 void nvg__initBounds (ref float[4] bounds) {
6460   bounds.ptr[0] = bounds.ptr[1] = float.max;
6461   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6462 }
6463 
6464 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6465   npoints *= 2;
6466   for (int i = 0; i < npoints; i += 2) {
6467     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6468     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6469     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6470     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6471   }
6472 }
6473 
6474 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) {
6475   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6476   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6477   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6478   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6479 }
6480 
6481 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) {
6482   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6483   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6484   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6485   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6486 
6487   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6488   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6489 }
6490 
6491 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) {
6492   pragma(inline, true);
6493   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6494 }
6495 
6496 // building paths & sub paths
6497 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6498   import core.stdc..string : memcpy;
6499   if (ps.npoints+n > ps.cpoints) {
6500     import core.stdc.stdlib : realloc;
6501     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6502     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6503     if (points is null) assert(0, "NanoVega: out of memory");
6504     ps.points = points;
6505     ps.cpoints = cpoints;
6506   }
6507   int i = ps.npoints;
6508   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6509   ps.npoints += n;
6510   return i;
6511 }
6512 
6513 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6514   NVGsegment* seg = null;
6515   if (ps.nsegments == ps.csegments) {
6516     int csegments = 1+ps.csegments+(ps.csegments<<1);
6517     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6518     if (segments is null) assert(0, "NanoVega: out of memory");
6519     ps.segments = segments;
6520     ps.csegments = csegments;
6521   }
6522 
6523   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6524 
6525   seg = &ps.segments[ps.nsegments];
6526   ++ps.nsegments;
6527   seg.firstPoint = firstPoint;
6528   seg.type = cast(short)type;
6529   seg.flags = flags;
6530   ++psp.nsegments;
6531 
6532   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6533   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6534 }
6535 
6536 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6537   const(float)* points = &ps.points[seg.firstPoint*2];
6538   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6539   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6540   switch (seg.type) {
6541     case Command.LineTo:
6542       d.ptr[0] = x1-x0;
6543       d.ptr[1] = y1-y0;
6544       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6545       break;
6546     case Command.BezierTo:
6547       immutable float x2 = points[2*2+0];
6548       immutable float y2 = points[2*2+1];
6549       immutable float x3 = points[3*2+0];
6550       immutable float y3 = points[3*2+1];
6551 
6552       immutable float omt = 1.0f-t;
6553       immutable float omt2 = omt*omt;
6554       immutable float t2 = t*t;
6555 
6556       d.ptr[0] =
6557         3.0f*omt2*(x1-x0)+
6558         6.0f*omt*t*(x2-x1)+
6559         3.0f*t2*(x3-x2);
6560 
6561       d.ptr[1] =
6562         3.0f*omt2*(y1-y0)+
6563         6.0f*omt*t*(y2-y1)+
6564         3.0f*t2*(y3-y2);
6565 
6566       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6567       break;
6568     default:
6569       break;
6570   }
6571 }
6572 
6573 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6574   if (psp.firstSegment == -1) return;
6575   NVGsegment* segments = &ps.segments[psp.firstSegment];
6576   for (int s = 0; s < psp.nsegments; ++s) {
6577     NVGsegment* seg = &segments[s];
6578     const(float)* points = &ps.points[seg.firstPoint*2];
6579     if (seg.type == Command.LineTo) {
6580       nvg__initBounds(seg.bounds);
6581       nvg__expandBounds(seg.bounds, points, 2);
6582     } else {
6583       nvg__bezierBounds(points, seg.bounds);
6584     }
6585   }
6586 }
6587 
6588 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6589   if (psp.firstSegment == -1) return;
6590   immutable bool closed = psp.closed;
6591   const(float)* points = ps.points;
6592   NVGsegment* seg = null;
6593   NVGsegment* segments = &ps.segments[psp.firstSegment];
6594   int nsegments = psp.nsegments;
6595   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6596 
6597   int ns = 0; // nsupports
6598   float[32] supportingPoints = void;
6599   int firstPoint, lastPoint;
6600 
6601   if (!closed) {
6602     segments[0].flags |= NVGSegmentFlags.Cap;
6603     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6604   }
6605 
6606   for (int s = 0; s < nsegments; ++s) {
6607     seg = &segments[s];
6608     nvg__initBounds(seg.bounds);
6609 
6610     firstPoint = seg.firstPoint*2;
6611     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6612 
6613     ns = 0;
6614 
6615     // First two supporting points are either side of the start point
6616     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6617     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6618 
6619     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6620     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6621 
6622     // Second two supporting points are either side of the end point
6623     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6624     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6625 
6626     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6627     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6628 
6629     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6630       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6631       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6632 
6633       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6634 
6635       if (M2 > 0.000001f) {
6636         float scale = 1.0f/M2;
6637         if (scale > 600.0f) scale = 600.0f;
6638         seg.miterDir.ptr[0] *= scale;
6639         seg.miterDir.ptr[1] *= scale;
6640       }
6641 
6642       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6643 
6644       // Add an additional support at the corner on the other line
6645       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6646       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6647 
6648       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6649         // Set a corner as beveled if the join type is bevel or mitered and
6650         // miterLimit is hit.
6651         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6652           seg.flags |= NVGSegmentFlags.Bevel;
6653         } else {
6654           // Corner is mitered - add miter point as a support
6655           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6656           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6657         }
6658       } else if (lineJoin == NVGLineCap.Round) {
6659         // ... and at the midpoint of the corner arc
6660         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6661         nvg__normalize(&vertexN[0], &vertexN[1]);
6662 
6663         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6664         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6665       }
6666     }
6667 
6668     if (seg.flags&NVGSegmentFlags.Cap) {
6669       switch (lineCap) {
6670         case NVGLineCap.Butt:
6671           // supports for butt already added
6672           break;
6673         case NVGLineCap.Square:
6674           // square cap supports are just the original two supports moved out along the direction
6675           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6676           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6677           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6678           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6679           break;
6680         case NVGLineCap.Round:
6681           // add one additional support for the round cap along the dir
6682           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6683           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6684           break;
6685         default:
6686           break;
6687       }
6688     }
6689 
6690     if (seg.flags&NVGSegmentFlags.Endcap) {
6691       // end supporting points, either side of line
6692       int end = 4;
6693       switch(lineCap) {
6694         case NVGLineCap.Butt:
6695           // supports for butt already added
6696           break;
6697         case NVGLineCap.Square:
6698           // square cap supports are just the original two supports moved out along the direction
6699           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6700           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6701           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6702           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6703           break;
6704         case NVGLineCap.Round:
6705           // add one additional support for the round cap along the dir
6706           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6707           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6708           break;
6709         default:
6710           break;
6711       }
6712     }
6713 
6714     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6715 
6716     prevseg = seg;
6717   }
6718 }
6719 
6720 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6721   NVGpickScene* ps = nvg__pickSceneGet(context);
6722   if (ps is null) return null;
6723 
6724   int i = 0;
6725 
6726   int ncommands = cast(int)acommands.length;
6727   const(float)* commands = acommands.ptr;
6728 
6729   NVGpickPath* pp = null;
6730   NVGpickSubPath* psp = null;
6731   float[2] start = void;
6732   int firstPoint;
6733 
6734   //bool hasHoles = false;
6735   NVGpickSubPath* prev = null;
6736 
6737   float[8] points = void;
6738   float[2] inflections = void;
6739   int ninflections = 0;
6740 
6741   NVGstate* state = nvg__getState(context);
6742   float[4] totalBounds = void;
6743   NVGsegment* segments = null;
6744   const(NVGsegment)* seg = null;
6745   NVGpickSubPath *curpsp;
6746 
6747   pp = nvg__allocPickPath(ps);
6748   if (pp is null) return null;
6749 
6750   pp.id = id;
6751 
6752   bool hasPoints = false;
6753 
6754   void closeIt () {
6755     if (psp is null || !hasPoints) return;
6756     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6757       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6758       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6759     }
6760     psp.closed = true;
6761   }
6762 
6763   while (i < ncommands) {
6764     int cmd = cast(int)commands[i++];
6765     switch (cmd) {
6766       case Command.MoveTo: // one coordinate pair
6767         const(float)* tfxy = commands+i;
6768         i += 2;
6769 
6770         // new starting point
6771         start.ptr[0..2] = tfxy[0..2];
6772 
6773         // start a new path for each sub path to handle sub paths that intersect other sub paths
6774         prev = psp;
6775         psp = nvg__allocPickSubPath(ps);
6776         if (psp is null) { psp = prev; break; }
6777         psp.firstSegment = -1;
6778         psp.winding = NVGSolidity.Solid;
6779         psp.next = prev;
6780 
6781         nvg__pickSceneAddPoints(ps, tfxy, 1);
6782         hasPoints = true;
6783         break;
6784       case Command.LineTo: // one coordinate pair
6785         const(float)* tfxy = commands+i;
6786         i += 2;
6787         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6788         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6789         hasPoints = true;
6790         break;
6791       case Command.BezierTo: // three coordinate pairs
6792         const(float)* tfxy = commands+i;
6793         i += 3*2;
6794 
6795         // Split the curve at it's dx==0 or dy==0 inflection points.
6796         // Thus:
6797         //    A horizontal line only ever interects the curves once.
6798         //  and
6799         //    Finding the closest point on any curve converges more reliably.
6800 
6801         // NOTE: We could just split on dy==0 here.
6802 
6803         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6804         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6805 
6806         ninflections = 0;
6807         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6808         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6809 
6810         if (ninflections) {
6811           float previnfl = 0;
6812           float[8] pointsA = void, pointsB = void;
6813 
6814           nvg__smallsort(inflections.ptr, ninflections);
6815 
6816           for (int infl = 0; infl < ninflections; ++infl) {
6817             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6818 
6819             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6820 
6821             previnfl = inflections.ptr[infl];
6822 
6823             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6824 
6825             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6826             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6827 
6828             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6829           }
6830 
6831           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6832           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6833         } else {
6834           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6835           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6836         }
6837         hasPoints = true;
6838         break;
6839       case Command.Close:
6840         closeIt();
6841         break;
6842       case Command.Winding:
6843         psp.winding = cast(short)cast(int)commands[i];
6844         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6845         i += 1;
6846         break;
6847       default:
6848         break;
6849     }
6850   }
6851 
6852   // force-close filled paths
6853   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6854 
6855   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6856   pp.subPaths = psp;
6857   pp.strokeWidth = state.strokeWidth*0.5f;
6858   pp.miterLimit = state.miterLimit;
6859   pp.lineCap = cast(short)state.lineCap;
6860   pp.lineJoin = cast(short)state.lineJoin;
6861   pp.evenOddMode = nvg__getState(context).evenOddMode;
6862 
6863   nvg__initBounds(totalBounds);
6864 
6865   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6866     if (forStroke) {
6867       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6868     } else {
6869       nvg__pickSubPathAddFillSupports(ps, curpsp);
6870     }
6871 
6872     if (curpsp.firstSegment == -1) continue;
6873     segments = &ps.segments[curpsp.firstSegment];
6874     nvg__initBounds(curpsp.bounds);
6875     for (int s = 0; s < curpsp.nsegments; ++s) {
6876       seg = &segments[s];
6877       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6878       nvg__unionBounds(curpsp.bounds, seg.bounds);
6879     }
6880 
6881     nvg__unionBounds(totalBounds, curpsp.bounds);
6882   }
6883 
6884   // Store the scissor rect if present.
6885   if (state.scissor.extent.ptr[0] != -1.0f) {
6886     // Use points storage to store the scissor data
6887     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6888     float* scissor = &ps.points[pp.scissor*2];
6889 
6890     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6891     scissor[0..6] = state.scissor.xform.mat[];
6892     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6893 
6894     pp.flags |= NVGPathFlags.Scissor;
6895   }
6896 
6897   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6898 
6899   return pp;
6900 }
6901 
6902 
6903 // Struct management
6904 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6905   NVGpickPath* pp = ps.freePaths;
6906   if (pp !is null) {
6907     ps.freePaths = pp.next;
6908   } else {
6909     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6910   }
6911   memset(pp, 0, NVGpickPath.sizeof);
6912   return pp;
6913 }
6914 
6915 // Put a pick path and any sub paths (back) to the free lists.
6916 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6917   // Add all sub paths to the sub path free list.
6918   // Finds the end of the path sub paths, links that to the current
6919   // sub path free list head and replaces the head ptr with the
6920   // head path sub path entry.
6921   NVGpickSubPath* psp = null;
6922   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6923 
6924   if (psp) {
6925     psp.next = ps.freeSubPaths;
6926     ps.freeSubPaths = pp.subPaths;
6927   }
6928   pp.subPaths = null;
6929 
6930   // Add the path to the path freelist
6931   pp.next = ps.freePaths;
6932   ps.freePaths = pp;
6933   if (pp.next is null) ps.lastPath = pp;
6934 }
6935 
6936 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6937   NVGpickSubPath* psp = ps.freeSubPaths;
6938   if (psp !is null) {
6939     ps.freeSubPaths = psp.next;
6940   } else {
6941     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6942     if (psp is null) return null;
6943   }
6944   memset(psp, 0, NVGpickSubPath.sizeof);
6945   return psp;
6946 }
6947 
6948 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6949   psp.next = ps.freeSubPaths;
6950   ps.freeSubPaths = psp;
6951 }
6952 
6953 NVGpickScene* nvg__allocPickScene () {
6954   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6955   if (ps is null) return null;
6956   memset(ps, 0, NVGpickScene.sizeof);
6957   ps.nlevels = 5;
6958   return ps;
6959 }
6960 
6961 void nvg__deletePickScene (NVGpickScene* ps) {
6962   NVGpickPath* pp;
6963   NVGpickSubPath* psp;
6964 
6965   // Add all paths (and thus sub paths) to the free list(s).
6966   while (ps.paths !is null) {
6967     pp = ps.paths.next;
6968     nvg__freePickPath(ps, ps.paths);
6969     ps.paths = pp;
6970   }
6971 
6972   // Delete all paths
6973   while (ps.freePaths !is null) {
6974     pp = ps.freePaths;
6975     ps.freePaths = pp.next;
6976     while (pp.subPaths !is null) {
6977       psp = pp.subPaths;
6978       pp.subPaths = psp.next;
6979       free(psp);
6980     }
6981     free(pp);
6982   }
6983 
6984   // Delete all sub paths
6985   while (ps.freeSubPaths !is null) {
6986     psp = ps.freeSubPaths.next;
6987     free(ps.freeSubPaths);
6988     ps.freeSubPaths = psp;
6989   }
6990 
6991   ps.npoints = 0;
6992   ps.nsegments = 0;
6993 
6994   if (ps.levels !is null) {
6995     free(ps.levels[0]);
6996     free(ps.levels);
6997   }
6998 
6999   if (ps.picked !is null) free(ps.picked);
7000   if (ps.points !is null) free(ps.points);
7001   if (ps.segments !is null) free(ps.segments);
7002 
7003   free(ps);
7004 }
7005 
7006 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7007   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7008   return ctx.pickScene;
7009 }
7010 
7011 
7012 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7013 // points is 4 points (8 floats)
7014 // lvl1 is 3 points (6 floats)
7015 // lvl2 is 2 points (4 floats)
7016 // lvl3 is 1 point (2 floats)
7017 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7018   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7019   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7020 
7021   // Level 1
7022   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7023   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7024 
7025   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7026   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7027 
7028   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7029   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7030 
7031   // Level 2
7032   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7033   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7034 
7035   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7036   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7037 
7038   // Level 3
7039   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7040   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7041 }
7042 
7043 // Calculates a point on a bezier at point t.
7044 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7045   immutable float omt = 1-t;
7046   immutable float omt3 = omt*omt*omt;
7047   immutable float omt2 = omt*omt;
7048   immutable float t3 = t*t*t;
7049   immutable float t2 = t*t;
7050 
7051   tpoint.ptr[0] =
7052     points[0]*omt3+
7053     points[2]*3.0f*omt2*t+
7054     points[4]*3.0f*omt*t2+
7055     points[6]*t3;
7056 
7057   tpoint.ptr[1] =
7058     points[1]*omt3+
7059     points[3]*3.0f*omt2*t+
7060     points[5]*3.0f*omt*t2+
7061     points[7]*t3;
7062 }
7063 
7064 // Splits a cubic bezier curve into two parts at point t.
7065 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7066   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7067   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7068 
7069   float[6] lvl1 = void;
7070   float[4] lvl2 = void;
7071   float[2] lvl3 = void;
7072 
7073   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7074 
7075   // First half
7076   pointsA[x0] = points[x0];
7077   pointsA[y0] = points[y0];
7078 
7079   pointsA[x1] = lvl1.ptr[x0];
7080   pointsA[y1] = lvl1.ptr[y0];
7081 
7082   pointsA[x2] = lvl2.ptr[x0];
7083   pointsA[y2] = lvl2.ptr[y0];
7084 
7085   pointsA[x3] = lvl3.ptr[x0];
7086   pointsA[y3] = lvl3.ptr[y0];
7087 
7088   // Second half
7089   pointsB[x0] = lvl3.ptr[x0];
7090   pointsB[y0] = lvl3.ptr[y0];
7091 
7092   pointsB[x1] = lvl2.ptr[x1];
7093   pointsB[y1] = lvl2.ptr[y1];
7094 
7095   pointsB[x2] = lvl1.ptr[x2];
7096   pointsB[y2] = lvl1.ptr[y2];
7097 
7098   pointsB[x3] = points[x3];
7099   pointsB[y3] = points[y3];
7100 }
7101 
7102 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7103 // Appends any found inflection points to the array inflections and increments *ninflections.
7104 // So finds the parameters where dx/dt or dy/dt is 0
7105 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7106   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7107   float[2] t = void;
7108   int nvalid = *ninflections;
7109 
7110   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7111   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7112   immutable float c = 3.0f*( v1-v0 );
7113 
7114   float d = b*b-4.0f*a*c;
7115   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7116     // Zero or one root
7117     t.ptr[0] = -b/2.0f*a;
7118     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7119       inflections[nvalid] = t.ptr[0];
7120       ++nvalid;
7121     }
7122   } else if (d > NVGPickEPS) {
7123     // zero, one or two roots
7124     d = nvg__sqrtf(d);
7125 
7126     t.ptr[0] = (-b+d)/(2.0f*a);
7127     t.ptr[1] = (-b-d)/(2.0f*a);
7128 
7129     for (int i = 0; i < 2; ++i) {
7130       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7131         inflections[nvalid] = t.ptr[i];
7132         ++nvalid;
7133       }
7134     }
7135   } else {
7136     // zero roots
7137   }
7138 
7139   *ninflections = nvalid;
7140 }
7141 
7142 // Sort a small number of floats in ascending order (0 < n < 6)
7143 void nvg__smallsort (float* values, int n) {
7144   bool bSwapped = true;
7145   for (int j = 0; j < n-1 && bSwapped; ++j) {
7146     bSwapped = false;
7147     for (int i = 0; i < n-1; ++i) {
7148       if (values[i] > values[i+1]) {
7149         auto tmp = values[i];
7150         values[i] = values[i+1];
7151         values[i+1] = tmp;
7152       }
7153     }
7154   }
7155 }
7156 
7157 // Calculates the bounding rect of a given cubic bezier curve.
7158 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7159   float[4] inflections = void;
7160   int ninflections = 0;
7161   float[2] tpoint = void;
7162 
7163   nvg__initBounds(bounds);
7164 
7165   // Include start and end points in bounds
7166   nvg__expandBounds(bounds, &points[0], 1);
7167   nvg__expandBounds(bounds, &points[6], 1);
7168 
7169   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7170 
7171   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7172   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7173 
7174   foreach (immutable int i; 0..ninflections) {
7175     nvg__bezierEval(points, inflections[i], tpoint);
7176     nvg__expandBounds(bounds, tpoint.ptr, 1);
7177   }
7178 }
7179 
7180 // Checks to see if a line originating from x,y along the +ve x axis
7181 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7182 // Returns `true` on intersection.
7183 // Horizontal lines are never hit.
7184 bool nvg__intersectLine (const(float)* points, float x, float y) {
7185   immutable float x1 = points[0];
7186   immutable float y1 = points[1];
7187   immutable float x2 = points[2];
7188   immutable float y2 = points[3];
7189   immutable float d = y2-y1;
7190   if (d > NVGPickEPS || d < -NVGPickEPS) {
7191     immutable float s = (x2-x1)/d;
7192     immutable float lineX = x1+(y-y1)*s;
7193     return (lineX > x);
7194   } else {
7195     return false;
7196   }
7197 }
7198 
7199 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7200 // It is assumed that the line originates from within the bounding box of
7201 // the bezier and that the curve has no dy=0 inflection points.
7202 // Returns the number of intersections found (which is either 1 or 0).
7203 int nvg__intersectBezier (const(float)* points, float x, float y) {
7204   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7205   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7206 
7207   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7208 
7209   // Initial t guess
7210   float t = void;
7211        if (y3 != y0) t = (y-y0)/(y3-y0);
7212   else if (x3 != x0) t = (x-x0)/(x3-x0);
7213   else t = 0.5f;
7214 
7215   // A few Newton iterations
7216   for (int iter = 0; iter < 6; ++iter) {
7217     immutable float omt = 1-t;
7218     immutable float omt2 = omt*omt;
7219     immutable float t2 = t*t;
7220     immutable float omt3 = omt2*omt;
7221     immutable float t3 = t2*t;
7222 
7223     immutable float ty = y0*omt3 +
7224       y1*3.0f*omt2*t +
7225       y2*3.0f*omt*t2 +
7226       y3*t3;
7227 
7228     // Newton iteration
7229     immutable float dty = 3.0f*omt2*(y1-y0) +
7230       6.0f*omt*t*(y2-y1) +
7231       3.0f*t2*(y3-y2);
7232 
7233     // dty will never == 0 since:
7234     //  Either omt, omt2 are zero OR t2 is zero
7235     //  y0 != y1 != y2 != y3 (checked above)
7236     t = t-(ty-y)/dty;
7237   }
7238 
7239   {
7240     immutable float omt = 1-t;
7241     immutable float omt2 = omt*omt;
7242     immutable float t2 = t*t;
7243     immutable float omt3 = omt2*omt;
7244     immutable float t3 = t2*t;
7245 
7246     immutable float tx =
7247       x0*omt3+
7248       x1*3.0f*omt2*t+
7249       x2*3.0f*omt*t2+
7250       x3*t3;
7251 
7252     return (tx > x ? 1 : 0);
7253   }
7254 }
7255 
7256 // Finds the closest point on a line to a given point
7257 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7258   immutable float x1 = points[0];
7259   immutable float y1 = points[1];
7260   immutable float x2 = points[2];
7261   immutable float y2 = points[3];
7262   immutable float pqx = x2-x1;
7263   immutable float pqz = y2-y1;
7264   immutable float dx = x-x1;
7265   immutable float dz = y-y1;
7266   immutable float d = pqx*pqx+pqz*pqz;
7267   float t = pqx*dx+pqz*dz;
7268   if (d > 0) t /= d;
7269   if (t < 0) t = 0; else if (t > 1) t = 1;
7270   closest[0] = x1+t*pqx;
7271   closest[1] = y1+t*pqz;
7272   *ot = t;
7273 }
7274 
7275 // Finds the closest point on a curve for a given point (x,y).
7276 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7277 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7278   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7279   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7280 
7281   // This assumes that the curve has no dy=0 inflection points.
7282 
7283   // Initial t guess
7284   float t = 0.5f;
7285 
7286   // A few Newton iterations
7287   for (int iter = 0; iter < 6; ++iter) {
7288     immutable float omt = 1-t;
7289     immutable float omt2 = omt*omt;
7290     immutable float t2 = t*t;
7291     immutable float omt3 = omt2*omt;
7292     immutable float t3 = t2*t;
7293 
7294     immutable float ty =
7295       y0*omt3+
7296       y1*3.0f*omt2*t+
7297       y2*3.0f*omt*t2+
7298       y3*t3;
7299 
7300     immutable float tx =
7301       x0*omt3+
7302       x1*3.0f*omt2*t+
7303       x2*3.0f*omt*t2+
7304       x3*t3;
7305 
7306     // Newton iteration
7307     immutable float dty =
7308       3.0f*omt2*(y1-y0)+
7309       6.0f*omt*t*(y2-y1)+
7310       3.0f*t2*(y3-y2);
7311 
7312     immutable float ddty =
7313       6.0f*omt*(y2-2.0f*y1+y0)+
7314       6.0f*t*(y3-2.0f*y2+y1);
7315 
7316     immutable float dtx =
7317       3.0f*omt2*(x1-x0)+
7318       6.0f*omt*t*(x2-x1)+
7319       3.0f*t2*(x3-x2);
7320 
7321     immutable float ddtx =
7322       6.0f*omt*(x2-2.0f*x1+x0)+
7323       6.0f*t*(x3-2.0f*x2+x1);
7324 
7325     immutable float errorx = tx-x;
7326     immutable float errory = ty-y;
7327 
7328     immutable float n = errorx*dtx+errory*dty;
7329     if (n == 0) break;
7330 
7331     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7332     if (d != 0) t = t-n/d; else break;
7333   }
7334 
7335   t = nvg__max(0, nvg__min(1.0, t));
7336   *ot = t;
7337   {
7338     immutable float omt = 1-t;
7339     immutable float omt2 = omt*omt;
7340     immutable float t2 = t*t;
7341     immutable float omt3 = omt2*omt;
7342     immutable float t3 = t2*t;
7343 
7344     immutable float ty =
7345       y0*omt3+
7346       y1*3.0f*omt2*t+
7347       y2*3.0f*omt*t2+
7348       y3*t3;
7349 
7350     immutable float tx =
7351       x0*omt3+
7352       x1*3.0f*omt2*t+
7353       x2*3.0f*omt*t2+
7354       x3*t3;
7355 
7356     closest[0] = tx;
7357     closest[1] = ty;
7358   }
7359 }
7360 
7361 // Returns:
7362 //  1  If (x,y) is contained by the stroke of the path
7363 //  0  If (x,y) is not contained by the path.
7364 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7365   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7366   if (psp.firstSegment == -1) return 0;
7367 
7368   float[2] closest = void;
7369   float[2] d = void;
7370   float t = void;
7371 
7372   // trace a line from x,y out along the positive x axis and count the number of intersections
7373   int nsegments = psp.nsegments;
7374   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7375   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7376   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7377 
7378   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7379     if (nvg__pointInBounds(x, y, seg.bounds)) {
7380       // Line potentially hits stroke.
7381       switch (seg.type) {
7382         case Command.LineTo:
7383           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7384           break;
7385         case Command.BezierTo:
7386           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7387           break;
7388         default:
7389           continue;
7390       }
7391 
7392       d.ptr[0] = x-closest.ptr[0];
7393       d.ptr[1] = y-closest.ptr[1];
7394 
7395       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7396           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7397           (lineJoin == NVGLineCap.Round))
7398       {
7399         // Closest point is in the middle of the line/curve, at a rounded join/cap
7400         // or at a smooth join
7401         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7402         if (distSqd < strokeWidthSqd) return 1;
7403       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7404                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7405         switch (lineCap) {
7406           case NVGLineCap.Butt:
7407             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7408             immutable float dirD = (t < NVGPickEPS ?
7409               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7410                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7411             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7412             break;
7413           case NVGLineCap.Square:
7414             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7415             break;
7416           case NVGLineCap.Round:
7417             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7418             if (distSqd < strokeWidthSqd) return 1;
7419             break;
7420           default:
7421             break;
7422         }
7423       } else if (seg.flags&NVGSegmentFlags.Corner) {
7424         // Closest point is at a corner
7425         const(NVGsegment)* seg0, seg1;
7426 
7427         if (t < NVGPickEPS) {
7428           seg0 = prevseg;
7429           seg1 = seg;
7430         } else {
7431           seg0 = seg;
7432           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7433         }
7434 
7435         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7436           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7437           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7438           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7439         } else {
7440           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7441           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7442           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7443         }
7444       }
7445     }
7446   }
7447 
7448   return 0;
7449 }
7450 
7451 // Returns:
7452 //   1  If (x,y) is contained by the path and the path is solid.
7453 //  -1  If (x,y) is contained by the path and the path is a hole.
7454 //   0  If (x,y) is not contained by the path.
7455 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7456   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7457   if (psp.firstSegment == -1) return 0;
7458 
7459   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7460   int nsegments = psp.nsegments;
7461   int nintersections = 0;
7462 
7463   // trace a line from x,y out along the positive x axis and count the number of intersections
7464   for (int s = 0; s < nsegments; ++s, ++seg) {
7465     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7466         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7467         seg.bounds.ptr[2] > x)
7468     {
7469       // Line hits the box.
7470       switch (seg.type) {
7471         case Command.LineTo:
7472           if (seg.bounds.ptr[0] > x) {
7473             // line originates outside the box
7474             ++nintersections;
7475           } else {
7476             // line originates inside the box
7477             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7478           }
7479           break;
7480         case Command.BezierTo:
7481           if (seg.bounds.ptr[0] > x) {
7482             // line originates outside the box
7483             ++nintersections;
7484           } else {
7485             // line originates inside the box
7486             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7487           }
7488           break;
7489         default:
7490           break;
7491       }
7492     }
7493   }
7494 
7495   if (evenOddMode) {
7496     return nintersections;
7497   } else {
7498     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7499   }
7500 }
7501 
7502 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7503   int pickCount = 0;
7504   const(NVGpickSubPath)* psp = pp.subPaths;
7505   while (psp !is null) {
7506     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7507     psp = psp.next;
7508   }
7509   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7510 }
7511 
7512 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7513   const(NVGpickSubPath)* psp = pp.subPaths;
7514   while (psp !is null) {
7515     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7516     psp = psp.next;
7517   }
7518   return false;
7519 }
7520 
7521 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7522   if (nvg__pointInBounds(x, y, pp.bounds)) {
7523     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7524     if (pp.flags&NVGPathFlags.Scissor) {
7525       const(float)* scissor = &ps.points[pp.scissor*2];
7526       // untransform scissor translation
7527       float stx = void, sty = void;
7528       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7529       immutable float rx = x-stx;
7530       immutable float ry = y-sty;
7531       //{ import core.stdc.stdio; printf("  (1): rxy=(%g,%g); scissor=[%g,%g,%g,%g,%g] [%g,%g]!\n", rx, ry, scissor[0], scissor[1], scissor[2], scissor[3], scissor[4], scissor[5], scissor[6], scissor[7]); }
7532       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7533           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7534       {
7535         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7536         return false;
7537       }
7538     }
7539     return true;
7540   }
7541   return false;
7542 }
7543 
7544 int nvg__countBitsUsed (uint v) pure {
7545   pragma(inline, true);
7546   import core.bitop : bsr;
7547   return (v != 0 ? bsr(v)+1 : 0);
7548 }
7549 
7550 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7551   if (ps is null || pp is null) return;
7552 
7553   int[4] cellbounds;
7554   int base = ps.nlevels-1;
7555   int level;
7556   int levelwidth;
7557   int levelshift;
7558   int levelx;
7559   int levely;
7560   NVGpickPath** cell = null;
7561 
7562   // Bit tricks for inserting into an implicit quadtree.
7563 
7564   // Calc bounds of path in cells at the lowest level
7565   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7566   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7567   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7568   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7569 
7570   // Find which bits differ between the min/max x/y coords
7571   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7572   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7573 
7574   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7575   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7576   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7577   if (level < 0) level = 0;
7578   //{ import core.stdc.stdio; printf("LEVEL: %d; bounds=(%g,%g)-(%g,%g)\n", level, pp.bounds[0], pp.bounds[1], pp.bounds[2], pp.bounds[3]); }
7579   //level = 0;
7580 
7581   // Find the correct cell in the chosen level, clamping to the edges.
7582   levelwidth = 1<<level;
7583   levelshift = (ps.nlevels-level)-1;
7584   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7585   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7586 
7587   // Insert the path into the linked list at that cell.
7588   cell = &ps.levels[level][levely*levelwidth+levelx];
7589 
7590   pp.cellnext = *cell;
7591   *cell = pp;
7592 
7593   if (ps.paths is null) ps.lastPath = pp;
7594   pp.next = ps.paths;
7595   ps.paths = pp;
7596 
7597   // Store the order (depth) of the path for picking ops.
7598   pp.order = cast(short)ps.npaths;
7599   ++ps.npaths;
7600 }
7601 
7602 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7603   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7604 
7605   //NVG_PICK_DEBUG_NEWFRAME();
7606 
7607   // Return all paths & sub paths from last frame to the free list
7608   while (ps.paths !is null) {
7609     NVGpickPath* pp = ps.paths.next;
7610     nvg__freePickPath(ps, ps.paths);
7611     ps.paths = pp;
7612   }
7613 
7614   ps.paths = null;
7615   ps.npaths = 0;
7616 
7617   // Store the screen metrics for the quadtree
7618   ps.width = width;
7619   ps.height = height;
7620 
7621   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7622   ps.xdim = cast(float)width/lowestSubDiv;
7623   ps.ydim = cast(float)height/lowestSubDiv;
7624 
7625   // Allocate the quadtree if required.
7626   if (ps.levels is null) {
7627     int ncells = 1;
7628 
7629     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7630     for (int l = 0; l < ps.nlevels; ++l) {
7631       int leveldim = 1<<l;
7632       ncells += leveldim*leveldim;
7633     }
7634 
7635     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7636 
7637     int cell = 1;
7638     for (int l = 1; l < ps.nlevels; ++l) {
7639       ps.levels[l] = &ps.levels[0][cell];
7640       int leveldim = 1<<l;
7641       cell += leveldim*leveldim;
7642     }
7643 
7644     ps.ncells = ncells;
7645   }
7646   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7647 
7648   // Allocate temporary storage for nvgHitTestAll results if required.
7649   if (ps.picked is null) {
7650     ps.cpicked = 16;
7651     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7652   }
7653 
7654   ps.npoints = 0;
7655   ps.nsegments = 0;
7656 }
7657 } // nothrow @trusted @nogc
7658 
7659 
7660 /// Return outline of the current path. Returned outline is not flattened.
7661 /// Group: paths
7662 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7663   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7664 
7665   auto res = NVGPathOutline.createNew();
7666 
7667   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7668   int ncommands = cast(int)acommands.length;
7669   const(float)* commands = acommands.ptr;
7670 
7671   float cx = 0, cy = 0;
7672   float[2] start = void;
7673   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7674   float[8] bcp = void; // bezier curve points; used to calculate bounds
7675 
7676   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7677     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7678     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7679     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7680     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7681   }
7682 
7683   bool hasPoints = false;
7684 
7685   void closeIt () nothrow @trusted @nogc {
7686     if (!hasPoints) return;
7687     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7688       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7689       res.ds.putArgs(start[]);
7690       cx = start.ptr[0];
7691       cy = start.ptr[1];
7692       addToBounds(cx, cy);
7693     }
7694   }
7695 
7696   int i = 0;
7697   while (i < ncommands) {
7698     int cmd = cast(int)commands[i++];
7699     switch (cmd) {
7700       case Command.MoveTo: // one coordinate pair
7701         const(float)* tfxy = commands+i;
7702         i += 2;
7703         // add command
7704         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7705         res.ds.putArgs(tfxy[0..2]);
7706         // new starting point
7707         start.ptr[0..2] = tfxy[0..2];
7708         cx = tfxy[0];
7709         cy = tfxy[0];
7710         addToBounds(cx, cy);
7711         hasPoints = true;
7712         break;
7713       case Command.LineTo: // one coordinate pair
7714         const(float)* tfxy = commands+i;
7715         i += 2;
7716         // add command
7717         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7718         res.ds.putArgs(tfxy[0..2]);
7719         cx = tfxy[0];
7720         cy = tfxy[0];
7721         addToBounds(cx, cy);
7722         hasPoints = true;
7723         break;
7724       case Command.BezierTo: // three coordinate pairs
7725         const(float)* tfxy = commands+i;
7726         i += 3*2;
7727         // add command
7728         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7729         res.ds.putArgs(tfxy[0..6]);
7730         // bounds
7731         bcp.ptr[0] = cx;
7732         bcp.ptr[1] = cy;
7733         bcp.ptr[2..8] = tfxy[0..6];
7734         nvg__bezierBounds(bcp.ptr, totalBounds);
7735         cx = tfxy[4];
7736         cy = tfxy[5];
7737         hasPoints = true;
7738         break;
7739       case Command.Close:
7740         closeIt();
7741         hasPoints = false;
7742         break;
7743       case Command.Winding:
7744         //psp.winding = cast(short)cast(int)commands[i];
7745         i += 1;
7746         break;
7747       default:
7748         break;
7749     }
7750   }
7751 
7752   res.ds.bounds[] = totalBounds[];
7753   return res;
7754 }
7755 
7756 
7757 // ////////////////////////////////////////////////////////////////////////// //
7758 // Text
7759 
7760 /** Creates font by loading it from the disk from specified file name.
7761  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7762  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7763  *
7764  * On POSIX systems it is possible to use fontconfig font names too.
7765  * `:noaa` in font path is still allowed, but it must be the last option.
7766  *
7767  * Group: text_api
7768  */
7769 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7770   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7771 }
7772 
7773 /** Creates font by loading it from the specified memory chunk.
7774  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7775  * Won't free data on error.
7776  *
7777  * Group: text_api
7778  */
7779 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7780   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7781 }
7782 
7783 /// Add fonts from another context.
7784 /// This is more effective than reloading fonts, 'cause font data will be shared.
7785 /// Group: text_api
7786 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7787   if (ctx is null || source is null) return;
7788   ctx.fs.addFontsFrom(source.fs);
7789 }
7790 
7791 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7792 /// Group: text_api
7793 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7794   pragma(inline, true);
7795   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7796 }
7797 
7798 /// Sets the font size of current text style.
7799 /// Group: text_api
7800 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7801   pragma(inline, true);
7802   nvg__getState(ctx).fontSize = size;
7803 }
7804 
7805 /// Gets the font size of current text style.
7806 /// Group: text_api
7807 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7808   pragma(inline, true);
7809   return nvg__getState(ctx).fontSize;
7810 }
7811 
7812 /// Sets the blur of current text style.
7813 /// Group: text_api
7814 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7815   pragma(inline, true);
7816   nvg__getState(ctx).fontBlur = blur;
7817 }
7818 
7819 /// Gets the blur of current text style.
7820 /// Group: text_api
7821 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7822   pragma(inline, true);
7823   return nvg__getState(ctx).fontBlur;
7824 }
7825 
7826 /// Sets the letter spacing of current text style.
7827 /// Group: text_api
7828 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7829   pragma(inline, true);
7830   nvg__getState(ctx).letterSpacing = spacing;
7831 }
7832 
7833 /// Gets the letter spacing of current text style.
7834 /// Group: text_api
7835 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7836   pragma(inline, true);
7837   return nvg__getState(ctx).letterSpacing;
7838 }
7839 
7840 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7841 /// Group: text_api
7842 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7843   pragma(inline, true);
7844   nvg__getState(ctx).lineHeight = lineHeight;
7845 }
7846 
7847 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7848 /// Group: text_api
7849 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7850   pragma(inline, true);
7851   return nvg__getState(ctx).lineHeight;
7852 }
7853 
7854 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7855 /// Group: text_api
7856 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7857   pragma(inline, true);
7858   nvg__getState(ctx).textAlign = talign;
7859 }
7860 
7861 /// Ditto.
7862 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7863   pragma(inline, true);
7864   nvg__getState(ctx).textAlign.horizontal = h;
7865 }
7866 
7867 /// Ditto.
7868 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7869   pragma(inline, true);
7870   nvg__getState(ctx).textAlign.vertical = v;
7871 }
7872 
7873 /// Ditto.
7874 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7875   pragma(inline, true);
7876   nvg__getState(ctx).textAlign.reset(h, v);
7877 }
7878 
7879 /// Ditto.
7880 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7881   pragma(inline, true);
7882   nvg__getState(ctx).textAlign.reset(h, v);
7883 }
7884 
7885 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7886 /// Group: text_api
7887 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7888   pragma(inline, true);
7889   return nvg__getState(ctx).textAlign;
7890 }
7891 
7892 /// Sets the font face based on specified id of current text style.
7893 /// Group: text_api
7894 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7895   pragma(inline, true);
7896   nvg__getState(ctx).fontId = font;
7897 }
7898 
7899 /// Gets the font face based on specified id of current text style.
7900 /// Group: text_api
7901 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7902   pragma(inline, true);
7903   return nvg__getState(ctx).fontId;
7904 }
7905 
7906 /** Sets the font face based on specified name of current text style.
7907  *
7908  * The underlying implementation is using O(1) data structure to lookup
7909  * font names, so you probably should use this function instead of [fontFaceId]
7910  * to make your code more robust and less error-prone.
7911  *
7912  * Group: text_api
7913  */
7914 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7915   pragma(inline, true);
7916   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7917 }
7918 
7919 static if (is(typeof(&fons__nvg__toPath))) {
7920   public enum NanoVegaHasCharToPath = true; ///
7921 } else {
7922   public enum NanoVegaHasCharToPath = false; ///
7923 }
7924 
7925 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7926 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7927 /// Returns `false` if there is no such glyph, or current font is not scalable.
7928 /// Group: text_api
7929 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7930   NVGstate* state = nvg__getState(ctx);
7931   ctx.fs.fontId = state.fontId;
7932   return ctx.fs.toPath(ctx, dch, bounds);
7933 }
7934 
7935 static if (is(typeof(&fons__nvg__bounds))) {
7936   public enum NanoVegaHasCharPathBounds = true; ///
7937 } else {
7938   public enum NanoVegaHasCharPathBounds = false; ///
7939 }
7940 
7941 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7942 /// The glyph is not scaled in any way.
7943 /// Returns `false` if there is no such glyph, or current font is not scalable.
7944 /// Group: text_api
7945 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7946   NVGstate* state = nvg__getState(ctx);
7947   ctx.fs.fontId = state.fontId;
7948   return ctx.fs.getPathBounds(dch, bounds);
7949 }
7950 
7951 /** [charOutline] will return [NVGPathOutline].
7952 
7953  some usage samples:
7954 
7955  ---
7956     float[4] bounds = void;
7957 
7958     nvg.scale(0.5, 0.5);
7959     nvg.translate(500, 800);
7960     nvg.evenOddFill;
7961 
7962     nvg.newPath();
7963     nvg.charToPath('&', bounds[]);
7964     conwriteln(bounds[]);
7965     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
7966     nvg.strokeColor(NVGColor("#0f0"));
7967     nvg.strokeWidth = 3;
7968     nvg.fill();
7969     nvg.stroke();
7970     // glyph bounds
7971     nvg.newPath();
7972     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7973     nvg.strokeColor(NVGColor("#00f"));
7974     nvg.stroke();
7975 
7976     nvg.newPath();
7977     nvg.charToPath('g', bounds[]);
7978     conwriteln(bounds[]);
7979     nvg.fill();
7980     nvg.strokeColor(NVGColor("#0f0"));
7981     nvg.stroke();
7982     // glyph bounds
7983     nvg.newPath();
7984     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
7985     nvg.strokeColor(NVGColor("#00f"));
7986     nvg.stroke();
7987 
7988     nvg.newPath();
7989     nvg.moveTo(0, 0);
7990     nvg.lineTo(600, 0);
7991     nvg.strokeColor(NVGColor("#0ff"));
7992     nvg.stroke();
7993 
7994     if (auto ol = nvg.charOutline('Q')) {
7995       scope(exit) ol.kill();
7996       nvg.newPath();
7997       conwriteln("==== length: ", ol.length, " ====");
7998       foreach (const ref cmd; ol.commands) {
7999         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8000         assert(cmd.valid);
8001         final switch (cmd.code) {
8002           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8003           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8004           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8005           case cmd.Kind.BezierTo: nvg.bezierTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3], cmd.args[4], cmd.args[5]); break;
8006         }
8007       }
8008       nvg.strokeColor(NVGColor("#f00"));
8009       nvg.stroke();
8010     }
8011  ---
8012 
8013  Group: text_api
8014  */
8015 public struct NVGPathOutline {
8016 private nothrow @trusted @nogc:
8017   struct DataStore {
8018     uint rc; // refcount
8019     ubyte* data;
8020     uint used;
8021     uint size;
8022     uint ccount; // number of commands
8023     float[4] bounds = 0; /// outline bounds
8024   nothrow @trusted @nogc:
8025     void putBytes (const(void)[] b) {
8026       if (b.length == 0) return;
8027       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8028       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8029       if (used+cast(uint)b.length > size) {
8030         import core.stdc.stdlib : realloc;
8031         uint newsz = size;
8032         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8033         assert(used+cast(uint)b.length <= newsz);
8034         data = cast(ubyte*)realloc(data, newsz);
8035         if (data is null) assert(0, "NanoVega: out of memory");
8036         size = newsz;
8037       }
8038       import core.stdc..string : memcpy;
8039       memcpy(data+used, b.ptr, b.length);
8040       used += cast(uint)b.length;
8041     }
8042     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8043     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8044   }
8045 
8046   static void incRef (DataStore* ds) {
8047     pragma(inline, true);
8048     if (ds !is null) {
8049       ++ds.rc;
8050       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8051     }
8052   }
8053 
8054   static void decRef (DataStore* ds) {
8055     version(aliced) pragma(inline, true);
8056     if (ds !is null) {
8057       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8058       if (--ds.rc == 0) {
8059         import core.stdc.stdlib : free;
8060         import core.stdc..string : memset;
8061         if (ds.data !is null) free(ds.data);
8062         memset(ds, 0, DataStore.sizeof); // just in case
8063         free(ds);
8064         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8065       }
8066     }
8067   }
8068 
8069 private:
8070   static NVGPathOutline createNew () {
8071     import core.stdc.stdlib : malloc;
8072     import core.stdc..string : memset;
8073     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8074     if (ds is null) assert(0, "NanoVega: out of memory");
8075     memset(ds, 0, DataStore.sizeof);
8076     ds.rc = 1;
8077     NVGPathOutline res;
8078     res.dsaddr = cast(usize)ds;
8079     return res;
8080   }
8081 
8082 private:
8083   usize dsaddr; // fool GC
8084 
8085   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8086 
8087 public:
8088   /// commands
8089   static struct Command {
8090     ///
8091     enum Kind : ubyte {
8092       MoveTo, ///
8093       LineTo, ///
8094       QuadTo, ///
8095       BezierTo, ///
8096       End, /// no more commands (this command is not `valid`!)
8097 
8098     }
8099     Kind code; ///
8100     const(float)[] args; ///
8101     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8102 
8103     static uint arglen (Kind code) pure nothrow @safe @nogc {
8104       pragma(inline, true);
8105       return
8106         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8107         code == Kind.QuadTo ? 4 :
8108         code == Kind.BezierTo ? 6 :
8109         0;
8110     }
8111 
8112     /// perform NanoVega command with stored data.
8113     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8114       if (ctx is null) return;
8115       final switch (code) {
8116         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8117         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8118         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8119         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8120         case Kind.End: break;
8121       }
8122     }
8123 
8124     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8125     void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8126       if (ctx is null || !valid) return;
8127       float[6] pts = void;
8128       pts[0..args.length] = args[];
8129       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8130       final switch (code) {
8131         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8132         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8133         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8134         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8135         case Kind.End: break;
8136       }
8137     }
8138   }
8139 
8140 public:
8141   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8142   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8143     auto res = createNew();
8144     res.ds.putCommand(Command.Kind.MoveTo);
8145     res.ds.putArgs(x0, y0);
8146     res.ds.putCommand(Command.Kind.QuadTo);
8147     res.ds.putArgs(cx, cy, x, y);
8148     return res;
8149   }
8150 
8151   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8152   static NVGPathOutline createNewBezier (in float x1, in float y1, in float x2, in float y2, in float x3, in float y3, in float x4, in float y4) {
8153     auto res = createNew();
8154     res.ds.putCommand(Command.Kind.MoveTo);
8155     res.ds.putArgs(x1, y1);
8156     res.ds.putCommand(Command.Kind.BezierTo);
8157     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8158     return res;
8159   }
8160 
8161 public:
8162   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8163   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8164 
8165   void opAssign() (in auto ref NVGPathOutline a) {
8166     incRef(cast(DataStore*)a.dsaddr);
8167     decRef(cast(DataStore*)dsaddr);
8168     dsaddr = a.dsaddr;
8169   }
8170 
8171   /// Clear storage.
8172   void clear () {
8173     pragma(inline, true);
8174     decRef(ds);
8175     dsaddr = 0;
8176   }
8177 
8178   /// Is this outline empty?
8179   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8180 
8181   /// Returns number of commands in outline.
8182   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8183 
8184   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8185   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8186 
8187   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8188   NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8189 
8190   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8191   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8192     import core.stdc..string : memset;
8193 
8194     NVGPathOutline res;
8195     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8196 
8197     // check if we need to flatten the path
8198     if (tfm is null) {
8199       bool dowork = false;
8200       foreach (const ref cs; commands) {
8201         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8202           dowork = true;
8203           break;
8204         }
8205       }
8206       if (!dowork) { res = this; return res; } // nothing to do
8207     }
8208 
8209     NVGcontextinternal ctx;
8210     memset(&ctx, 0, ctx.sizeof);
8211     ctx.cache = nvg__allocPathCache();
8212     scope(exit) {
8213       import core.stdc.stdlib : free;
8214       nvg__deletePathCache(ctx.cache);
8215     }
8216 
8217     ctx.tessTol = 0.25f;
8218     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8219     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8220     ctx.distTol = 0.01f;
8221     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8222 
8223     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8224 
8225     // has some curves or transformations, convert path
8226     res = createNew();
8227     float[8] args = void;
8228 
8229     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8230 
8231     float lastX = float.max, lastY = float.max;
8232     bool lastWasMove = false;
8233 
8234     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8235       if (tfm !is null) tfm.point(x, y);
8236       bool isMove = (cmd == Command.Kind.MoveTo);
8237       if (isMove) {
8238         // moveto
8239         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8240       } else {
8241         // lineto
8242         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8243       }
8244       lastWasMove = isMove;
8245       lastX = x;
8246       lastY = y;
8247       res.ds.putCommand(cmd);
8248       res.ds.putArgs(x, y);
8249       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8250       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8251       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8252       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8253     }
8254 
8255     // sorry for this pasta
8256     void flattenBezier (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) nothrow @trusted @nogc {
8257       ctx.cache.npoints = 0;
8258       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8259         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8260       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8261         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8262       } else {
8263         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8264       }
8265       // add generated points
8266       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8267     }
8268 
8269     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8270       flattenBezier(
8271         x0, y0,
8272         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8273         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8274         x, y,
8275         0,
8276       );
8277     }
8278 
8279     float cx = 0, cy = 0;
8280     foreach (const ref cs; commands) {
8281       switch (cs.code) {
8282         case Command.Kind.LineTo:
8283         case Command.Kind.MoveTo:
8284           addPoint(cs.args[0], cs.args[1], cs.code);
8285           cx = cs.args[0];
8286           cy = cs.args[1];
8287           break;
8288         case Command.Kind.QuadTo:
8289           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8290           cx = cs.args[2];
8291           cy = cs.args[3];
8292           break;
8293         case Command.Kind.BezierTo:
8294           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8295           cx = cs.args[4];
8296           cy = cs.args[5];
8297           break;
8298         default:
8299           break;
8300       }
8301     }
8302 
8303     return res;
8304   }
8305 
8306   /// Returns forward range with all glyph commands.
8307   auto commands () const nothrow @trusted @nogc {
8308     static struct Range {
8309     private nothrow @trusted @nogc:
8310       usize dsaddr;
8311       uint cpos; // current position in data
8312       uint cleft; // number of commands left
8313       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8314     public:
8315       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8316       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8317       void opAssign() (in auto ref Range a) {
8318         incRef(cast(DataStore*)a.dsaddr);
8319         decRef(cast(DataStore*)dsaddr);
8320         dsaddr = a.dsaddr;
8321         cpos = a.cpos;
8322         cleft = a.cleft;
8323       }
8324       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8325       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8326       @property int length () const pure { pragma(inline, true); return cleft; }
8327       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8328       @property Command front () const {
8329         Command res = void;
8330         if (cleft > 0) {
8331           res.code = cast(Command.Kind)data[cpos];
8332           switch (res.code) {
8333             case Command.Kind.MoveTo:
8334             case Command.Kind.LineTo:
8335               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8336               break;
8337             case Command.Kind.QuadTo:
8338               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8339               break;
8340             case Command.Kind.BezierTo:
8341               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8342               break;
8343             default:
8344               res.code = Command.Kind.End;
8345               res.args = null;
8346               break;
8347           }
8348         } else {
8349           res.code = Command.Kind.End;
8350           res.args = null;
8351         }
8352         return res;
8353       }
8354       void popFront () {
8355         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8356         --cleft;
8357         switch (data[cpos]) {
8358           case Command.Kind.MoveTo:
8359           case Command.Kind.LineTo:
8360             cpos += 1+1*2*cast(uint)float.sizeof;
8361             break;
8362           case Command.Kind.QuadTo:
8363             cpos += 1+2*2*cast(uint)float.sizeof;
8364             break;
8365           case Command.Kind.BezierTo:
8366             cpos += 1+3*2*cast(uint)float.sizeof;
8367             break;
8368           default:
8369             cleft = 0;
8370             break;
8371         }
8372       }
8373     }
8374     if (dsaddr) {
8375       incRef(cast(DataStore*)dsaddr); // range anchors it
8376       return Range(dsaddr, 0, ds.ccount);
8377     } else {
8378       return Range.init;
8379     }
8380   }
8381 }
8382 
8383 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8384 
8385 /// Destroy glyph outiline and free allocated memory.
8386 /// Group: text_api
8387 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8388   pragma(inline, true);
8389   ol.clear();
8390 }
8391 
8392 static if (is(typeof(&fons__nvg__toOutline))) {
8393   public enum NanoVegaHasCharOutline = true; ///
8394 } else {
8395   public enum NanoVegaHasCharOutline = false; ///
8396 }
8397 
8398 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8399 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8400 /// Returns `null` if there is no such glyph, or current font is not scalable.
8401 /// Group: text_api
8402 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8403   import core.stdc.stdlib : malloc;
8404   import core.stdc..string : memcpy;
8405   NVGstate* state = nvg__getState(ctx);
8406   ctx.fs.fontId = state.fontId;
8407   auto oline = NVGPathOutline.createNew();
8408   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8409   return oline;
8410 }
8411 
8412 
8413 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8414   pragma(inline, true);
8415   return (cast(int)(a/d+0.5f))*d;
8416 }
8417 
8418 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8419   pragma(inline, true);
8420   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8421 }
8422 
8423 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8424   int[4] dirty = void;
8425   if (ctx.fs.validateTexture(dirty.ptr)) {
8426     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8427     // Update texture
8428     if (fontImage.valid) {
8429       int iw, ih;
8430       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8431       int x = dirty[0];
8432       int y = dirty[1];
8433       int w = dirty[2]-dirty[0];
8434       int h = dirty[3]-dirty[1];
8435       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8436     }
8437   }
8438 }
8439 
8440 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8441   int iw, ih;
8442   nvg__flushTextTexture(ctx);
8443   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8444   // if next fontImage already have a texture
8445   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8446     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8447   } else {
8448     // calculate the new font image size and create it
8449     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8450     if (iw > ih) ih *= 2; else iw *= 2;
8451     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8452     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8453     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8454       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8455       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8456     }
8457   }
8458   ++ctx.fontImageIdx;
8459   ctx.fs.resetAtlas(iw, ih);
8460   return true;
8461 }
8462 
8463 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8464   NVGstate* state = nvg__getState(ctx);
8465   NVGPaint paint = state.fill;
8466 
8467   // Render triangles.
8468   paint.image = ctx.fontImages[ctx.fontImageIdx];
8469 
8470   // Apply global alpha
8471   paint.innerColor.a *= state.alpha;
8472   paint.middleColor.a *= state.alpha;
8473   paint.outerColor.a *= state.alpha;
8474 
8475   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8476 
8477   ++ctx.drawCallCount;
8478   ctx.textTriCount += nverts/3;
8479 }
8480 
8481 /// Draws text string at specified location. Returns next x position.
8482 /// Group: text_api
8483 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8484   NVGstate* state = nvg__getState(ctx);
8485   FONSTextIter!T iter, prevIter;
8486   FONSQuad q;
8487   NVGVertex* verts;
8488   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8489   float invscale = 1.0f/scale;
8490   int cverts = 0;
8491   int nverts = 0;
8492 
8493   if (state.fontId == FONS_INVALID) return x;
8494   if (str.length == 0) return x;
8495 
8496   ctx.fs.size = state.fontSize*scale;
8497   ctx.fs.spacing = state.letterSpacing*scale;
8498   ctx.fs.blur = state.fontBlur*scale;
8499   ctx.fs.textAlign = state.textAlign;
8500   ctx.fs.fontId = state.fontId;
8501 
8502   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8503   verts = nvg__allocTempVerts(ctx, cverts);
8504   if (verts is null) return x;
8505 
8506   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8507   prevIter = iter;
8508   while (iter.next(q)) {
8509     float[4*2] c = void;
8510     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8511       if (nverts != 0) {
8512         // TODO: add back-end bit to do this just once per frame
8513         nvg__flushTextTexture(ctx);
8514         nvg__renderText(ctx, verts, nverts);
8515         nverts = 0;
8516       }
8517       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8518       iter = prevIter;
8519       iter.next(q); // try again
8520       if (iter.prevGlyphIndex < 0) {
8521         // still can not find glyph, try replacement
8522         iter = prevIter;
8523         if (!iter.getDummyChar(q)) break;
8524       }
8525     }
8526     prevIter = iter;
8527     // transform corners
8528     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8529     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8530     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8531     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8532     // create triangles
8533     if (nverts+6 <= cverts) {
8534       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8535       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8536       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8537       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8538       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8539       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8540     }
8541   }
8542 
8543   // TODO: add back-end bit to do this just once per frame
8544   if (nverts > 0) {
8545     nvg__flushTextTexture(ctx);
8546     nvg__renderText(ctx, verts, nverts);
8547   }
8548 
8549   return iter.nextx/scale;
8550 }
8551 
8552 /** Draws multi-line text string at specified location wrapped at the specified width.
8553  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8554  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8555  *
8556  * Group: text_api
8557  */
8558 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8559   NVGstate* state = nvg__getState(ctx);
8560   if (state.fontId == FONS_INVALID) return;
8561 
8562   NVGTextRow!T[2] rows;
8563   auto oldAlign = state.textAlign;
8564   scope(exit) state.textAlign = oldAlign;
8565   auto halign = state.textAlign.horizontal;
8566   float lineh = 0;
8567 
8568   ctx.textMetrics(null, null, &lineh);
8569   state.textAlign.horizontal = NVGTextAlign.H.Left;
8570   for (;;) {
8571     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8572     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8573     if (rres.length == 0) break;
8574     foreach (ref row; rres) {
8575       final switch (halign) {
8576         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8577         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8578         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8579       }
8580       y += lineh*state.lineHeight;
8581     }
8582     str = rres[$-1].rest;
8583   }
8584 }
8585 
8586 private template isGoodPositionDelegate(DG) {
8587   private DG dg;
8588   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8589              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8590     enum isGoodPositionDelegate = true;
8591   else
8592     enum isGoodPositionDelegate = false;
8593 }
8594 
8595 /** Calculates the glyph x positions of the specified text.
8596  * Measured values are returned in local coordinate space.
8597  *
8598  * Group: text_api
8599  */
8600 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8601 if (isAnyCharType!T)
8602 {
8603   if (str.length == 0 || positions.length == 0) return positions[0..0];
8604   usize posnum;
8605   auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) {
8606     positions.ptr[posnum++] = pos;
8607     return (posnum < positions.length);
8608   });
8609   return positions[0..len];
8610 }
8611 
8612 /// Ditto.
8613 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8614 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8615 {
8616   import std.traits : ReturnType;
8617   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8618 
8619   NVGstate* state = nvg__getState(ctx);
8620   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8621   float invscale = 1.0f/scale;
8622   FONSTextIter!T iter, prevIter;
8623   FONSQuad q;
8624   int npos = 0;
8625 
8626   if (str.length == 0) return 0;
8627 
8628   ctx.fs.size = state.fontSize*scale;
8629   ctx.fs.spacing = state.letterSpacing*scale;
8630   ctx.fs.blur = state.fontBlur*scale;
8631   ctx.fs.textAlign = state.textAlign;
8632   ctx.fs.fontId = state.fontId;
8633 
8634   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8635   prevIter = iter;
8636   while (iter.next(q)) {
8637     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8638       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8639       iter = prevIter;
8640       iter.next(q); // try again
8641       if (iter.prevGlyphIndex < 0) {
8642         // still can not find glyph, try replacement
8643         iter = prevIter;
8644         if (!iter.getDummyChar(q)) break;
8645       }
8646     }
8647     prevIter = iter;
8648     NVGGlyphPosition position = void; //WARNING!
8649     position.strpos = cast(usize)(iter.stringp-str.ptr);
8650     position.x = iter.x*invscale;
8651     position.minx = nvg__min(iter.x, q.x0)*invscale;
8652     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8653     ++npos;
8654     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8655   }
8656 
8657   return npos;
8658 }
8659 
8660 private template isGoodRowDelegate(CT, DG) {
8661   private DG dg;
8662   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8663              is(typeof({ NVGTextRow!CT row; dg(row); })))
8664     enum isGoodRowDelegate = true;
8665   else
8666     enum isGoodRowDelegate = false;
8667 }
8668 
8669 /** Breaks the specified text into lines.
8670  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8671  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8672  *
8673  * Group: text_api
8674  */
8675 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8676 if (isAnyCharType!T)
8677 {
8678   if (rows.length == 0) return rows;
8679   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8680   int nrow = 0;
8681   auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) {
8682     rows[nrow++] = row;
8683     return (nrow < rows.length);
8684   });
8685   return rows[0..count];
8686 }
8687 
8688 /** Breaks the specified text into lines.
8689  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8690  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8691  * Returns number of rows.
8692  *
8693  * Group: text_api
8694  */
8695 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8696 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8697 {
8698   import std.traits : ReturnType;
8699   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8700 
8701   enum NVGcodepointType : int {
8702     Space,
8703     NewLine,
8704     Char,
8705   }
8706 
8707   NVGstate* state = nvg__getState(ctx);
8708   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8709   float invscale = 1.0f/scale;
8710   FONSTextIter!T iter, prevIter;
8711   FONSQuad q;
8712   int nrows = 0;
8713   float rowStartX = 0;
8714   float rowWidth = 0;
8715   float rowMinX = 0;
8716   float rowMaxX = 0;
8717   int rowStart = 0;
8718   int rowEnd = 0;
8719   int wordStart = 0;
8720   float wordStartX = 0;
8721   float wordMinX = 0;
8722   int breakEnd = 0;
8723   float breakWidth = 0;
8724   float breakMaxX = 0;
8725   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8726   uint pcodepoint = 0;
8727 
8728   if (state.fontId == FONS_INVALID) return 0;
8729   if (str.length == 0 || dg is null) return 0;
8730 
8731   ctx.fs.size = state.fontSize*scale;
8732   ctx.fs.spacing = state.letterSpacing*scale;
8733   ctx.fs.blur = state.fontBlur*scale;
8734   ctx.fs.textAlign = state.textAlign;
8735   ctx.fs.fontId = state.fontId;
8736 
8737   breakRowWidth *= scale;
8738 
8739   enum Phase {
8740     Normal, // searching for breaking point
8741     SkipBlanks, // skip leading blanks
8742   }
8743   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8744 
8745   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8746   prevIter = iter;
8747   while (iter.next(q)) {
8748     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8749       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8750       iter = prevIter;
8751       iter.next(q); // try again
8752       if (iter.prevGlyphIndex < 0) {
8753         // still can not find glyph, try replacement
8754         iter = prevIter;
8755         if (!iter.getDummyChar(q)) break;
8756       }
8757     }
8758     prevIter = iter;
8759     switch (iter.codepoint) {
8760       case 9: // \t
8761       case 11: // \v
8762       case 12: // \f
8763       case 32: // space
8764       case 0x00a0: // NBSP
8765         type = NVGcodepointType.Space;
8766         break;
8767       case 10: // \n
8768         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8769         break;
8770       case 13: // \r
8771         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8772         break;
8773       case 0x0085: // NEL
8774       case 0x2028: // Line Separator
8775       case 0x2029: // Paragraph Separator
8776         type = NVGcodepointType.NewLine;
8777         break;
8778       default:
8779         type = NVGcodepointType.Char;
8780         break;
8781     }
8782     if (phase == Phase.SkipBlanks) {
8783       // fix row start
8784       rowStart = cast(int)(iter.stringp-str.ptr);
8785       rowEnd = rowStart;
8786       rowStartX = iter.x;
8787       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8788       rowMinX = q.x0-rowStartX;
8789       rowMaxX = q.x1-rowStartX;
8790       wordStart = rowStart;
8791       wordStartX = iter.x;
8792       wordMinX = q.x0-rowStartX;
8793       breakEnd = rowStart;
8794       breakWidth = 0.0;
8795       breakMaxX = 0.0;
8796       if (type == NVGcodepointType.Space) continue;
8797       phase = Phase.Normal;
8798     }
8799 
8800     if (type == NVGcodepointType.NewLine) {
8801       // always handle new lines
8802       NVGTextRow!T row;
8803       row..string = str;
8804       row.start = rowStart;
8805       row.end = rowEnd;
8806       row.width = rowWidth*invscale;
8807       row.minx = rowMinX*invscale;
8808       row.maxx = rowMaxX*invscale;
8809       ++nrows;
8810       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8811       phase = Phase.SkipBlanks;
8812     } else {
8813       float nextWidth = iter.nextx-rowStartX;
8814       // track last non-white space character
8815       if (type == NVGcodepointType.Char) {
8816         rowEnd = cast(int)(iter.nextp-str.ptr);
8817         rowWidth = iter.nextx-rowStartX;
8818         rowMaxX = q.x1-rowStartX;
8819       }
8820       // track last end of a word
8821       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8822         breakEnd = cast(int)(iter.stringp-str.ptr);
8823         breakWidth = rowWidth;
8824         breakMaxX = rowMaxX;
8825       }
8826       // track last beginning of a word
8827       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8828         wordStart = cast(int)(iter.stringp-str.ptr);
8829         wordStartX = iter.x;
8830         wordMinX = q.x0-rowStartX;
8831       }
8832       // break to new line when a character is beyond break width
8833       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8834         // the run length is too long, need to break to new line
8835         NVGTextRow!T row;
8836         row..string = str;
8837         if (breakEnd == rowStart) {
8838           // the current word is longer than the row length, just break it from here
8839           row.start = rowStart;
8840           row.end = cast(int)(iter.stringp-str.ptr);
8841           row.width = rowWidth*invscale;
8842           row.minx = rowMinX*invscale;
8843           row.maxx = rowMaxX*invscale;
8844           ++nrows;
8845           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8846           rowStartX = iter.x;
8847           rowStart = cast(int)(iter.stringp-str.ptr);
8848           rowEnd = cast(int)(iter.nextp-str.ptr);
8849           rowWidth = iter.nextx-rowStartX;
8850           rowMinX = q.x0-rowStartX;
8851           rowMaxX = q.x1-rowStartX;
8852           wordStart = rowStart;
8853           wordStartX = iter.x;
8854           wordMinX = q.x0-rowStartX;
8855         } else {
8856           // break the line from the end of the last word, and start new line from the beginning of the new
8857           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8858           row.start = rowStart;
8859           row.end = breakEnd;
8860           row.width = breakWidth*invscale;
8861           row.minx = rowMinX*invscale;
8862           row.maxx = breakMaxX*invscale;
8863           ++nrows;
8864           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8865           rowStartX = wordStartX;
8866           rowStart = wordStart;
8867           rowEnd = cast(int)(iter.nextp-str.ptr);
8868           rowWidth = iter.nextx-rowStartX;
8869           rowMinX = wordMinX;
8870           rowMaxX = q.x1-rowStartX;
8871           // no change to the word start
8872         }
8873         // set null break point
8874         breakEnd = rowStart;
8875         breakWidth = 0.0;
8876         breakMaxX = 0.0;
8877       }
8878     }
8879 
8880     pcodepoint = iter.codepoint;
8881     ptype = type;
8882   }
8883 
8884   // break the line from the end of the last word, and start new line from the beginning of the new
8885   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8886     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8887     NVGTextRow!T row;
8888     row..string = str;
8889     row.start = rowStart;
8890     row.end = cast(int)str.length;
8891     row.width = rowWidth*invscale;
8892     row.minx = rowMinX*invscale;
8893     row.maxx = rowMaxX*invscale;
8894     ++nrows;
8895     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8896   }
8897 
8898   return nrows;
8899 }
8900 
8901 /** Returns iterator which you can use to calculate text bounds and advancement.
8902  * This is usable when you need to do some text layouting with wrapping, to avoid
8903  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8904  * algorithm. Note that you can copy the returned struct to save iterator state.
8905  *
8906  * You can check if iterator is valid with [valid] property, put new chars with
8907  * [put] method, get current advance with [advance] property, and current
8908  * bounds with `getBounds(ref float[4] bounds)` method.
8909  *
8910  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8911  *
8912  * Group: text_api
8913  */
8914 public struct TextBoundsIterator {
8915 private:
8916   NVGContext ctx;
8917   FONSTextBoundsIterator fsiter; // fontstash iterator
8918   float scale, invscale, xscaled, yscaled;
8919   // font settings
8920   float fsSize, fsSpacing, fsBlur;
8921   int fsFontId;
8922   NVGTextAlign fsAlign;
8923 
8924 public:
8925   /// Setups iteration. Takes current font parameters from the given NanoVega context.
8926   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8927 
8928   /// Resets iteration. Takes current font parameters from the given NanoVega context.
8929   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8930     fsiter = fsiter.init;
8931     this = this.init;
8932     if (actx is null) return;
8933     NVGstate* state = nvg__getState(actx);
8934     if (state is null) return;
8935     if (state.fontId == FONS_INVALID) { ctx = null; return; }
8936 
8937     ctx = actx;
8938     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8939     invscale = 1.0f/scale;
8940 
8941     fsSize = state.fontSize*scale;
8942     fsSpacing = state.letterSpacing*scale;
8943     fsBlur = state.fontBlur*scale;
8944     fsAlign = state.textAlign;
8945     fsFontId = state.fontId;
8946     restoreFont();
8947 
8948     xscaled = ax*scale;
8949     yscaled = ay*scale;
8950     fsiter.reset(ctx.fs, xscaled, yscaled);
8951   }
8952 
8953   /// Restart iteration. Will not restore font.
8954   void restart () nothrow @trusted @nogc {
8955     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8956   }
8957 
8958   /// Restore font settings for the context.
8959   void restoreFont () nothrow @trusted @nogc {
8960     if (ctx !is null) {
8961       ctx.fs.size = fsSize;
8962       ctx.fs.spacing = fsSpacing;
8963       ctx.fs.blur = fsBlur;
8964       ctx.fs.textAlign = fsAlign;
8965       ctx.fs.fontId = fsFontId;
8966     }
8967   }
8968 
8969   /// Is this iterator valid?
8970   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
8971 
8972   /// Add chars.
8973   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
8974 
8975   /// Returns current advance
8976   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
8977 
8978   /// Returns current text bounds.
8979   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
8980     if (ctx !is null) {
8981       fsiter.getBounds(bounds);
8982       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
8983       bounds[0] *= invscale;
8984       bounds[1] *= invscale;
8985       bounds[2] *= invscale;
8986       bounds[3] *= invscale;
8987     } else {
8988       bounds[] = 0;
8989     }
8990   }
8991 
8992   /// Returns current horizontal text bounds.
8993   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
8994     if (ctx !is null) {
8995       fsiter.getHBounds(xmin, xmax);
8996       xmin *= invscale;
8997       xmax *= invscale;
8998     }
8999   }
9000 
9001   /// Returns current vertical text bounds.
9002   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9003     if (ctx !is null) {
9004       //fsiter.getVBounds(ymin, ymax);
9005       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9006       ymin *= invscale;
9007       ymax *= invscale;
9008     }
9009   }
9010 }
9011 
9012 /// Returns font line height (without line spacing), measured in local coordinate space.
9013 /// Group: text_api
9014 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9015   float res = void;
9016   ctx.textMetrics(null, null, &res);
9017   return res;
9018 }
9019 
9020 /// Returns font ascender (positive), measured in local coordinate space.
9021 /// Group: text_api
9022 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9023   float res = void;
9024   ctx.textMetrics(&res, null, null);
9025   return res;
9026 }
9027 
9028 /// Returns font descender (negative), measured in local coordinate space.
9029 /// Group: text_api
9030 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9031   float res = void;
9032   ctx.textMetrics(null, &res, null);
9033   return res;
9034 }
9035 
9036 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9037  * Measured values are returned in local coordinate space.
9038  *
9039  * Group: text_api
9040  */
9041 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9042   float[4] bnd = void;
9043   ctx.textBounds(0, 0, str, bnd[]);
9044   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9045     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9046     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9047   } else {
9048     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9049     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9050   }
9051 }
9052 
9053 /** Measures the specified text string. Returns horizontal size of the measured text.
9054  * Measured values are returned in local coordinate space.
9055  *
9056  * Group: text_api
9057  */
9058 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9059   float w = void;
9060   ctx.textExtents(str, &w, null);
9061   return w;
9062 }
9063 
9064 /** Measures the specified text string. Parameter bounds should be a float[4],
9065  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9066  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9067  * Measured values are returned in local coordinate space.
9068  *
9069  * Group: text_api
9070  */
9071 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9072 if (isAnyCharType!T)
9073 {
9074   NVGstate* state = nvg__getState(ctx);
9075 
9076   if (state.fontId == FONS_INVALID) {
9077     bounds[] = 0;
9078     return 0;
9079   }
9080 
9081   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9082   ctx.fs.size = state.fontSize*scale;
9083   ctx.fs.spacing = state.letterSpacing*scale;
9084   ctx.fs.blur = state.fontBlur*scale;
9085   ctx.fs.textAlign = state.textAlign;
9086   ctx.fs.fontId = state.fontId;
9087 
9088   float[4] b = void;
9089   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9090   immutable float invscale = 1.0f/scale;
9091   if (bounds.length) {
9092     // use line bounds for height
9093     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9094     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9095     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9096     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9097     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9098   }
9099   return width*invscale;
9100 }
9101 
9102 /// Ditto.
9103 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9104   NVGstate* state = nvg__getState(ctx);
9105   NVGTextRow!T[2] rows;
9106   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9107   float invscale = 1.0f/scale;
9108   float lineh = 0, rminy = 0, rmaxy = 0;
9109   float minx, miny, maxx, maxy;
9110 
9111   if (state.fontId == FONS_INVALID) {
9112     bounds[] = 0;
9113     return;
9114   }
9115 
9116   auto oldAlign = state.textAlign;
9117   scope(exit) state.textAlign = oldAlign;
9118   auto halign = state.textAlign.horizontal;
9119 
9120   ctx.textMetrics(null, null, &lineh);
9121   state.textAlign.horizontal = NVGTextAlign.H.Left;
9122 
9123   minx = maxx = x;
9124   miny = maxy = y;
9125 
9126   ctx.fs.size = state.fontSize*scale;
9127   ctx.fs.spacing = state.letterSpacing*scale;
9128   ctx.fs.blur = state.fontBlur*scale;
9129   ctx.fs.textAlign = state.textAlign;
9130   ctx.fs.fontId = state.fontId;
9131   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9132   rminy *= invscale;
9133   rmaxy *= invscale;
9134 
9135   for (;;) {
9136     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9137     if (rres.length == 0) break;
9138     foreach (ref row; rres) {
9139       float rminx, rmaxx, dx = 0;
9140       // horizontal bounds
9141       final switch (halign) {
9142         case NVGTextAlign.H.Left: dx = 0; break;
9143         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9144         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9145       }
9146       rminx = x+row.minx+dx;
9147       rmaxx = x+row.maxx+dx;
9148       minx = nvg__min(minx, rminx);
9149       maxx = nvg__max(maxx, rmaxx);
9150       // vertical bounds
9151       miny = nvg__min(miny, y+rminy);
9152       maxy = nvg__max(maxy, y+rmaxy);
9153       y += lineh*state.lineHeight;
9154     }
9155     str = rres[$-1].rest;
9156   }
9157 
9158   if (bounds.length) {
9159     if (bounds.length > 0) bounds.ptr[0] = minx;
9160     if (bounds.length > 1) bounds.ptr[1] = miny;
9161     if (bounds.length > 2) bounds.ptr[2] = maxx;
9162     if (bounds.length > 3) bounds.ptr[3] = maxy;
9163   }
9164 }
9165 
9166 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9167 /// Group: text_api
9168 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9169   NVGstate* state = nvg__getState(ctx);
9170 
9171   if (state.fontId == FONS_INVALID) {
9172     if (ascender !is null) *ascender *= 0;
9173     if (descender !is null) *descender *= 0;
9174     if (lineh !is null) *lineh *= 0;
9175     return;
9176   }
9177 
9178   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9179   immutable float invscale = 1.0f/scale;
9180 
9181   ctx.fs.size = state.fontSize*scale;
9182   ctx.fs.spacing = state.letterSpacing*scale;
9183   ctx.fs.blur = state.fontBlur*scale;
9184   ctx.fs.textAlign = state.textAlign;
9185   ctx.fs.fontId = state.fontId;
9186 
9187   ctx.fs.getVertMetrics(ascender, descender, lineh);
9188   if (ascender !is null) *ascender *= invscale;
9189   if (descender !is null) *descender *= invscale;
9190   if (lineh !is null) *lineh *= invscale;
9191 }
9192 
9193 
9194 // ////////////////////////////////////////////////////////////////////////// //
9195 // fontstash
9196 // ////////////////////////////////////////////////////////////////////////// //
9197 import core.stdc.stdlib : malloc, realloc, free;
9198 import core.stdc..string : memset, memcpy, strncpy, strcmp, strlen;
9199 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9200 
9201 public:
9202 // welcome to version hell!
9203 version(nanovg_force_stb_ttf) {
9204 } else {
9205   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9206 }
9207 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9208 //version(nanovg_ignore_mono);
9209 
9210 version(nanovg_force_stb_ttf) {
9211   private enum NanoVegaForceFreeType = false;
9212 } else {
9213   version (nanovg_builtin_freetype_bindings) {
9214     version(Posix) {
9215       private enum NanoVegaForceFreeType = true;
9216     } else {
9217       private enum NanoVegaForceFreeType = false;
9218     }
9219   } else {
9220     version(Posix) {
9221       private enum NanoVegaForceFreeType = true;
9222     } else {
9223       private enum NanoVegaForceFreeType = false;
9224     }
9225   }
9226 }
9227 
9228 version(nanovg_use_freetype_ii) {
9229   enum NanoVegaIsUsingSTBTTF = false;
9230   //pragma(msg, "iv.freetype: forced");
9231 } else {
9232   static if (NanoVegaForceFreeType) {
9233     enum NanoVegaIsUsingSTBTTF = false;
9234   } else {
9235     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9236       import iv.stb.ttf;
9237       enum NanoVegaIsUsingSTBTTF = true;
9238       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9239     } else static if (__traits(compiles, { import arsd.ttf; })) {
9240       import arsd.ttf;
9241       enum NanoVegaIsUsingSTBTTF = true;
9242       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9243     } else static if (__traits(compiles, { import stb_truetype; })) {
9244       import stb_truetype;
9245       enum NanoVegaIsUsingSTBTTF = true;
9246       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9247     } else static if (__traits(compiles, { import iv.freetype; })) {
9248       version (nanovg_builtin_freetype_bindings) {
9249         enum NanoVegaIsUsingSTBTTF = false;
9250         version = nanovg_builtin_freetype_bindings;
9251       } else {
9252         import iv.freetype;
9253         enum NanoVegaIsUsingSTBTTF = false;
9254       }
9255       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9256     } else {
9257       static assert(0, "no stb_ttf/iv.freetype found!");
9258     }
9259   }
9260 }
9261 
9262 
9263 // ////////////////////////////////////////////////////////////////////////// //
9264 //version = nanovg_ft_mono;
9265 
9266 /// Invald font id.
9267 /// Group: font_stash
9268 public enum FONS_INVALID = -1;
9269 
9270 public enum FONSBitmapFlag : uint {
9271   Required = 0,
9272   Optional = 1,
9273 }
9274 
9275 public enum FONSError : int {
9276   NoError = 0,
9277   AtlasFull = 1, // Font atlas is full.
9278   ScratchFull = 2, // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE.
9279   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9280   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9281 }
9282 
9283 /// Initial parameters for new FontStash.
9284 /// Group: font_stash
9285 public struct FONSParams {
9286   enum Flag : uint {
9287     ZeroTopLeft    = 0U, // default
9288     ZeroBottomLeft = 1U,
9289   }
9290   int width, height;
9291   Flag flags = Flag.ZeroTopLeft;
9292   void* userPtr;
9293   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9294   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9295   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9296   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9297   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9298 }
9299 
9300 //TODO: document this
9301 public struct FONSQuad {
9302   float x0=0, y0=0, s0=0, t0=0;
9303   float x1=0, y1=0, s1=0, t1=0;
9304 }
9305 
9306 //TODO: document this
9307 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9308   alias CharType = CT;
9309   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9310   uint codepoint;
9311   short isize, iblur;
9312   FONSContext stash;
9313   FONSfont* font;
9314   int prevGlyphIndex;
9315   const(CT)* s; // string
9316   const(CT)* n; // next
9317   const(CT)* e; // end
9318   FONSBitmapFlag bitmapOption;
9319   static if (is(CT == char)) {
9320     uint utf8state;
9321   }
9322 
9323   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9324   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9325 
9326   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9327   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9328   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9329 
9330   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9331     import core.stdc..string : memset;
9332 
9333     memset(&this, 0, this.sizeof);
9334     if (astash is null) return false;
9335 
9336     FONSstate* state = astash.getState;
9337 
9338     if (state.font < 0 || state.font >= astash.nfonts) return false;
9339     font = astash.fonts[state.font];
9340     if (font is null || font.fdata is null) return false;
9341 
9342     isize = cast(short)(state.size*10.0f);
9343     iblur = cast(short)state.blur;
9344     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9345 
9346     // align horizontally
9347     if (state.talign.left) {
9348       // empty
9349     } else if (state.talign.right) {
9350       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9351       ax -= width;
9352     } else if (state.talign.center) {
9353       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9354       ax -= width*0.5f;
9355     }
9356 
9357     // align vertically
9358     ay += astash.getVertAlign(font, state.talign, isize);
9359 
9360     x = nextx = ax;
9361     y = nexty = ay;
9362     spacing = state.spacing;
9363 
9364     if (astr.ptr is null) {
9365            static if (is(CharType == char)) astr = "";
9366       else static if (is(CharType == wchar)) astr = ""w;
9367       else static if (is(CharType == dchar)) astr = ""d;
9368       else static assert(0, "wtf?!");
9369     }
9370     s = astr.ptr;
9371     n = astr.ptr;
9372     e = astr.ptr+astr.length;
9373 
9374     codepoint = 0;
9375     prevGlyphIndex = -1;
9376     bitmapOption = abitmapOption;
9377     stash = astash;
9378 
9379     return true;
9380   }
9381 
9382   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9383     if (stash is null || font is null) return false;
9384     // get glyph and quad
9385     x = nextx;
9386     y = nexty;
9387     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9388     if (glyph !is null) {
9389       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9390       prevGlyphIndex = glyph.index;
9391       return true;
9392     } else {
9393       prevGlyphIndex = -1;
9394       return false;
9395     }
9396   }
9397 
9398   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9399     if (stash is null || font is null) return false;
9400     FONSglyph* glyph = null;
9401     static if (is(CharType == char)) {
9402       const(char)* str = this.n;
9403       this.s = this.n;
9404       if (str is this.e) return false;
9405       const(char)* e = this.e;
9406       for (; str !is e; ++str) {
9407         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9408         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9409         if (utf8state) continue;
9410         ++str; // 'cause we'll break anyway
9411         // get glyph and quad
9412         x = nextx;
9413         y = nexty;
9414         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9415         if (glyph !is null) {
9416           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9417           prevGlyphIndex = glyph.index;
9418         } else {
9419           prevGlyphIndex = -1;
9420         }
9421         break;
9422       }
9423       this.n = str;
9424     } else {
9425       const(CharType)* str = this.n;
9426       this.s = this.n;
9427       if (str is this.e) return false;
9428       codepoint = cast(uint)(*str++);
9429       if (codepoint > dchar.max) codepoint = 0xFFFD;
9430       // get glyph and quad
9431       x = nextx;
9432       y = nexty;
9433       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9434       if (glyph !is null) {
9435         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9436         prevGlyphIndex = glyph.index;
9437       } else {
9438         prevGlyphIndex = -1;
9439       }
9440       this.n = str;
9441     }
9442     return true;
9443   }
9444 }
9445 
9446 
9447 // ////////////////////////////////////////////////////////////////////////// //
9448 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9449 
9450 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9451 version(nanovg_builtin_freetype_bindings) {
9452 pragma(lib, "freetype");
9453 private extern(C) nothrow @trusted @nogc {
9454 private import core.stdc.config : c_long, c_ulong;
9455 alias FT_Pos = c_long;
9456 // config/ftconfig.h
9457 alias FT_Int16 = short;
9458 alias FT_UInt16 = ushort;
9459 alias FT_Int32 = int;
9460 alias FT_UInt32 = uint;
9461 alias FT_Fast = int;
9462 alias FT_UFast = uint;
9463 alias FT_Int64 = long;
9464 alias FT_Uint64 = ulong;
9465 // fttypes.h
9466 alias FT_Bool = ubyte;
9467 alias FT_FWord = short;
9468 alias FT_UFWord = ushort;
9469 alias FT_Char = char;
9470 alias FT_Byte = ubyte;
9471 alias FT_Bytes = FT_Byte*;
9472 alias FT_Tag = FT_UInt32;
9473 alias FT_String = char;
9474 alias FT_Short = short;
9475 alias FT_UShort = ushort;
9476 alias FT_Int = int;
9477 alias FT_UInt = uint;
9478 alias FT_Long = c_long;
9479 alias FT_ULong = c_ulong;
9480 alias FT_F2Dot14 = short;
9481 alias FT_F26Dot6 = c_long;
9482 alias FT_Fixed = c_long;
9483 alias FT_Error = int;
9484 alias FT_Pointer = void*;
9485 alias FT_Offset = usize;
9486 alias FT_PtrDist = ptrdiff_t;
9487 
9488 struct FT_UnitVector {
9489   FT_F2Dot14 x;
9490   FT_F2Dot14 y;
9491 }
9492 
9493 struct FT_Matrix {
9494   FT_Fixed xx, xy;
9495   FT_Fixed yx, yy;
9496 }
9497 
9498 struct FT_Data {
9499   const(FT_Byte)* pointer;
9500   FT_Int length;
9501 }
9502 alias FT_Face = FT_FaceRec*;
9503 struct FT_FaceRec {
9504   FT_Long num_faces;
9505   FT_Long face_index;
9506   FT_Long face_flags;
9507   FT_Long style_flags;
9508   FT_Long num_glyphs;
9509   FT_String* family_name;
9510   FT_String* style_name;
9511   FT_Int num_fixed_sizes;
9512   FT_Bitmap_Size* available_sizes;
9513   FT_Int num_charmaps;
9514   FT_CharMap* charmaps;
9515   FT_Generic generic;
9516   FT_BBox bbox;
9517   FT_UShort units_per_EM;
9518   FT_Short ascender;
9519   FT_Short descender;
9520   FT_Short height;
9521   FT_Short max_advance_width;
9522   FT_Short max_advance_height;
9523   FT_Short underline_position;
9524   FT_Short underline_thickness;
9525   FT_GlyphSlot glyph;
9526   FT_Size size;
9527   FT_CharMap charmap;
9528   FT_Driver driver;
9529   FT_Memory memory;
9530   FT_Stream stream;
9531   FT_ListRec sizes_list;
9532   FT_Generic autohint;
9533   void* extensions;
9534   FT_Face_Internal internal;
9535 }
9536 struct FT_Bitmap_Size {
9537   FT_Short height;
9538   FT_Short width;
9539   FT_Pos size;
9540   FT_Pos x_ppem;
9541   FT_Pos y_ppem;
9542 }
9543 alias FT_CharMap = FT_CharMapRec*;
9544 struct FT_CharMapRec {
9545   FT_Face face;
9546   FT_Encoding encoding;
9547   FT_UShort platform_id;
9548   FT_UShort encoding_id;
9549 }
9550 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9551 struct FT_Generic {
9552   void* data;
9553   FT_Generic_Finalizer finalizer;
9554 }
9555 struct FT_Vector {
9556   FT_Pos x;
9557   FT_Pos y;
9558 }
9559 struct FT_BBox {
9560   FT_Pos xMin, yMin;
9561   FT_Pos xMax, yMax;
9562 }
9563 alias FT_Pixel_Mode = int;
9564 enum {
9565   FT_PIXEL_MODE_NONE = 0,
9566   FT_PIXEL_MODE_MONO,
9567   FT_PIXEL_MODE_GRAY,
9568   FT_PIXEL_MODE_GRAY2,
9569   FT_PIXEL_MODE_GRAY4,
9570   FT_PIXEL_MODE_LCD,
9571   FT_PIXEL_MODE_LCD_V,
9572   FT_PIXEL_MODE_MAX
9573 }
9574 struct FT_Bitmap {
9575   uint rows;
9576   uint width;
9577   int pitch;
9578   ubyte* buffer;
9579   ushort num_grays;
9580   ubyte pixel_mode;
9581   ubyte palette_mode;
9582   void* palette;
9583 }
9584 struct FT_Outline {
9585   short n_contours;
9586   short n_points;
9587   FT_Vector* points;
9588   byte* tags;
9589   short* contours;
9590   int flags;
9591 }
9592 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9593 struct FT_GlyphSlotRec {
9594   FT_Library library;
9595   FT_Face face;
9596   FT_GlyphSlot next;
9597   FT_UInt reserved;
9598   FT_Generic generic;
9599   FT_Glyph_Metrics metrics;
9600   FT_Fixed linearHoriAdvance;
9601   FT_Fixed linearVertAdvance;
9602   FT_Vector advance;
9603   FT_Glyph_Format format;
9604   FT_Bitmap bitmap;
9605   FT_Int bitmap_left;
9606   FT_Int bitmap_top;
9607   FT_Outline outline;
9608   FT_UInt num_subglyphs;
9609   FT_SubGlyph subglyphs;
9610   void* control_data;
9611   c_long control_len;
9612   FT_Pos lsb_delta;
9613   FT_Pos rsb_delta;
9614   void* other;
9615   FT_Slot_Internal internal;
9616 }
9617 alias FT_Size = FT_SizeRec*;
9618 struct FT_SizeRec {
9619   FT_Face face;
9620   FT_Generic generic;
9621   FT_Size_Metrics metrics;
9622   FT_Size_Internal internal;
9623 }
9624 alias FT_Encoding = FT_Tag;
9625 alias FT_Face_Internal = void*;
9626 alias FT_Driver = void*;
9627 alias FT_Memory = void*;
9628 alias FT_Stream = void*;
9629 alias FT_Library = void*;
9630 alias FT_SubGlyph = void*;
9631 alias FT_Slot_Internal = void*;
9632 alias FT_Size_Internal = void*;
9633 alias FT_ListNode = FT_ListNodeRec*;
9634 alias FT_List = FT_ListRec*;
9635 struct FT_ListNodeRec {
9636   FT_ListNode prev;
9637   FT_ListNode next;
9638   void* data;
9639 }
9640 struct FT_ListRec {
9641   FT_ListNode head;
9642   FT_ListNode tail;
9643 }
9644 struct FT_Glyph_Metrics {
9645   FT_Pos width;
9646   FT_Pos height;
9647   FT_Pos horiBearingX;
9648   FT_Pos horiBearingY;
9649   FT_Pos horiAdvance;
9650   FT_Pos vertBearingX;
9651   FT_Pos vertBearingY;
9652   FT_Pos vertAdvance;
9653 }
9654 alias FT_Glyph_Format = FT_Tag;
9655 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9656   pragma(inline, true);
9657   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9658 }
9659 enum : FT_Tag {
9660   FT_GLYPH_FORMAT_NONE = 0,
9661   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9662   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9663   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9664   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9665 }
9666 struct FT_Size_Metrics {
9667   FT_UShort x_ppem;
9668   FT_UShort y_ppem;
9669 
9670   FT_Fixed x_scale;
9671   FT_Fixed y_scale;
9672 
9673   FT_Pos ascender;
9674   FT_Pos descender;
9675   FT_Pos height;
9676   FT_Pos max_advance;
9677 }
9678 enum FT_LOAD_DEFAULT = 0x0U;
9679 enum FT_LOAD_NO_SCALE = 1U<<0;
9680 enum FT_LOAD_NO_HINTING = 1U<<1;
9681 enum FT_LOAD_RENDER = 1U<<2;
9682 enum FT_LOAD_NO_BITMAP = 1U<<3;
9683 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9684 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9685 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9686 enum FT_LOAD_PEDANTIC = 1U<<7;
9687 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9688 enum FT_LOAD_NO_RECURSE = 1U<<10;
9689 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9690 enum FT_LOAD_MONOCHROME = 1U<<12;
9691 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9692 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9693 enum FT_LOAD_COLOR = 1U<<20;
9694 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9695 enum FT_FACE_FLAG_KERNING = 1U<<6;
9696 alias FT_Kerning_Mode = int;
9697 enum /*FT_Kerning_Mode*/ {
9698   FT_KERNING_DEFAULT = 0,
9699   FT_KERNING_UNFITTED,
9700   FT_KERNING_UNSCALED
9701 }
9702 extern(C) nothrow @nogc {
9703   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9704   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9705   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9706   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9707 }
9708 struct FT_Outline_Funcs {
9709   FT_Outline_MoveToFunc move_to;
9710   FT_Outline_LineToFunc line_to;
9711   FT_Outline_ConicToFunc conic_to;
9712   FT_Outline_CubicToFunc cubic_to;
9713   int shift;
9714   FT_Pos delta;
9715 }
9716 
9717 FT_Error FT_Init_FreeType (FT_Library*);
9718 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9719 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9720 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9721 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9722 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9723 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9724 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9725 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9726 }
9727 } else version(bindbc) {
9728   import bindbc.freetype;
9729   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9730   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9731   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9732 } else {
9733   import iv.freetype;
9734 }
9735 
9736 struct FONSttFontImpl {
9737   FT_Face font;
9738   bool mono; // no aa?
9739 }
9740 
9741 __gshared FT_Library ftLibrary;
9742 
9743 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9744   FT_Error ftError;
9745   //FONS_NOTUSED(context);
9746   ftError = FT_Init_FreeType(&ftLibrary);
9747   return (ftError == 0);
9748 }
9749 
9750 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9751   font.mono = v;
9752 }
9753 
9754 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9755   return font.mono;
9756 }
9757 
9758 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9759   FT_Error ftError;
9760   //font.font.userdata = stash;
9761   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9762   return ftError == 0;
9763 }
9764 
9765 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9766   *ascent = font.font.ascender;
9767   *descent = font.font.descender;
9768   *lineGap = font.font.height-(*ascent - *descent);
9769 }
9770 
9771 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9772   return size/(font.font.ascender-font.font.descender);
9773 }
9774 
9775 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9776   return FT_Get_Char_Index(font.font, codepoint);
9777 }
9778 
9779 int fons__tt_buildGlyphBitmap (FONSttFontImpl* font, int glyph, float size, float scale, int* advance, int* lsb, int* x0, int* y0, int* x1, int* y1) nothrow @trusted @nogc {
9780   FT_Error ftError;
9781   FT_GlyphSlot ftGlyph;
9782   //version(nanovg_ignore_mono) enum exflags = 0;
9783   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9784   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9785   ftError = FT_Set_Pixel_Sizes(font.font, 0, cast(FT_UInt)(size*cast(float)font.font.units_per_EM/cast(float)(font.font.ascender-font.font.descender)));
9786   if (ftError) return 0;
9787   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9788   if (ftError) return 0;
9789   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9790   if (ftError) return 0;
9791   ftGlyph = font.font.glyph;
9792   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9793   *x0 = ftGlyph.bitmap_left;
9794   *x1 = *x0+ftGlyph.bitmap.width;
9795   *y0 = -ftGlyph.bitmap_top;
9796   *y1 = *y0+ftGlyph.bitmap.rows;
9797   return 1;
9798 }
9799 
9800 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9801   FT_GlyphSlot ftGlyph = font.font.glyph;
9802   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9803   //version(nanovg_ignore_mono) enum RenderAA = true;
9804   //else version(nanovg_ft_mono) enum RenderAA = false;
9805   //else enum RenderAA = true;
9806   if (font.mono) {
9807     auto src = ftGlyph.bitmap.buffer;
9808     auto dst = output;
9809     auto spt = ftGlyph.bitmap.pitch;
9810     if (spt < 0) spt = -spt;
9811     foreach (int y; 0..ftGlyph.bitmap.rows) {
9812       ubyte count = 0, b = 0;
9813       auto s = src;
9814       auto d = dst;
9815       foreach (int x; 0..ftGlyph.bitmap.width) {
9816         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9817         *d++ = (b&0x80 ? 255 : 0);
9818       }
9819       src += spt;
9820       dst += outStride;
9821     }
9822   } else {
9823     auto src = ftGlyph.bitmap.buffer;
9824     auto dst = output;
9825     auto spt = ftGlyph.bitmap.pitch;
9826     if (spt < 0) spt = -spt;
9827     foreach (int y; 0..ftGlyph.bitmap.rows) {
9828       import core.stdc..string : memcpy;
9829       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9830       memcpy(dst, src, ftGlyph.bitmap.width);
9831       src += spt;
9832       dst += outStride;
9833     }
9834   }
9835 }
9836 
9837 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9838   FT_Vector ftKerning;
9839   version(none) {
9840     // fitted kerning
9841     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9842     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9843     return cast(int)ftKerning.x; // round up and convert to integer
9844   } else {
9845     // unfitted kerning
9846     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9847     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9848     if (FT_Set_Pixel_Sizes(font.font, 0, cast(FT_UInt)(size*cast(float)font.font.units_per_EM/cast(float)(font.font.ascender-font.font.descender)))) return 0;
9849     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9850     version(none) {
9851       if (ftKerning.x) {
9852         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9853         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9854       }
9855     }
9856     version(none) {
9857       FT_Vector kk;
9858       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9859       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9860       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9861       //assert(ftKerning.x == kadvfrac);
9862       if (ftKerning.x || kadvfrac) {
9863         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (%d) (size=%g)\n", glyph1, glyph2, ftKerning.x, cast(int)kadvfrac, cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6), cast(double)size); }
9864       }
9865       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9866       return kadvfrac/64.0f;
9867     }
9868     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9869     return ftKerning.x/64.0f;
9870   }
9871 }
9872 
9873 extern(C) nothrow @trusted @nogc {
9874   static struct OutlinerData {
9875     @disable this (this);
9876     void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9877     NVGContext vg;
9878     NVGPathOutline.DataStore* ol;
9879     FT_BBox outlineBBox;
9880   nothrow @trusted @nogc:
9881     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9882     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9883   }
9884 
9885   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9886     auto odata = cast(OutlinerData*)user;
9887     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9888     if (odata.ol !is null) {
9889       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9890       odata.ol.putArgs(odata.transx(to.x));
9891       odata.ol.putArgs(odata.transy(to.y));
9892     }
9893     return 0;
9894   }
9895 
9896   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9897     auto odata = cast(OutlinerData*)user;
9898     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9899     if (odata.ol !is null) {
9900       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9901       odata.ol.putArgs(odata.transx(to.x));
9902       odata.ol.putArgs(odata.transy(to.y));
9903     }
9904     return 0;
9905   }
9906 
9907   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9908     auto odata = cast(OutlinerData*)user;
9909     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9910     if (odata.ol !is null) {
9911       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9912       odata.ol.putArgs(odata.transx(c1.x));
9913       odata.ol.putArgs(odata.transy(c1.y));
9914       odata.ol.putArgs(odata.transx(to.x));
9915       odata.ol.putArgs(odata.transy(to.y));
9916     }
9917     return 0;
9918   }
9919 
9920   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9921     auto odata = cast(OutlinerData*)user;
9922     if (odata.vg !is null) odata.vg.bezierTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(c2.x), odata.transy(c2.y), odata.transx(to.x), odata.transy(to.y));
9923     if (odata.ol !is null) {
9924       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9925       odata.ol.putArgs(odata.transx(c1.x));
9926       odata.ol.putArgs(odata.transy(c1.y));
9927       odata.ol.putArgs(odata.transx(c2.x));
9928       odata.ol.putArgs(odata.transy(c2.y));
9929       odata.ol.putArgs(odata.transx(to.x));
9930       odata.ol.putArgs(odata.transy(to.y));
9931     }
9932     return 0;
9933   }
9934 }
9935 
9936 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9937   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9938 
9939   FT_Outline_Funcs funcs;
9940   funcs.move_to = &fons__nvg__moveto_cb;
9941   funcs.line_to = &fons__nvg__lineto_cb;
9942   funcs.conic_to = &fons__nvg__quadto_cb;
9943   funcs.cubic_to = &fons__nvg__cubicto_cb;
9944 
9945   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9946   if (err) { bounds[] = 0; return false; }
9947   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9948 
9949   FT_Outline outline = font.font.glyph.outline;
9950 
9951   OutlinerData odata;
9952   odata.vg = vg;
9953   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9954 
9955   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9956   if (err) { bounds[] = 0; return false; }
9957   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9958   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
9959   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
9960   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
9961   return true;
9962 }
9963 
9964 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
9965   FT_Outline_Funcs funcs;
9966   funcs.move_to = &fons__nvg__moveto_cb;
9967   funcs.line_to = &fons__nvg__lineto_cb;
9968   funcs.conic_to = &fons__nvg__quadto_cb;
9969   funcs.cubic_to = &fons__nvg__cubicto_cb;
9970 
9971   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9972   if (err) return false;
9973   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
9974 
9975   FT_Outline outline = font.font.glyph.outline;
9976 
9977   OutlinerData odata;
9978   odata.ol = ol;
9979   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9980 
9981   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9982   if (err) return false;
9983   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
9984   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
9985   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
9986   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
9987   return true;
9988 }
9989 
9990 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
9991   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9992 
9993   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9994   if (err) return false;
9995   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9996 
9997   FT_Outline outline = font.font.glyph.outline;
9998   FT_BBox outlineBBox;
9999   FT_Outline_Get_CBox(&outline, &outlineBBox);
10000   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10001   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10002   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10003   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10004   return true;
10005 }
10006 
10007 
10008 } else {
10009 // ////////////////////////////////////////////////////////////////////////// //
10010 // sorry
10011 import std.traits : isFunctionPointer, isDelegate;
10012 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10013   import std.traits;
10014   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10015   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10016 }
10017 
10018 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10019   try {
10020     return assumeNoThrowNoGC(t)();
10021   } catch (Exception e) {
10022     assert(0, "OOPS!");
10023   }
10024 }
10025 
10026 struct FONSttFontImpl {
10027   stbtt_fontinfo font;
10028   bool mono; // no aa?
10029 }
10030 
10031 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10032   return 1;
10033 }
10034 
10035 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10036   font.mono = v;
10037 }
10038 
10039 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10040   return font.mono;
10041 }
10042 
10043 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10044   int stbError;
10045   font.font.userdata = context;
10046   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10047   return stbError;
10048 }
10049 
10050 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10051   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10052 }
10053 
10054 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10055   float res = void;
10056   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10057   return res;
10058 }
10059 
10060 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10061   int res;
10062   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10063   return res;
10064 }
10065 
10066 int fons__tt_buildGlyphBitmap (FONSttFontImpl* font, int glyph, float size, float scale, int* advance, int* lsb, int* x0, int* y0, int* x1, int* y1) nothrow @trusted @nogc {
10067   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10068   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10069   return 1;
10070 }
10071 
10072 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10073   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10074 }
10075 
10076 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10077   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10078   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10079   float res = void;
10080   forceNoThrowNoGC({
10081     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10082     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10083   });
10084   /*
10085   if (res != 0) {
10086     { import core.stdc.stdio; printf("fres=%g; size=%g; %g (%g); rv=%g\n", res, size, res*stbtt_ScaleForMappingEmToPixels(&font.font, size), stbtt_ScaleForPixelHeight(&font.font, size*100), res*stbtt_ScaleForPixelHeight(&font.font, size*100)); }
10087   }
10088   */
10089   //k8: dunno if this is right; i guess it isn't but...
10090   return res;
10091 }
10092 
10093 // old arsd.ttf sux! ;-)
10094 static if (is(typeof(STBTT_vcubic))) {
10095 
10096 static struct OutlinerData {
10097   @disable this (this);
10098   void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
10099   NVGPathOutline.DataStore* ol;
10100 nothrow @trusted @nogc:
10101   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10102   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10103 }
10104 
10105 
10106 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10107   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10108 
10109   bool okflag = false;
10110 
10111   forceNoThrowNoGC({
10112     int x0, y0, x1, y1;
10113     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10114       bounds[] = 0;
10115       return;
10116     }
10117 
10118     if (bounds.length > 0) bounds.ptr[0] = x0;
10119     if (bounds.length > 1) bounds.ptr[1] = -y1;
10120     if (bounds.length > 2) bounds.ptr[2] = x1;
10121     if (bounds.length > 3) bounds.ptr[3] = -y0;
10122 
10123     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10124 
10125     stbtt_vertex* verts = null;
10126     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10127     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10128     if (vcount < 1) return;
10129 
10130     foreach (const ref vt; verts[0..vcount]) {
10131       switch (vt.type) {
10132         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10133         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10134         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10135         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10136         default:
10137       }
10138     }
10139 
10140     okflag = true;
10141   });
10142 
10143   return okflag;
10144 }
10145 
10146 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10147   bool okflag = false;
10148 
10149   forceNoThrowNoGC({
10150     int x0, y0, x1, y1;
10151 
10152     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10153       ol.bounds[] = 0;
10154       return;
10155     }
10156 
10157     ol.bounds.ptr[0] = x0;
10158     ol.bounds.ptr[1] = -y1;
10159     ol.bounds.ptr[2] = x1;
10160     ol.bounds.ptr[3] = -y0;
10161 
10162     stbtt_vertex* verts = null;
10163     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10164     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10165     if (vcount < 1) return;
10166 
10167     OutlinerData odata;
10168     odata.ol = ol;
10169 
10170     foreach (const ref vt; verts[0..vcount]) {
10171       switch (vt.type) {
10172         case STBTT_vmove:
10173           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10174           odata.ol.putArgs(odata.transx(vt.x));
10175           odata.ol.putArgs(odata.transy(vt.y));
10176           break;
10177         case STBTT_vline:
10178           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10179           odata.ol.putArgs(odata.transx(vt.x));
10180           odata.ol.putArgs(odata.transy(vt.y));
10181           break;
10182         case STBTT_vcurve:
10183           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10184           odata.ol.putArgs(odata.transx(vt.x));
10185           odata.ol.putArgs(odata.transy(vt.y));
10186           odata.ol.putArgs(odata.transx(vt.cx));
10187           odata.ol.putArgs(odata.transy(vt.cy));
10188           break;
10189         case STBTT_vcubic:
10190           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10191           odata.ol.putArgs(odata.transx(vt.x));
10192           odata.ol.putArgs(odata.transy(vt.y));
10193           odata.ol.putArgs(odata.transx(vt.cx));
10194           odata.ol.putArgs(odata.transy(vt.cy));
10195           odata.ol.putArgs(odata.transx(vt.cx1));
10196           odata.ol.putArgs(odata.transy(vt.cy1));
10197           break;
10198         default:
10199       }
10200     }
10201 
10202     okflag = true;
10203   });
10204 
10205   return okflag;
10206 }
10207 
10208 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10209   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10210 
10211   bool okflag = false;
10212 
10213   forceNoThrowNoGC({
10214     int x0, y0, x1, y1;
10215     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10216       if (bounds.length > 0) bounds.ptr[0] = x0;
10217       if (bounds.length > 1) bounds.ptr[1] = -y1;
10218       if (bounds.length > 2) bounds.ptr[2] = x1;
10219       if (bounds.length > 3) bounds.ptr[3] = -y0;
10220       okflag = true;
10221     } else {
10222       bounds[] = 0;
10223     }
10224   });
10225 
10226   return okflag;
10227 }
10228 
10229 } // check for old stb_ttf
10230 
10231 
10232 } // version
10233 
10234 
10235 // ////////////////////////////////////////////////////////////////////////// //
10236 private:
10237 enum FONS_SCRATCH_BUF_SIZE = 64000;
10238 enum FONS_HASH_LUT_SIZE = 256;
10239 enum FONS_INIT_FONTS = 4;
10240 enum FONS_INIT_GLYPHS = 256;
10241 enum FONS_INIT_ATLAS_NODES = 256;
10242 enum FONS_VERTEX_COUNT = 1024;
10243 enum FONS_MAX_STATES = 20;
10244 enum FONS_MAX_FALLBACKS = 20;
10245 
10246 
10247 struct FONSglyph {
10248   uint codepoint;
10249   int index;
10250   int next;
10251   short size, blur;
10252   short x0, y0, x1, y1;
10253   short xadv, xoff, yoff;
10254 }
10255 
10256 // refcounted
10257 struct FONSfontData {
10258   ubyte* data;
10259   int dataSize;
10260   bool freeData;
10261   int rc;
10262 
10263   @disable this (this); // no copies
10264   void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); }
10265 }
10266 
10267 // won't set rc to 1
10268 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10269   import core.stdc.stdlib : malloc;
10270   assert(adata !is null);
10271   assert(asize > 0);
10272   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10273   if (res is null) assert(0, "FONS: out of memory");
10274   res.data = adata;
10275   res.dataSize = asize;
10276   res.freeData = afree;
10277   res.rc = 0;
10278   return res;
10279 }
10280 
10281 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10282   pragma(inline, true);
10283   if (fd !is null) ++fd.rc;
10284 }
10285 
10286 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10287   if (fd !is null) {
10288     if (--fd.rc == 0) {
10289       import core.stdc.stdlib : free;
10290       if (fd.freeData && fd.data !is null) {
10291         free(fd.data);
10292         fd.data = null;
10293       }
10294       free(fd);
10295       fd = null;
10296     }
10297   }
10298 }
10299 
10300 // as creating and destroying fonts is a rare operation, malloc some data
10301 struct FONSfont {
10302   FONSttFontImpl font;
10303   char* name; // malloced, strz, always lowercase
10304   uint namelen;
10305   uint namehash;
10306   char* path; // malloced, strz
10307   FONSfontData* fdata;
10308   float ascender;
10309   float descender;
10310   float lineh;
10311   FONSglyph* glyphs;
10312   int cglyphs;
10313   int nglyphs;
10314   int[FONS_HASH_LUT_SIZE] lut;
10315   int[FONS_MAX_FALLBACKS] fallbacks;
10316   int nfallbacks;
10317 
10318   @disable this (this);
10319   void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); }
10320 
10321   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10322     uint hash = 5381;
10323     foreach (ubyte b; cast(const(ubyte)[])s) {
10324       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10325       hash = ((hash<<5)+hash)+b;
10326     }
10327     return hash;
10328   }
10329 
10330   // except glyphs
10331   void freeMemory () nothrow @trusted @nogc {
10332     import core.stdc.stdlib : free;
10333     if (name !is null) { free(name); name = null; }
10334     namelen = namehash = 0;
10335     if (path !is null) { free(path); path = null; }
10336     fdata.decref();
10337   }
10338 
10339   // this also calcs name hash
10340   void setName (const(char)[] aname) nothrow @trusted @nogc {
10341     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10342     import core.stdc.stdlib : realloc;
10343     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10344     namelen = cast(uint)aname.length;
10345     name = cast(char*)realloc(name, namelen+1);
10346     if (name is null) assert(0, "FONS: out of memory");
10347     if (aname.length) name[0..aname.length] = aname[];
10348     name[namelen] = 0;
10349     // lowercase it
10350     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10351     namehash = djbhash(name[0..namelen]);
10352     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10353   }
10354 
10355   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10356     import core.stdc.stdlib : realloc;
10357     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10358     path = cast(char*)realloc(path, apath.length+1);
10359     if (path is null) assert(0, "FONS: out of memory");
10360     if (apath.length) path[0..apath.length] = apath[];
10361     path[apath.length] = 0;
10362   }
10363 
10364   // this won't check hash
10365   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10366     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10367     if (namelen != aname.length) return false;
10368     const(char)* ns = name;
10369     // name part
10370     foreach (char ch; aname) {
10371       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10372       if (ch != *ns++) return false;
10373     }
10374     // done (length was checked earlier)
10375     return true;
10376   }
10377 
10378   void clear () nothrow @trusted @nogc {
10379     import core.stdc.stdlib : free;
10380     import core.stdc..string : memset;
10381     if (glyphs !is null) free(glyphs);
10382     freeMemory();
10383     memset(&this, 0, this.sizeof);
10384   }
10385 
10386   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10387     if (nglyphs+1 > cglyphs) {
10388       import core.stdc.stdlib : realloc;
10389       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10390       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10391       if (glyphs is null) assert(0, "FontStash: out of memory");
10392     }
10393     ++nglyphs;
10394     return &glyphs[nglyphs-1];
10395   }
10396 }
10397 
10398 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10399   if (font !is null) {
10400     import core.stdc.stdlib : free;
10401     font.clear();
10402     free(font);
10403     font = null;
10404   }
10405 }
10406 
10407 
10408 // ////////////////////////////////////////////////////////////////////////// //
10409 struct FONSstate {
10410   int font;
10411   NVGTextAlign talign;
10412   float size = 0;
10413   float blur = 0;
10414   float spacing = 0;
10415 }
10416 
10417 
10418 // ////////////////////////////////////////////////////////////////////////// //
10419 // atlas based on Skyline Bin Packer by Jukka Jylänki
10420 alias FONSAtlas = FONSatlasInternal*;
10421 
10422 struct FONSatlasInternal {
10423   static struct Node {
10424     short x, y, width;
10425   }
10426 
10427   int width, height;
10428   Node* nodes;
10429   int nnodes;
10430   int cnodes;
10431 
10432   @disable this (this);
10433   void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10434 
10435 nothrow @trusted @nogc:
10436   static FONSAtlas create (int w, int h, int nnodes) {
10437     import core.stdc.stdlib : malloc;
10438     import core.stdc..string : memset;
10439 
10440     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10441     if (atlas is null) assert(0, "FontStash: out of memory");
10442     memset(atlas, 0, FONSatlasInternal.sizeof);
10443 
10444     atlas.width = w;
10445     atlas.height = h;
10446 
10447     // allocate space for skyline nodes
10448     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10449     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10450     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10451     atlas.nnodes = 0;
10452     atlas.cnodes = nnodes;
10453 
10454     // init root node
10455     atlas.nodes[0].x = 0;
10456     atlas.nodes[0].y = 0;
10457     atlas.nodes[0].width = cast(short)w;
10458     ++atlas.nnodes;
10459 
10460     return atlas;
10461   }
10462 
10463   void clear () {
10464     import core.stdc.stdlib : free;
10465     import core.stdc..string : memset;
10466 
10467     if (nodes !is null) free(nodes);
10468     memset(&this, 0, this.sizeof);
10469   }
10470 
10471   void insertNode (int idx, int x, int y, int w) {
10472     if (nnodes+1 > cnodes) {
10473       import core.stdc.stdlib : realloc;
10474       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10475       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10476       if (nodes is null) assert(0, "FontStash: out of memory");
10477     }
10478     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10479     nodes[idx].x = cast(short)x;
10480     nodes[idx].y = cast(short)y;
10481     nodes[idx].width = cast(short)w;
10482     ++nnodes;
10483   }
10484 
10485   void removeNode (int idx) {
10486     if (nnodes == 0) return;
10487     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10488     --nnodes;
10489   }
10490 
10491   // insert node for empty space
10492   void expand (int w, int h) {
10493     if (w > width) insertNode(nnodes, width, 0, w-width);
10494     width = w;
10495     height = h;
10496   }
10497 
10498   void reset (int w, int h) {
10499     width = w;
10500     height = h;
10501     nnodes = 0;
10502     // init root node
10503     nodes[0].x = 0;
10504     nodes[0].y = 0;
10505     nodes[0].width = cast(short)w;
10506     ++nnodes;
10507   }
10508 
10509   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10510     insertNode(idx, x, y+h, w);
10511 
10512     // delete skyline segments that fall under the shadow of the new segment
10513     for (int i = idx+1; i < nnodes; ++i) {
10514       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10515         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10516         nodes[i].x += cast(short)shrink;
10517         nodes[i].width -= cast(short)shrink;
10518         if (nodes[i].width <= 0) {
10519           removeNode(i);
10520           --i;
10521         } else {
10522           break;
10523         }
10524       } else {
10525         break;
10526       }
10527     }
10528 
10529     // Merge same height skyline segments that are next to each other
10530     for (int i = 0; i < nnodes-1; ++i) {
10531       if (nodes[i].y == nodes[i+1].y) {
10532         nodes[i].width += nodes[i+1].width;
10533         removeNode(i+1);
10534         --i;
10535       }
10536     }
10537   }
10538 
10539   // checks if there is enough space at the location of skyline span 'i',
10540   // and return the max height of all skyline spans under that at that location,
10541   // (think tetris block being dropped at that position); or -1 if no space found
10542   int rectFits (int i, int w, int h) {
10543     int x = nodes[i].x;
10544     int y = nodes[i].y;
10545     if (x+w > width) return -1;
10546     int spaceLeft = w;
10547     while (spaceLeft > 0) {
10548       if (i == nnodes) return -1;
10549       y = nvg__max(y, nodes[i].y);
10550       if (y+h > height) return -1;
10551       spaceLeft -= nodes[i].width;
10552       ++i;
10553     }
10554     return y;
10555   }
10556 
10557   bool addRect (int rw, int rh, int* rx, int* ry) {
10558     int besth = height, bestw = width, besti = -1;
10559     int bestx = -1, besty = -1;
10560 
10561     // Bottom left fit heuristic.
10562     for (int i = 0; i < nnodes; ++i) {
10563       int y = rectFits(i, rw, rh);
10564       if (y != -1) {
10565         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10566           besti = i;
10567           bestw = nodes[i].width;
10568           besth = y+rh;
10569           bestx = nodes[i].x;
10570           besty = y;
10571         }
10572       }
10573     }
10574 
10575     if (besti == -1) return false;
10576 
10577     // perform the actual packing
10578     addSkylineLevel(besti, bestx, besty, rw, rh);
10579 
10580     *rx = bestx;
10581     *ry = besty;
10582 
10583     return true;
10584   }
10585 }
10586 
10587 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10588   if (atlas !is null) {
10589     import core.stdc.stdlib : free;
10590     atlas.clear();
10591     free(atlas);
10592     atlas = null;
10593   }
10594 }
10595 
10596 
10597 // ////////////////////////////////////////////////////////////////////////// //
10598 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10599 /// Group: font_stash
10600 public struct FONScontextInternal {
10601 private:
10602   FONSParams params;
10603   float itw, ith;
10604   ubyte* texData;
10605   int[4] dirtyRect;
10606   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10607   int cfonts; // allocated
10608   int nfonts; // used (so we can track hash table stats)
10609   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10610   int hused, hsize;// used items and total items in [hashidx]
10611   FONSAtlas atlas;
10612   ubyte* scratch;
10613   int nscratch;
10614   FONSstate[FONS_MAX_STATES] states;
10615   int nstates;
10616 
10617   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10618 
10619   @disable this (this);
10620   void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10621 
10622 private:
10623   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10624     if (s0.length != s1.length) return false;
10625     const(char)* sp0 = s0.ptr;
10626     const(char)* sp1 = s1.ptr;
10627     foreach (immutable _; 0..s0.length) {
10628       char c0 = *sp0++;
10629       char c1 = *sp1++;
10630       if (c0 != c1) {
10631         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10632         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10633         if (c0 != c1) return false;
10634       }
10635     }
10636     return true;
10637   }
10638 
10639   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc {
10640     pragma(inline, true);
10641     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10642   }
10643 
10644   // simple linear probing; returns [FONS_INVALID] if not found
10645   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10646     if (nfonts == 0) return FONS_INVALID;
10647     auto nhash = FONSfont.djbhash(name);
10648     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10649     auto res = nhash%hsize;
10650     // hash will never be 100% full, so this loop is safe
10651     for (;;) {
10652       int idx = hashidx[res];
10653       if (idx == -1) break;
10654       auto font = fonts[idx];
10655       if (font is null) assert(0, "FONS internal error");
10656       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10657       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10658       res = (res+1)%hsize;
10659     }
10660     return FONS_INVALID;
10661   }
10662 
10663   // should be called $(B before) freeing `fonts[fidx]`
10664   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10665     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10666     if (fonts[fidx] is null) assert(0, "FONS internal error");
10667     if (hused != nfonts) assert(0, "FONS internal error");
10668     auto nhash = fonts[fidx].namehash;
10669     auto res = nhash%hsize;
10670     // hash will never be 100% full, so this loop is safe
10671     for (;;) {
10672       int idx = hashidx[res];
10673       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10674       if (idx == fidx) {
10675         // i found her! copy rest here
10676         int nidx = (res+1)%hsize;
10677         for (;;) {
10678           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10679           res = nidx;
10680           nidx = (nidx+1)%hsize;
10681         }
10682         return;
10683       }
10684       res = (res+1)%hsize;
10685     }
10686   }
10687 
10688   // add font with the given index to hash
10689   // prerequisite: font should not exists in hash
10690   void addIndexToHash (int idx) nothrow @trusted @nogc {
10691     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10692     if (fonts[idx] is null) assert(0, "FONS internal error");
10693     import core.stdc.stdlib : realloc;
10694     auto nhash = fonts[idx].namehash;
10695     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10696     // allocate new hash table if there was none
10697     if (hsize == 0) {
10698       enum InitSize = 256;
10699       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10700       if (newlist is null) assert(0, "FONS: out of memory");
10701       newlist[0..InitSize] = -1;
10702       hsize = InitSize;
10703       hused = 0;
10704       hashidx = newlist;
10705     }
10706     int res = cast(int)(nhash%hsize);
10707     // need to rehash? we want our hash table 50% full at max
10708     if (hashidx[res] != -1 && hused >= hsize/2) {
10709       uint nsz = hsize*2;
10710       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10711       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10712       if (newlist is null) assert(0, "FONS: out of memory");
10713       newlist[0..nsz] = -1;
10714       hused = 0;
10715       // rehash
10716       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10717         if (ff is null) continue;
10718         // find slot for this font (guaranteed to have one)
10719         uint newslot = ff.namehash%nsz;
10720         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10721         newlist[newslot] = cast(int)fidx;
10722         ++hused;
10723       }
10724       hsize = nsz;
10725       hashidx = newlist;
10726       // we added everything, including [idx], so nothing more to do here
10727     } else {
10728       // find slot (guaranteed to have one)
10729       while (hashidx[res] != -1) res = (res+1)%hsize;
10730       // i found her!
10731       hashidx[res] = idx;
10732       ++hused;
10733     }
10734   }
10735 
10736   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10737     int gx, gy;
10738     ubyte* dst;
10739 
10740     if (!atlas.addRect(w, h, &gx, &gy)) return;
10741 
10742     // Rasterize
10743     dst = &texData[gx+gy*params.width];
10744     foreach (int y; 0..h) {
10745       foreach (int x; 0..w) {
10746         dst[x] = 0xff;
10747       }
10748       dst += params.width;
10749     }
10750 
10751     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10752     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10753     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10754     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10755   }
10756 
10757   // returns fid, not hash slot
10758   int allocFontAt (int atidx) nothrow @trusted @nogc {
10759     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10760 
10761     if (atidx < 0) {
10762       if (nfonts >= cfonts) {
10763         import core.stdc.stdlib : realloc;
10764         import core.stdc..string : memset;
10765         assert(nfonts == cfonts);
10766         int newsz = cfonts+64;
10767         if (newsz > 65535) assert(0, "FONS: too many fonts");
10768         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10769         if (newlist is null) assert(0, "FONS: out of memory");
10770         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10771         fonts = newlist;
10772         cfonts = newsz;
10773       }
10774       assert(nfonts < cfonts);
10775     }
10776 
10777     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10778     if (font is null) assert(0, "FONS: out of memory");
10779     memset(font, 0, FONSfont.sizeof);
10780 
10781     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10782     if (font.glyphs is null) assert(0, "FONS: out of memory");
10783     font.cglyphs = FONS_INIT_GLYPHS;
10784     font.nglyphs = 0;
10785 
10786     if (atidx < 0) {
10787       fonts[nfonts] = font;
10788       return nfonts++;
10789     } else {
10790       fonts[atidx] = font;
10791       return atidx;
10792     }
10793   }
10794 
10795   // 0: ooops
10796   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10797     if (renderfont !is null) *renderfont = font;
10798     if (font is null || font.fdata is null) return 0;
10799     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10800     // try to find the glyph in fallback fonts
10801     if (g == 0) {
10802       foreach (immutable i; 0..font.nfallbacks) {
10803         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10804         if (fallbackFont !is null) {
10805           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10806           if (fallbackIndex != 0) {
10807             if (renderfont !is null) *renderfont = fallbackFont;
10808             return g;
10809           }
10810         }
10811       }
10812       // no char, try to find replacement one
10813       if (dch != 0xFFFD) {
10814         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10815         if (g == 0) {
10816           foreach (immutable i; 0..font.nfallbacks) {
10817             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10818             if (fallbackFont !is null) {
10819               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10820               if (fallbackIndex != 0) {
10821                 if (renderfont !is null) *renderfont = fallbackFont;
10822                 return g;
10823               }
10824             }
10825           }
10826         }
10827       }
10828     }
10829     return g;
10830   }
10831 
10832   void clear () nothrow @trusted @nogc {
10833     import core.stdc.stdlib : free;
10834 
10835     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10836     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10837 
10838     if (atlas !is null) atlas.kill();
10839     if (fonts !is null) free(fonts);
10840     if (texData !is null) free(texData);
10841     if (scratch !is null) free(scratch);
10842     if (hashidx !is null) free(hashidx);
10843   }
10844 
10845   // add font from another fontstash
10846   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10847     if (font is null || font.fdata is null) return FONS_INVALID;
10848     font.fdata.incref();
10849     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10850     if (res == FONS_INVALID) font.fdata.decref(); // oops
10851     return res;
10852   }
10853 
10854   // fdata refcount must be already increased; it won't be changed
10855   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10856     int i, ascent, descent, fh, lineGap;
10857 
10858     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10859     if (name.length > 32767) return FONS_INVALID;
10860     if (fdata is null) return FONS_INVALID;
10861 
10862     // find a font with the given name
10863     int newidx;
10864     FONSfont* oldfont = null;
10865     int oldidx = findNameInHash(name);
10866     if (oldidx != FONS_INVALID) {
10867       // replacement font
10868       oldfont = fonts[oldidx];
10869       newidx = oldidx;
10870     } else {
10871       // new font, allocate new bucket
10872       newidx = -1;
10873     }
10874 
10875     newidx = allocFontAt(newidx);
10876     FONSfont* font = fonts[newidx];
10877     font.setName(name);
10878     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10879     font.fdata = fdata; // set the font data (don't change reference count)
10880     fons__tt_setMono(&this, &font.font, !defAA);
10881 
10882     // init font
10883     nscratch = 0;
10884     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10885       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10886       font.fdata = null;
10887       font.kill();
10888       if (oldidx != FONS_INVALID) {
10889         assert(oldidx == newidx);
10890         fonts[oldidx] = oldfont;
10891       } else {
10892         assert(newidx == nfonts-1);
10893         fonts[newidx] = null;
10894         --nfonts;
10895       }
10896       return FONS_INVALID;
10897     } else {
10898       // free old font data, if any
10899       if (oldfont !is null) oldfont.kill();
10900     }
10901 
10902     // add font to name hash
10903     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10904 
10905     // store normalized line height
10906     // the real line height is got by multiplying the lineh by font size
10907     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10908     fh = ascent-descent;
10909     font.ascender = cast(float)ascent/cast(float)fh;
10910     font.descender = cast(float)descent/cast(float)fh;
10911     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10912 
10913     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10914     return newidx;
10915   }
10916 
10917   // isize: size*10
10918   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10919     if (params.isZeroTopLeft) {
10920       final switch (talign.vertical) {
10921         case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10922         case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10923         case NVGTextAlign.V.Baseline: return 0.0f;
10924         case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10925       }
10926     } else {
10927       final switch (talign.vertical) {
10928         case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10929         case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10930         case NVGTextAlign.V.Baseline: return 0.0f;
10931         case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10932       }
10933     }
10934     assert(0);
10935   }
10936 
10937 public:
10938   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10939    *
10940    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10941    * FontStash only to measure text), you can simply pass `FONSParams.init`).
10942    */
10943   static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc {
10944     import core.stdc..string : memcpy;
10945 
10946     FONSContext stash = null;
10947 
10948     // allocate memory for the font stash
10949     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10950     if (stash is null) goto error;
10951     memset(stash, 0, FONScontextInternal.sizeof);
10952 
10953     memcpy(&stash.params, &params, params.sizeof);
10954     if (stash.params.width < 1) stash.params.width = 32;
10955     if (stash.params.height < 1) stash.params.height = 32;
10956 
10957     // allocate scratch buffer
10958     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
10959     if (stash.scratch is null) goto error;
10960 
10961     // initialize implementation library
10962     if (!fons__tt_init(stash)) goto error;
10963 
10964     if (stash.params.renderCreate !is null) {
10965       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
10966     }
10967 
10968     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
10969     if (stash.atlas is null) goto error;
10970 
10971     // don't allocate space for fonts: hash manager will do that for us later
10972     //stash.cfonts = 0;
10973     //stash.nfonts = 0;
10974 
10975     // create texture for the cache
10976     stash.itw = 1.0f/stash.params.width;
10977     stash.ith = 1.0f/stash.params.height;
10978     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
10979     if (stash.texData is null) goto error;
10980     memset(stash.texData, 0, stash.params.width*stash.params.height);
10981 
10982     stash.dirtyRect.ptr[0] = stash.params.width;
10983     stash.dirtyRect.ptr[1] = stash.params.height;
10984     stash.dirtyRect.ptr[2] = 0;
10985     stash.dirtyRect.ptr[3] = 0;
10986 
10987     // add white rect at 0, 0 for debug drawing
10988     stash.addWhiteRect(2, 2);
10989 
10990     stash.pushState();
10991     stash.clearState();
10992 
10993     return stash;
10994 
10995   error:
10996     stash.kill();
10997     return null;
10998   }
10999 
11000 public:
11001   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11002   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11003     FONSfont* baseFont = fonts[base];
11004     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11005       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11006       return true;
11007     }
11008     return false;
11009   }
11010 
11011   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11012   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11013 
11014   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11015   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11016 
11017   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11018   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11019 
11020   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11021   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11022 
11023   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11024   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11025 
11026   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11027 
11028   /// Check if FontStash has a font with the given name loaded.
11029   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11030 
11031   /// Get AA for the current font, or for the specified font.
11032   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11033     FONSstate* state = getState;
11034     if (font < 0) font = state.font;
11035     if (font < 0 || font >= nfonts) return false;
11036     FONSfont* f = fonts[font];
11037     return (f !is null ? !f.font.mono : false);
11038   }
11039 
11040   /// Push current state. Returns `false` if state stack overflowed.
11041   bool pushState () nothrow @trusted @nogc {
11042     if (nstates >= FONS_MAX_STATES) {
11043       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11044       return false;
11045     }
11046     if (nstates > 0) {
11047       import core.stdc..string : memcpy;
11048       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11049     }
11050     ++nstates;
11051     return true;
11052   }
11053 
11054   /// Pop current state. Returns `false` if state stack underflowed.
11055   bool popState () nothrow @trusted @nogc {
11056     if (nstates <= 1) {
11057       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11058       return false;
11059     }
11060     --nstates;
11061     return true;
11062   }
11063 
11064   /// Clear current state (i.e. set it to some sane defaults).
11065   void clearState () nothrow @trusted @nogc {
11066     FONSstate* state = getState;
11067     state.size = 12.0f;
11068     state.font = 0;
11069     state.blur = 0;
11070     state.spacing = 0;
11071     state.talign.reset;
11072   }
11073 
11074   private enum NoAlias = ":noaa";
11075 
11076   /** Add font to FontStash.
11077    *
11078    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11079    * with same name and path several times, FontStash will load it only once. Also, you can
11080    * load new disk font for any existing logical font.
11081    *
11082    * Params:
11083    *   name = logical font name, that will be used to select this font later.
11084    *   path = path to disk file with your font.
11085    *   defAA = should FontStash use antialiased font rasterizer?
11086    *
11087    * Returns:
11088    *   font id or [FONS_INVALID].
11089    */
11090   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11091     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11092     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11093 
11094     // if font path ends with ":noaa", turn off antialiasing
11095     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11096       path = path[0..$-NoAlias.length];
11097       if (path.length == 0) return FONS_INVALID;
11098       defAA = false;
11099     }
11100 
11101     // if font name ends with ":noaa", turn off antialiasing
11102     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11103       name = name[0..$-NoAlias.length];
11104       defAA = false;
11105     }
11106 
11107     // find a font with the given name
11108     int fidx = findNameInHash(name);
11109     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11110 
11111     int loadFontFile (const(char)[] path) {
11112       // check if existing font (if any) has the same path
11113       if (fidx >= 0) {
11114         import core.stdc..string : strlen;
11115         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11116         version(Posix) {
11117           //{ import core.stdc.stdio; printf("+++ font [%.*s] was loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)fonts[fidx].path.length, fonts[fidx].path.ptr); }
11118           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11119             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11120             // i found her!
11121             return fidx;
11122           }
11123         } else {
11124           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11125             // i found her!
11126             return fidx;
11127           }
11128         }
11129       }
11130       version(Windows) {
11131         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11132         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11133       }
11134       // either no such font, or different path
11135       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11136       int xres = FONS_INVALID;
11137       try {
11138         import core.stdc.stdlib : free, malloc;
11139         static if (NanoVegaHasIVVFS) {
11140           auto fl = VFile(path);
11141           auto dataSize = fl.size;
11142           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11143           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11144           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11145           scope(failure) free(data); // oops
11146           fl.rawReadExact(data[0..cast(uint)dataSize]);
11147           fl.close();
11148         } else {
11149           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11150           import std.internal.cstring : tempCString;
11151           auto fl = fopen(path.tempCString, "rb");
11152           if (fl is null) return FONS_INVALID;
11153           scope(exit) fclose(fl);
11154           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11155           auto dataSize = ftell(fl);
11156           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11157           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11158           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11159           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11160           scope(failure) free(data); // oops
11161           ubyte* dptr = data;
11162           auto left = cast(uint)dataSize;
11163           while (left > 0) {
11164             auto rd = fread(dptr, 1, left, fl);
11165             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11166             dptr += rd;
11167             left -= rd;
11168           }
11169         }
11170         scope(failure) free(data); // oops
11171         // create font data
11172         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11173         fdata.incref();
11174         xres = addFontWithData(name, fdata, defAA);
11175         if (xres == FONS_INVALID) {
11176           fdata.decref(); // this will free [data] and [fdata]
11177         } else {
11178           // remember path
11179           fonts[xres].setPath(path);
11180         }
11181       } catch (Exception e) {
11182         // oops; sorry
11183       }
11184       return xres;
11185     }
11186 
11187     // first try direct path
11188     auto res = loadFontFile(path);
11189     // if loading failed, try fontconfig (if fontconfig is available)
11190     static if (NanoVegaHasFontConfig) {
11191       if (res == FONS_INVALID && fontconfigAvailable) {
11192         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11193         bool ok = true;
11194         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11195         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11196         if (ok) {
11197           import std.internal.cstring : tempCString;
11198           FcPattern* pat = FcNameParse(path.tempCString);
11199           if (pat !is null) {
11200             scope(exit) FcPatternDestroy(pat);
11201             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11202               FcDefaultSubstitute(pat);
11203               // find the font
11204               FcResult result;
11205               FcPattern* font = FcFontMatch(null, pat, &result);
11206               if (font !is null) {
11207                 scope(exit) FcPatternDestroy(font);
11208                 char* file = null;
11209                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11210                   if (file !is null && file[0]) {
11211                     import core.stdc..string : strlen;
11212                     res = loadFontFile(file[0..strlen(file)]);
11213                   }
11214                 }
11215               }
11216             }
11217           }
11218         }
11219       }
11220     }
11221     return res;
11222   }
11223 
11224   /** Add font to FontStash, using data from memory.
11225    *
11226    * And already loaded font to FontStash. You can replace existing logical fonts.
11227    * But note that you can't remove logical font by passing "empty" data.
11228    *
11229    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11230    *
11231    * Params:
11232    *   name = logical font name, that will be used to select this font later.
11233    *   data = font data.
11234    *   dataSize = font data size.
11235    *   freeData = should FontStash take ownership of the font data?
11236    *   defAA = should FontStash use antialiased font rasterizer?
11237    *
11238    * Returns:
11239    *   font id or [FONS_INVALID].
11240    */
11241   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11242     if (data is null || dataSize < 16) return FONS_INVALID;
11243     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11244     fdata.incref();
11245     auto res = addFontWithData(name, fdata, defAA);
11246     if (res == FONS_INVALID) {
11247       // we promised to not free data on error
11248       fdata.freeData = false;
11249       fdata.decref(); // this will free [fdata]
11250     }
11251     return res;
11252   }
11253 
11254   /** Add fonts from another FontStash.
11255    *
11256    * This is more effective (and faster) than reloading fonts, because internally font data
11257    * is reference counted.
11258    */
11259   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11260     if (source is null) return;
11261     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11262       if (font !is null) {
11263         auto newidx = addCookedFont(font);
11264         FONSfont* newfont = fonts[newidx];
11265         assert(newfont !is null);
11266         assert(newfont.path is null);
11267         // copy path
11268         if (font.path !is null && font.path[0]) {
11269           import core.stdc.stdlib : malloc;
11270           import core.stdc..string : strcpy, strlen;
11271           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11272           if (newfont.path is null) assert(0, "FONS: out of memory");
11273           strcpy(newfont.path, font.path);
11274         }
11275       }
11276     }
11277   }
11278 
11279   /// Returns logical font name corresponding to the given font id, or `null`.
11280   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11281   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11282     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11283     return fonts[idx].name[0..fonts[idx].namelen];
11284   }
11285 
11286   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11287   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11288     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11289     // remove ":noaa" suffix
11290     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11291       name = name[0..$-NoAlias.length];
11292     }
11293     if (name.length == 0) return FONS_INVALID;
11294     return findNameInHash(name);
11295   }
11296 
11297   /** Measures the specified text string. Parameter bounds should be a float[4],
11298    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11299    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11300    */
11301   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11302     FONSstate* state = getState;
11303     uint codepoint;
11304     uint utf8state = 0;
11305     FONSQuad q;
11306     FONSglyph* glyph = null;
11307     int prevGlyphIndex = -1;
11308     short isize = cast(short)(state.size*10.0f);
11309     short iblur = cast(short)state.blur;
11310     FONSfont* font;
11311 
11312     if (state.font < 0 || state.font >= nfonts) return 0;
11313     font = fonts[state.font];
11314     if (font is null || font.fdata is null) return 0;
11315 
11316     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11317 
11318     // Align vertically.
11319     y += getVertAlign(font, state.talign, isize);
11320 
11321     float minx = x, maxx = x;
11322     float miny = y, maxy = y;
11323     float startx = x;
11324 
11325     foreach (T ch; str) {
11326       static if (T.sizeof == 1) {
11327         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11328         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11329         if (utf8state) continue;
11330       } else {
11331         static if (T.sizeof == 4) {
11332           if (ch > dchar.max) ch = 0xFFFD;
11333         }
11334         codepoint = cast(uint)ch;
11335       }
11336       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11337       if (glyph !is null) {
11338         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11339         if (q.x0 < minx) minx = q.x0;
11340         if (q.x1 > maxx) maxx = q.x1;
11341         if (params.isZeroTopLeft) {
11342           if (q.y0 < miny) miny = q.y0;
11343           if (q.y1 > maxy) maxy = q.y1;
11344         } else {
11345           if (q.y1 < miny) miny = q.y1;
11346           if (q.y0 > maxy) maxy = q.y0;
11347         }
11348         prevGlyphIndex = glyph.index;
11349       } else {
11350         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11351         prevGlyphIndex = -1;
11352       }
11353     }
11354 
11355     float advance = x-startx;
11356     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11357 
11358     // Align horizontally
11359     if (state.talign.left) {
11360       // empty
11361     } else if (state.talign.right) {
11362       minx -= advance;
11363       maxx -= advance;
11364     } else if (state.talign.center) {
11365       minx -= advance*0.5f;
11366       maxx -= advance*0.5f;
11367     }
11368 
11369     if (bounds.length) {
11370       if (bounds.length > 0) bounds.ptr[0] = minx;
11371       if (bounds.length > 1) bounds.ptr[1] = miny;
11372       if (bounds.length > 2) bounds.ptr[2] = maxx;
11373       if (bounds.length > 3) bounds.ptr[3] = maxy;
11374     }
11375 
11376     return advance;
11377   }
11378 
11379   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11380   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11381     FONSstate* state = getState;
11382     if (state.font < 0 || state.font >= nfonts) {
11383       if (ascender !is null) *ascender = 0;
11384       if (descender !is null) *descender = 0;
11385       if (lineh !is null) *lineh = 0;
11386     } else {
11387       FONSfont* font = fonts[state.font];
11388       if (font is null || font.fdata is null) {
11389         if (ascender !is null) *ascender = 0;
11390         if (descender !is null) *descender = 0;
11391         if (lineh !is null) *lineh = 0;
11392       } else {
11393         short isize = cast(short)(state.size*10.0f);
11394         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11395         if (descender !is null) *descender = font.descender*isize/10.0f;
11396         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11397       }
11398     }
11399   }
11400 
11401   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11402   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11403     FONSfont* font;
11404     FONSstate* state = getState;
11405     short isize;
11406 
11407     if (minyp !is null) *minyp = 0;
11408     if (maxyp !is null) *maxyp = 0;
11409 
11410     if (state.font < 0 || state.font >= nfonts) return;
11411     font = fonts[state.font];
11412     isize = cast(short)(state.size*10.0f);
11413     if (font is null || font.fdata is null) return;
11414 
11415     y += getVertAlign(font, state.talign, isize);
11416 
11417     if (params.isZeroTopLeft) {
11418       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11419       immutable float maxy = miny+font.lineh*isize/10.0f;
11420       if (minyp !is null) *minyp = miny;
11421       if (maxyp !is null) *maxyp = maxy;
11422     } else {
11423       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11424       immutable float miny = maxy-font.lineh*isize/10.0f;
11425       if (minyp !is null) *minyp = miny;
11426       if (maxyp !is null) *maxyp = maxy;
11427     }
11428   }
11429 
11430   /// Returns font line height.
11431   float fontHeight () nothrow @trusted @nogc {
11432     float res = void;
11433     getVertMetrics(null, null, &res);
11434     return res;
11435   }
11436 
11437   /// Returns font ascender (positive).
11438   float fontAscender () nothrow @trusted @nogc {
11439     float res = void;
11440     getVertMetrics(&res, null, null);
11441     return res;
11442   }
11443 
11444   /// Returns font descender (negative).
11445   float fontDescender () nothrow @trusted @nogc {
11446     float res = void;
11447     getVertMetrics(null, &res, null);
11448     return res;
11449   }
11450 
11451   //TODO: document this
11452   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11453     if (width !is null) *width = params.width;
11454     if (height !is null) *height = params.height;
11455     return texData;
11456   }
11457 
11458   //TODO: document this
11459   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11460     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11461       dirty[0] = dirtyRect.ptr[0];
11462       dirty[1] = dirtyRect.ptr[1];
11463       dirty[2] = dirtyRect.ptr[2];
11464       dirty[3] = dirtyRect.ptr[3];
11465       // reset dirty rect
11466       dirtyRect.ptr[0] = params.width;
11467       dirtyRect.ptr[1] = params.height;
11468       dirtyRect.ptr[2] = 0;
11469       dirtyRect.ptr[3] = 0;
11470       return true;
11471     }
11472     return false;
11473   }
11474 
11475   //TODO: document this
11476   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11477     handleError = callback;
11478   }
11479 
11480   //TODO: document this
11481   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11482     if (width !is null) *width = params.width;
11483     if (height !is null) *height = params.height;
11484   }
11485 
11486   //TODO: document this
11487   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11488     import core.stdc.stdlib : free;
11489     import core.stdc..string : memcpy, memset;
11490 
11491     int maxy = 0;
11492     ubyte* data = null;
11493 
11494     width = nvg__max(width, params.width);
11495     height = nvg__max(height, params.height);
11496 
11497     if (width == params.width && height == params.height) return true;
11498 
11499     // Flush pending glyphs.
11500     flush();
11501 
11502     // Create new texture
11503     if (params.renderResize !is null) {
11504       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11505     }
11506     // Copy old texture data over.
11507     data = cast(ubyte*)malloc(width*height);
11508     if (data is null) return 0;
11509     foreach (immutable int i; 0..params.height) {
11510       ubyte* dst = &data[i*width];
11511       ubyte* src = &texData[i*params.width];
11512       memcpy(dst, src, params.width);
11513       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11514     }
11515     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11516 
11517     free(texData);
11518     texData = data;
11519 
11520     // Increase atlas size
11521     atlas.expand(width, height);
11522 
11523     // Add existing data as dirty.
11524     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11525     dirtyRect.ptr[0] = 0;
11526     dirtyRect.ptr[1] = 0;
11527     dirtyRect.ptr[2] = params.width;
11528     dirtyRect.ptr[3] = maxy;
11529 
11530     params.width = width;
11531     params.height = height;
11532     itw = 1.0f/params.width;
11533     ith = 1.0f/params.height;
11534 
11535     return true;
11536   }
11537 
11538   //TODO: document this
11539   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11540     import core.stdc.stdlib : realloc;
11541     import core.stdc..string : memcpy, memset;
11542 
11543     // flush pending glyphs
11544     flush();
11545 
11546     // create new texture
11547     if (params.renderResize !is null) {
11548       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11549     }
11550 
11551     // reset atlas
11552     atlas.reset(width, height);
11553 
11554     // clear texture data
11555     texData = cast(ubyte*)realloc(texData, width*height);
11556     if (texData is null) assert(0, "FONS: out of memory");
11557     memset(texData, 0, width*height);
11558 
11559     // reset dirty rect
11560     dirtyRect.ptr[0] = width;
11561     dirtyRect.ptr[1] = height;
11562     dirtyRect.ptr[2] = 0;
11563     dirtyRect.ptr[3] = 0;
11564 
11565     // Reset cached glyphs
11566     foreach (FONSfont* font; fonts[0..nfonts]) {
11567       if (font !is null) {
11568         font.nglyphs = 0;
11569         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11570       }
11571     }
11572 
11573     params.width = width;
11574     params.height = height;
11575     itw = 1.0f/params.width;
11576     ith = 1.0f/params.height;
11577 
11578     // Add white rect at 0, 0 for debug drawing.
11579     addWhiteRect(2, 2);
11580 
11581     return true;
11582   }
11583 
11584   //TODO: document this
11585   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11586     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11587     static if (is(typeof(&fons__nvg__bounds))) {
11588       FONSstate* state = getState;
11589       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11590       FONSfont* font;
11591       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11592       if (g == 0) { bounds[] = 0; return false; }
11593       assert(font !is null);
11594       return fons__nvg__bounds(&font.font, g, bounds);
11595     } else {
11596       bounds[] = 0;
11597       return false;
11598     }
11599   }
11600 
11601   //TODO: document this
11602   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11603     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11604     static if (is(typeof(&fons__nvg__toPath))) {
11605       if (vg is null) { bounds[] = 0; return false; }
11606       FONSstate* state = getState;
11607       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11608       FONSfont* font;
11609       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11610       if (g == 0) { bounds[] = 0; return false; }
11611       assert(font !is null);
11612       return fons__nvg__toPath(vg, &font.font, g, bounds);
11613     } else {
11614       bounds[] = 0;
11615       return false;
11616     }
11617   }
11618 
11619   //TODO: document this
11620   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11621     if (ol is null) return false;
11622     static if (is(typeof(&fons__nvg__toOutline))) {
11623       FONSstate* state = getState;
11624       if (state.font < 0 || state.font >= nfonts) return false;
11625       FONSfont* font;
11626       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11627       if (g == 0) return false;
11628       assert(font !is null);
11629       return fons__nvg__toOutline(&font.font, g, ol);
11630     } else {
11631       return false;
11632     }
11633   }
11634 
11635   //TODO: document this
11636   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11637     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11638       pragma(inline, true);
11639       a += ~(a<<15);
11640       a ^=  (a>>10);
11641       a +=  (a<<3);
11642       a ^=  (a>>6);
11643       a += ~(a<<11);
11644       a ^=  (a>>16);
11645       return a;
11646     }
11647 
11648     // based on Exponential blur, Jani Huhtanen, 2006
11649     enum APREC = 16;
11650     enum ZPREC = 7;
11651 
11652     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11653       foreach (immutable int y; 0..h) {
11654         int z = 0; // force zero border
11655         foreach (int x; 1..w) {
11656           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11657           dst[x] = cast(ubyte)(z>>ZPREC);
11658         }
11659         dst[w-1] = 0; // force zero border
11660         z = 0;
11661         for (int x = w-2; x >= 0; --x) {
11662           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11663           dst[x] = cast(ubyte)(z>>ZPREC);
11664         }
11665         dst[0] = 0; // force zero border
11666         dst += dstStride;
11667       }
11668     }
11669 
11670     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11671       foreach (immutable int x; 0..w) {
11672         int z = 0; // force zero border
11673         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11674           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11675           dst[y] = cast(ubyte)(z>>ZPREC);
11676         }
11677         dst[(h-1)*dstStride] = 0; // force zero border
11678         z = 0;
11679         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11680           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11681           dst[y] = cast(ubyte)(z>>ZPREC);
11682         }
11683         dst[0] = 0; // force zero border
11684         ++dst;
11685       }
11686     }
11687 
11688     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11689       import std.math : expf = exp;
11690       if (blur < 1) return;
11691       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11692       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11693       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11694       fons__blurRows(dst, w, h, dstStride, alpha);
11695       fons__blurCols(dst, w, h, dstStride, alpha);
11696       fons__blurRows(dst, w, h, dstStride, alpha);
11697       fons__blurCols(dst, w, h, dstStride, alpha);
11698       //fons__blurrows(dst, w, h, dstStride, alpha);
11699       //fons__blurcols(dst, w, h, dstStride, alpha);
11700     }
11701 
11702     int advance, lsb, x0, y0, x1, y1, gx, gy;
11703     FONSglyph* glyph = null;
11704     float size = isize/10.0f;
11705     FONSfont* renderFont = font;
11706 
11707     if (isize < 2) return null;
11708     if (iblur > 20) iblur = 20;
11709     int pad = iblur+2;
11710 
11711     // Reset allocator.
11712     nscratch = 0;
11713 
11714     // Find code point and size.
11715     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11716     int i = font.lut.ptr[h];
11717     while (i != -1) {
11718       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11719       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11720         glyph = &font.glyphs[i];
11721         // Negative coordinate indicates there is no bitmap data created.
11722         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11723         // At this point, glyph exists but the bitmap data is not yet created.
11724         break;
11725       }
11726       i = font.glyphs[i].next;
11727     }
11728 
11729     // Create a new glyph or rasterize bitmap data for a cached glyph.
11730     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11731     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11732     // It is possible that we did not find a fallback glyph.
11733     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11734 
11735     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11736     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11737     int gw = x1-x0+pad*2;
11738     int gh = y1-y0+pad*2;
11739 
11740     // Determines the spot to draw glyph in the atlas.
11741     if (bitmapOption == FONSBitmapFlag.Required) {
11742       // Find free spot for the rect in the atlas.
11743       bool added = atlas.addRect(gw, gh, &gx, &gy);
11744       if (!added && handleError !is null) {
11745         // Atlas is full, let the user to resize the atlas (or not), and try again.
11746         handleError(FONSError.AtlasFull, 0);
11747         added = atlas.addRect(gw, gh, &gx, &gy);
11748       }
11749       if (!added) return null;
11750     } else {
11751       // Negative coordinate indicates there is no bitmap data created.
11752       gx = -1;
11753       gy = -1;
11754     }
11755 
11756     // Init glyph.
11757     if (glyph is null) {
11758       glyph = font.allocGlyph();
11759       glyph.codepoint = codepoint;
11760       glyph.size = isize;
11761       glyph.blur = iblur;
11762       glyph.next = 0;
11763 
11764       // Insert char to hash lookup.
11765       glyph.next = font.lut.ptr[h];
11766       font.lut.ptr[h] = font.nglyphs-1;
11767     }
11768     glyph.index = g;
11769     glyph.x0 = cast(short)gx;
11770     glyph.y0 = cast(short)gy;
11771     glyph.x1 = cast(short)(glyph.x0+gw);
11772     glyph.y1 = cast(short)(glyph.y0+gh);
11773     glyph.xadv = cast(short)(scale*advance*10.0f);
11774     glyph.xoff = cast(short)(x0-pad);
11775     glyph.yoff = cast(short)(y0-pad);
11776 
11777     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11778 
11779     // Rasterize
11780     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11781     fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11782 
11783     // Make sure there is one pixel empty border.
11784     dst = &texData[glyph.x0+glyph.y0*params.width];
11785     foreach (immutable int y; 0..gh) {
11786       dst[y*params.width] = 0;
11787       dst[gw-1+y*params.width] = 0;
11788     }
11789     foreach (immutable int x; 0..gw) {
11790       dst[x] = 0;
11791       dst[x+(gh-1)*params.width] = 0;
11792     }
11793 
11794     // Debug code to color the glyph background
11795     version(none) {
11796       foreach (immutable yy; 0..gh) {
11797         foreach (immutable xx; 0..gw) {
11798           int a = cast(int)dst[xx+yy*params.width]+42;
11799           if (a > 255) a = 255;
11800           dst[xx+yy*params.width] = cast(ubyte)a;
11801         }
11802       }
11803     }
11804 
11805     // Blur
11806     if (iblur > 0) {
11807       nscratch = 0;
11808       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11809       fons__blur(bdst, gw, gh, params.width, iblur);
11810     }
11811 
11812     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11813     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11814     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11815     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11816 
11817     return glyph;
11818   }
11819 
11820   //TODO: document this
11821   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11822     if (prevGlyphIndex >= 0) {
11823       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11824       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11825       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11826     }
11827 
11828     // Each glyph has 2px border to allow good interpolation,
11829     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11830     // Inset the texture region by one pixel for correct interpolation.
11831     immutable float xoff = cast(short)(glyph.xoff+1);
11832     immutable float yoff = cast(short)(glyph.yoff+1);
11833     immutable float x0 = cast(float)(glyph.x0+1);
11834     immutable float y0 = cast(float)(glyph.y0+1);
11835     immutable float x1 = cast(float)(glyph.x1-1);
11836     immutable float y1 = cast(float)(glyph.y1-1);
11837 
11838     if (params.isZeroTopLeft) {
11839       immutable float rx = cast(float)cast(int)(*x+xoff);
11840       immutable float ry = cast(float)cast(int)(*y+yoff);
11841 
11842       q.x0 = rx;
11843       q.y0 = ry;
11844       q.x1 = rx+x1-x0;
11845       q.y1 = ry+y1-y0;
11846 
11847       q.s0 = x0*itw;
11848       q.t0 = y0*ith;
11849       q.s1 = x1*itw;
11850       q.t1 = y1*ith;
11851     } else {
11852       immutable float rx = cast(float)cast(int)(*x+xoff);
11853       immutable float ry = cast(float)cast(int)(*y-yoff);
11854 
11855       q.x0 = rx;
11856       q.y0 = ry;
11857       q.x1 = rx+x1-x0;
11858       q.y1 = ry-y1+y0;
11859 
11860       q.s0 = x0*itw;
11861       q.t0 = y0*ith;
11862       q.s1 = x1*itw;
11863       q.t1 = y1*ith;
11864     }
11865 
11866     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11867   }
11868 
11869   void flush () nothrow @trusted @nogc {
11870     // flush texture
11871     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11872       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11873       // reset dirty rect
11874       dirtyRect.ptr[0] = params.width;
11875       dirtyRect.ptr[1] = params.height;
11876       dirtyRect.ptr[2] = 0;
11877       dirtyRect.ptr[3] = 0;
11878     }
11879   }
11880 }
11881 
11882 /// Free all resources used by the `stash`, and `stash` itself.
11883 /// Group: font_stash
11884 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11885   import core.stdc.stdlib : free;
11886   if (stash is null) return;
11887   stash.clear();
11888   free(stash);
11889   stash = null;
11890 }
11891 
11892 
11893 // ////////////////////////////////////////////////////////////////////////// //
11894 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11895   ubyte* ptr;
11896   FONSContext stash = cast(FONSContext)up;
11897   // 16-byte align the returned pointer
11898   size = (size+0xf)&~0xf;
11899   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11900     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11901     return null;
11902   }
11903   ptr = stash.scratch+stash.nscratch;
11904   stash.nscratch += cast(int)size;
11905   return ptr;
11906 }
11907 
11908 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11909   // empty
11910 }
11911 
11912 
11913 // ////////////////////////////////////////////////////////////////////////// //
11914 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11915 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11916 
11917 enum FONS_UTF8_ACCEPT = 0;
11918 enum FONS_UTF8_REJECT = 12;
11919 
11920 static immutable ubyte[364] utf8d = [
11921   // The first part of the table maps bytes to character classes that
11922   // to reduce the size of the transition table and create bitmasks.
11923   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11924   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11925   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11926   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11927   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
11928   7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
11929   8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
11930   10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
11931 
11932   // The second part is a transition table that maps a combination
11933   // of a state of the automaton and a character class to a state.
11934   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11935   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11936   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11937   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11938   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11939 ];
11940 
11941 private enum DecUtfMixin(string state, string codep, string byte_) =
11942 `{
11943   uint type_ = utf8d.ptr[`~byte_~`];
11944   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11945   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11946     `~state~` = FONS_UTF8_ACCEPT;
11947     `~codep~` = 0xFFFD;
11948   }
11949  }`;
11950 
11951 /*
11952 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11953   pragma(inline, true);
11954   uint type = utf8d.ptr[byte_];
11955   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11956   *state = utf8d.ptr[256 + *state+type];
11957   return *state;
11958 }
11959 */
11960 
11961 
11962 // ////////////////////////////////////////////////////////////////////////// //
11963 /// This iterator can be used to do text measurement.
11964 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
11965 /// Group: font_stash
11966 public struct FONSTextBoundsIterator {
11967 private:
11968   FONSContext stash;
11969   FONSstate state;
11970   uint codepoint = 0xFFFD;
11971   uint utf8state = 0;
11972   int prevGlyphIndex = -1;
11973   short isize, iblur;
11974   float scale = 0;
11975   FONSfont* font;
11976   float startx = 0, x = 0, y = 0;
11977   float minx = 0, miny = 0, maxx = 0, maxy = 0;
11978 
11979 private:
11980   void clear () nothrow @trusted @nogc {
11981     import core.stdc..string : memset;
11982     memset(&this, 0, this.sizeof);
11983     this.prevGlyphIndex = -1;
11984     this.codepoint = 0xFFFD;
11985   }
11986 
11987 public:
11988   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11989   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
11990 
11991   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
11992   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
11993     clear();
11994 
11995     if (astash is null || astash.nstates == 0) return;
11996 
11997     stash = astash;
11998     state = *stash.getState;
11999 
12000     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12001     font = stash.fonts[state.font];
12002     if (font is null || font.fdata is null) { clear(); return; }
12003 
12004     x = ax;
12005     y = ay;
12006     isize = cast(short)(state.size*10.0f);
12007     iblur = cast(short)state.blur;
12008     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12009 
12010     // align vertically
12011     y += astash.getVertAlign(font, state.talign, isize);
12012 
12013     minx = maxx = x;
12014     miny = maxy = y;
12015     startx = x;
12016   }
12017 
12018   /// Can this iterator be used?
12019   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12020 
12021   /// Put some text into iterator, calculate new values.
12022   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12023     enum DoCodePointMixin = q{
12024       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12025       if (glyph !is null) {
12026         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12027         if (q.x0 < minx) minx = q.x0;
12028         if (q.x1 > maxx) maxx = q.x1;
12029         if (stash.params.isZeroTopLeft) {
12030           if (q.y0 < miny) miny = q.y0;
12031           if (q.y1 > maxy) maxy = q.y1;
12032         } else {
12033           if (q.y1 < miny) miny = q.y1;
12034           if (q.y0 > maxy) maxy = q.y0;
12035         }
12036         prevGlyphIndex = glyph.index;
12037       } else {
12038         prevGlyphIndex = -1;
12039       }
12040     };
12041 
12042     if (stash is null || str.length == 0) return; // alas
12043 
12044     FONSQuad q;
12045     FONSglyph* glyph;
12046 
12047     static if (is(T == char)) {
12048       foreach (char ch; str) {
12049         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12050         if (utf8state) continue; // full char is not collected yet
12051         mixin(DoCodePointMixin);
12052       }
12053     } else {
12054       if (utf8state) {
12055         utf8state = 0;
12056         codepoint = 0xFFFD;
12057         mixin(DoCodePointMixin);
12058       }
12059       foreach (T dch; str) {
12060         static if (is(T == dchar)) {
12061           if (dch > dchar.max) dch = 0xFFFD;
12062         }
12063         codepoint = cast(uint)dch;
12064         mixin(DoCodePointMixin);
12065       }
12066     }
12067   }
12068 
12069   /// Returns current advance.
12070   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12071 
12072   /// Returns current text bounds.
12073   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12074     if (stash is null) { bounds[] = 0; return; }
12075     float lminx = minx, lmaxx = maxx;
12076     // align horizontally
12077     if (state.talign.left) {
12078       // empty
12079     } else if (state.talign.right) {
12080       float ca = advance;
12081       lminx -= ca;
12082       lmaxx -= ca;
12083     } else if (state.talign.center) {
12084       float ca = advance*0.5f;
12085       lminx -= ca;
12086       lmaxx -= ca;
12087     }
12088     bounds[0] = lminx;
12089     bounds[1] = miny;
12090     bounds[2] = lmaxx;
12091     bounds[3] = maxy;
12092   }
12093 
12094   /// Returns current horizontal text bounds.
12095   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12096     if (stash !is null) {
12097       float lminx = minx, lmaxx = maxx;
12098       // align horizontally
12099       if (state.talign.left) {
12100         // empty
12101       } else if (state.talign.right) {
12102         float ca = advance;
12103         lminx -= ca;
12104         lmaxx -= ca;
12105       } else if (state.talign.center) {
12106         float ca = advance*0.5f;
12107         lminx -= ca;
12108         lmaxx -= ca;
12109       }
12110       xmin = lminx;
12111       xmax = lmaxx;
12112     } else {
12113       xmin = xmax = 0;
12114     }
12115   }
12116 
12117   /// Returns current vertical text bounds.
12118   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12119     pragma(inline, true);
12120     if (stash !is null) {
12121       ymin = miny;
12122       ymax = maxy;
12123     } else {
12124       ymin = ymax = 0;
12125     }
12126   }
12127 
12128   /// Returns font line height.
12129   float lineHeight () nothrow @trusted @nogc {
12130     pragma(inline, true);
12131     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12132   }
12133 
12134   /// Returns font ascender (positive).
12135   float ascender () nothrow @trusted @nogc {
12136     pragma(inline, true);
12137     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12138   }
12139 
12140   /// Returns font descender (negative).
12141   float descender () nothrow @trusted @nogc {
12142     pragma(inline, true);
12143     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12144   }
12145 }
12146 
12147 
12148 // ////////////////////////////////////////////////////////////////////////// //
12149 // backgl
12150 // ////////////////////////////////////////////////////////////////////////// //
12151 import core.stdc.stdlib : malloc, realloc, free;
12152 import core.stdc..string : memcpy, memset;
12153 
12154 static if (__VERSION__ < 2076) {
12155   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12156     import std.traits;
12157     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12158     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12159   }
12160 }
12161 
12162 
12163 //import arsd.simpledisplay;
12164 version(nanovg_bindbc_opengl_bindings) {
12165   import bindbc.opengl;
12166 } else version(nanovg_builtin_opengl_bindings) {
12167   import arsd.simpledisplay;
12168 
12169 	/++
12170 		A SimpleWindow subclass that encapsulates some nanovega defaults. You just set a `redrawNVGScene` delegate and, optionally, your nromal event handlers for simpledisplay, and the rest is set up for you.
12171 
12172 		History:
12173 			Added January 22, 2021 (version 9.2 release)
12174 	+/
12175 	public class NVGWindow : SimpleWindow {
12176 		NVGContext nvg;
12177 
12178 		/++
12179 
12180 		+/
12181 		this(int width, int height, string title) {
12182 			setOpenGLContextVersion(3, 0);
12183 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12184 
12185 			this.onClosing = delegate() {
12186 				nvg.kill();
12187 			};
12188 
12189 			this.visibleForTheFirstTime = delegate() {
12190 				nvg = nvgCreateContext();
12191 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12192 			};
12193 
12194 			this.redrawOpenGlScene = delegate() {
12195 				if(redrawNVGScene is null)
12196 					return;
12197 				glViewport(0, 0, this.width, this.height);
12198 				if(clearOnEachFrame) {
12199 					glClearColor(0, 0, 0, 0);
12200 					glClear(glNVGClearFlags);
12201 				}
12202 
12203 				nvg.beginFrame(this.width, this.height);
12204 				scope(exit) nvg.endFrame();
12205 
12206 				redrawNVGScene(nvg);
12207 			};
12208 
12209 			this.setEventHandlers(
12210 				&redrawOpenGlSceneNow,
12211 				(KeyEvent ke) {
12212 					if(ke.key == Key.Escape || ke.key == Key.Q)
12213 						this.close();
12214 				}
12215 			);
12216 		}
12217 
12218 		/++
12219 
12220 		+/
12221 		bool clearOnEachFrame = true;
12222 
12223 		/++
12224 
12225 		+/
12226 		void delegate(NVGContext nvg) redrawNVGScene;
12227 
12228 		/++
12229 
12230 		+/
12231 		void redrawNVGSceneNow() {
12232 			redrawOpenGlSceneNow();
12233 		}
12234 	}
12235 
12236 } else {
12237   import iv.glbinds;
12238 }
12239 
12240 private:
12241 // sdpy is missing that yet
12242 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12243 
12244 
12245 
12246 version(bindbc){
12247   private extern(System) nothrow @nogc:
12248   // this definition doesn't exist in regular OpenGL (?)
12249   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12250   private void nanovgInitOpenGL () {
12251     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12252     GLSupport support = loadOpenGL();
12253     if (support == GLSupport.noLibrary)
12254       assert(0, "OpenGL initialization failed: shared library failed to load");
12255     else if (support == GLSupport.badLibrary)
12256       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12257     else if (support == GLSupport.noContext)
12258       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12259   }
12260 } else { // OpenGL API missing from simpledisplay
12261     private void nanovgInitOpenGL () @nogc nothrow {
12262       __gshared bool initialized = false;
12263       if (initialized) return;
12264 
12265       try
12266         gl3.loadDynamicLibrary();
12267       catch(Exception)
12268       	assert(0, "GL 3 failed to load");
12269 
12270       initialized = true;
12271   }
12272 }
12273 
12274 
12275 
12276 /// Context creation flags.
12277 /// Group: context_management
12278 public enum NVGContextFlag : int {
12279   /// Nothing special, i.e. empty flag.
12280   None = 0,
12281   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12282   Antialias = 1U<<0,
12283   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12284     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12285   StencilStrokes = 1U<<1,
12286   /// Flag indicating that additional debug checks are done.
12287   Debug = 1U<<2,
12288   /// Filter (antialias) fonts
12289   FontAA = 1U<<7,
12290   /// Don't filter (antialias) fonts
12291   FontNoAA = 1U<<8,
12292   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12293   Default = 1U<<31,
12294 }
12295 
12296 public enum NANOVG_GL_USE_STATE_FILTER = true;
12297 
12298 /// Returns flags for glClear().
12299 /// Group: context_management
12300 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12301   pragma(inline, true);
12302   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12303 }
12304 
12305 
12306 // ////////////////////////////////////////////////////////////////////////// //
12307 private:
12308 
12309 version = nanovega_shared_stencil;
12310 //version = nanovega_debug_clipping;
12311 
12312 enum GLNVGuniformLoc {
12313   ViewSize,
12314   Tex,
12315   Frag,
12316   TMat,
12317   TTr,
12318   ClipTex,
12319 }
12320 
12321 alias GLNVGshaderType = int;
12322 enum /*GLNVGshaderType*/ {
12323   NSVG_SHADER_FILLCOLOR,
12324   NSVG_SHADER_FILLGRAD,
12325   NSVG_SHADER_FILLIMG,
12326   NSVG_SHADER_SIMPLE, // also used for clipfill
12327   NSVG_SHADER_IMG,
12328 }
12329 
12330 struct GLNVGshader {
12331   GLuint prog;
12332   GLuint frag;
12333   GLuint vert;
12334   GLint[GLNVGuniformLoc.max+1] loc;
12335 }
12336 
12337 struct GLNVGtexture {
12338   int id;
12339   GLuint tex;
12340   int width, height;
12341   NVGtexture type;
12342   int flags;
12343   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12344   int nextfree;
12345 }
12346 
12347 struct GLNVGblend {
12348   bool simple;
12349   GLenum srcRGB;
12350   GLenum dstRGB;
12351   GLenum srcAlpha;
12352   GLenum dstAlpha;
12353 }
12354 
12355 alias GLNVGcallType = int;
12356 enum /*GLNVGcallType*/ {
12357   GLNVG_NONE = 0,
12358   GLNVG_FILL,
12359   GLNVG_CONVEXFILL,
12360   GLNVG_STROKE,
12361   GLNVG_TRIANGLES,
12362   GLNVG_AFFINE, // change affine transformation matrix
12363   GLNVG_PUSHCLIP,
12364   GLNVG_POPCLIP,
12365   GLNVG_RESETCLIP,
12366   GLNVG_CLIP_DDUMP_ON,
12367   GLNVG_CLIP_DDUMP_OFF,
12368 }
12369 
12370 struct GLNVGcall {
12371   int type;
12372   int evenOdd; // for fill
12373   int image;
12374   int pathOffset;
12375   int pathCount;
12376   int triangleOffset;
12377   int triangleCount;
12378   int uniformOffset;
12379   NVGMatrix affine;
12380   GLNVGblend blendFunc;
12381   NVGClipMode clipmode;
12382 }
12383 
12384 struct GLNVGpath {
12385   int fillOffset;
12386   int fillCount;
12387   int strokeOffset;
12388   int strokeCount;
12389 }
12390 
12391 align(1) struct GLNVGfragUniforms {
12392 align(1):
12393   enum UNIFORM_ARRAY_SIZE = 13;
12394   // note: after modifying layout or size of uniform array,
12395   // don't forget to also update the fragment shader source!
12396   align(1) union {
12397   align(1):
12398     align(1) struct {
12399     align(1):
12400       float[12] scissorMat; // matrices are actually 3 vec4s
12401       float[12] paintMat;
12402       NVGColor innerCol;
12403       NVGColor middleCol;
12404       NVGColor outerCol;
12405       float[2] scissorExt;
12406       float[2] scissorScale;
12407       float[2] extent;
12408       float radius;
12409       float feather;
12410       float strokeMult;
12411       float strokeThr;
12412       float texType;
12413       float type;
12414       float doclip;
12415       float midp; // for gradients
12416       float unused2, unused3;
12417     }
12418     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12419   }
12420 }
12421 
12422 enum GLMaskState {
12423   DontMask = -1,
12424   Uninitialized = 0,
12425   Initialized = 1,
12426   JustCleared = 2,
12427 }
12428 
12429 final class GLNVGTextureLocker {}
12430 
12431 struct GLNVGcontext {
12432   private import core.thread : ThreadID;
12433 
12434   GLNVGshader shader;
12435   GLNVGtexture* textures;
12436   float[2] view;
12437   int freetexid; // -1: none
12438   int ntextures;
12439   int ctextures;
12440   GLuint vertBuf;
12441   int fragSize;
12442   int flags;
12443   // FBOs for masks
12444   GLuint[NVG_MAX_STATES] fbo;
12445   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12446   int fboWidth, fboHeight;
12447   GLMaskState[NVG_MAX_STATES] maskStack;
12448   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12449   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12450   int lastClipUniOfs;
12451   bool doClipUnion; // specal mode
12452   GLNVGshader shaderFillFBO;
12453   GLNVGshader shaderCopyFBO;
12454 
12455   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12456   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12457   ThreadID mainTID;
12458   uint mainFBO;
12459 
12460   // Per frame buffers
12461   GLNVGcall* calls;
12462   int ccalls;
12463   int ncalls;
12464   GLNVGpath* paths;
12465   int cpaths;
12466   int npaths;
12467   NVGVertex* verts;
12468   int cverts;
12469   int nverts;
12470   ubyte* uniforms;
12471   int cuniforms;
12472   int nuniforms;
12473   NVGMatrix lastAffine;
12474 
12475   // cached state
12476   static if (NANOVG_GL_USE_STATE_FILTER) {
12477     GLuint boundTexture;
12478     GLuint stencilMask;
12479     GLenum stencilFunc;
12480     GLint stencilFuncRef;
12481     GLuint stencilFuncMask;
12482     GLNVGblend blendFunc;
12483   }
12484 }
12485 
12486 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12487 
12488 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12489   static if (NANOVG_GL_USE_STATE_FILTER) {
12490     if (gl.boundTexture != tex) {
12491       gl.boundTexture = tex;
12492       glBindTexture(GL_TEXTURE_2D, tex);
12493     }
12494   } else {
12495     glBindTexture(GL_TEXTURE_2D, tex);
12496   }
12497 }
12498 
12499 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12500   static if (NANOVG_GL_USE_STATE_FILTER) {
12501     if (gl.stencilMask != mask) {
12502       gl.stencilMask = mask;
12503       glStencilMask(mask);
12504     }
12505   } else {
12506     glStencilMask(mask);
12507   }
12508 }
12509 
12510 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12511   static if (NANOVG_GL_USE_STATE_FILTER) {
12512     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12513       gl.stencilFunc = func;
12514       gl.stencilFuncRef = ref_;
12515       gl.stencilFuncMask = mask;
12516       glStencilFunc(func, ref_, mask);
12517     }
12518   } else {
12519     glStencilFunc(func, ref_, mask);
12520   }
12521 }
12522 
12523 // texture id is never zero
12524 // sets refcount to one
12525 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12526   GLNVGtexture* tex = null;
12527 
12528   int tid = gl.freetexid;
12529   if (tid == -1) {
12530     if (gl.ntextures >= gl.ctextures) {
12531       assert(gl.ntextures == gl.ctextures);
12532       //pragma(msg, GLNVGtexture.sizeof*32);
12533       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12534       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12535       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12536       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12537       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12538       gl.textures = textures;
12539       gl.ctextures = ctextures;
12540     }
12541     assert(gl.ntextures+1 <= gl.ctextures);
12542     tid = gl.ntextures++;
12543     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12544   } else {
12545     gl.freetexid = gl.textures[tid].nextfree;
12546   }
12547   assert(tid <= gl.ntextures);
12548 
12549   assert(gl.textures[tid].id == 0);
12550   tex = &gl.textures[tid];
12551   memset(tex, 0, (*tex).sizeof);
12552   tex.id = tid+1;
12553   tex.rc = 1;
12554   tex.nextfree = -1;
12555 
12556   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12557 
12558   return tex;
12559 }
12560 
12561 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12562   if (id <= 0 || id > gl.ntextures) return null;
12563   if (gl.textures[id-1].id == 0) return null; // free one
12564   assert(gl.textures[id-1].id == id);
12565   return &gl.textures[id-1];
12566 }
12567 
12568 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12569   if (id <= 0 || id > gl.ntextures) return false;
12570   auto tx = &gl.textures[id-1];
12571   if (tx.id == 0) { id = 0; return false; } // free one
12572   assert(tx.id == id);
12573   assert(tx.tex != 0);
12574   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12575   import core.atomic : atomicOp;
12576   if (atomicOp!"-="(tx.rc, 1) == 0) {
12577     import core.thread : ThreadID;
12578     ThreadID mytid;
12579     static if (__VERSION__ < 2076) {
12580       DGNoThrowNoGC(() {
12581         import core.thread; mytid = Thread.getThis.id;
12582       })();
12583     } else {
12584       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12585     }
12586     if (gl.mainTID == mytid && gl.inFrame) {
12587       // can delete it right now
12588       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12589       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12590       memset(tx, 0, (*tx).sizeof);
12591       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12592       tx.nextfree = gl.freetexid;
12593       gl.freetexid = id-1;
12594     } else {
12595       // alas, we aren't doing frame business, so we should postpone deletion
12596       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12597       version(aliced) {
12598         synchronized(GLNVGTextureLocker.classinfo) {
12599           tx.id = 0; // mark it as dead
12600           gl.mustCleanTextures = true; // set "need cleanup" flag
12601         }
12602       } else {
12603         try {
12604           synchronized(GLNVGTextureLocker.classinfo) {
12605             tx.id = 0; // mark it as dead
12606             gl.mustCleanTextures = true; // set "need cleanup" flag
12607           }
12608         } catch (Exception e) {}
12609       }
12610     }
12611   }
12612   id = 0;
12613   return true;
12614 }
12615 
12616 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12617   import core.stdc.stdio : fprintf, stderr;
12618   GLchar[512+1] str = 0;
12619   GLsizei len = 0;
12620   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12621   if (len > 512) len = 512;
12622   str[len] = '\0';
12623   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12624 }
12625 
12626 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12627   import core.stdc.stdio : fprintf, stderr;
12628   GLchar[512+1] str = 0;
12629   GLsizei len = 0;
12630   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12631   if (len > 512) len = 512;
12632   str[len] = '\0';
12633   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12634 }
12635 
12636 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12637   static if (!force) {
12638     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12639   }
12640   glGetError();
12641 }
12642 
12643 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12644   GLenum err;
12645   static if (!force) {
12646     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12647   }
12648   err = glGetError();
12649   if (err != GL_NO_ERROR) {
12650     import core.stdc.stdio : fprintf, stderr;
12651     fprintf(stderr, "Error %08x after %s\n", err, str);
12652     return;
12653   }
12654 }
12655 
12656 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12657   GLint status;
12658   GLuint prog, vert, frag;
12659   const(char)*[3] str;
12660 
12661   memset(shader, 0, (*shader).sizeof);
12662 
12663   prog = glCreateProgram();
12664   vert = glCreateShader(GL_VERTEX_SHADER);
12665   frag = glCreateShader(GL_FRAGMENT_SHADER);
12666   str[0] = header;
12667   str[1] = (opts !is null ? opts : "");
12668   str[2] = vshader;
12669   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12670 
12671   glCompileShader(vert);
12672   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12673   if (status != GL_TRUE) {
12674     glnvg__dumpShaderError(vert, name, "vert");
12675     return false;
12676   }
12677 
12678   str[0] = header;
12679   str[1] = (opts !is null ? opts : "");
12680   str[2] = fshader;
12681   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12682 
12683   glCompileShader(frag);
12684   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12685   if (status != GL_TRUE) {
12686     glnvg__dumpShaderError(frag, name, "frag");
12687     return false;
12688   }
12689 
12690   glAttachShader(prog, vert);
12691   glAttachShader(prog, frag);
12692 
12693   glBindAttribLocation(prog, 0, "vertex");
12694   glBindAttribLocation(prog, 1, "tcoord");
12695 
12696   glLinkProgram(prog);
12697   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12698   if (status != GL_TRUE) {
12699     glnvg__dumpProgramError(prog, name);
12700     return false;
12701   }
12702 
12703   shader.prog = prog;
12704   shader.vert = vert;
12705   shader.frag = frag;
12706 
12707   return true;
12708 }
12709 
12710 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12711   if (shader.prog != 0) glDeleteProgram(shader.prog);
12712   if (shader.vert != 0) glDeleteShader(shader.vert);
12713   if (shader.frag != 0) glDeleteShader(shader.frag);
12714 }
12715 
12716 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12717   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12718   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12719   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12720   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12721   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12722   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12723 }
12724 
12725 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12726   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12727     if (fbo != 0) {
12728       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12729       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12730       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12731       glDeleteFramebuffers(1, &fbo);
12732       fbo = 0;
12733     }
12734   }
12735   gl.fboWidth = gl.fboHeight = 0;
12736 }
12737 
12738 // returns `true` is new FBO was created
12739 // will not unbind buffer, if it was created
12740 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12741   assert(fidx >= 0 && fidx < gl.fbo.length);
12742   assert(gl.fboWidth > 0);
12743   assert(gl.fboHeight > 0);
12744 
12745   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12746 
12747   glnvg__resetError(gl);
12748 
12749   // allocate FBO object
12750   GLuint fbo = 0;
12751   glGenFramebuffers(1, &fbo);
12752   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12753   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12754   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12755   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12756 
12757   // attach 2D texture to this FBO
12758   GLuint tidColor = 0;
12759   glGenTextures(1, &tidColor);
12760   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12761   glBindTexture(GL_TEXTURE_2D, tidColor);
12762   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12763   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12764   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12765   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12766   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12767   //FIXME: linear or nearest?
12768   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12769   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12770   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12771   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12772   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12773   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12774   // empty texture
12775   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12776   // create texture with only one color channel
12777   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12778   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12779   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12780   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12781   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12782 
12783   // attach stencil texture to this FBO
12784   GLuint tidStencil = 0;
12785   version(nanovega_shared_stencil) {
12786     if (gl.fboTex.ptr[0].ptr[0] == 0) {
12787       glGenTextures(1, &tidStencil);
12788       if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12789       gl.fboTex.ptr[0].ptr[0] = tidStencil;
12790     } else {
12791       tidStencil = gl.fboTex.ptr[0].ptr[0];
12792     }
12793     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12794   } else {
12795     glGenTextures(1, &tidStencil);
12796     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12797     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12798   }
12799   glBindTexture(GL_TEXTURE_2D, tidStencil);
12800   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12801   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12802   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12803   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12804 
12805   {
12806     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12807     if (status != GL_FRAMEBUFFER_COMPLETE) {
12808       version(all) {
12809         import core.stdc.stdio;
12810         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12811         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12812         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12813         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12814       }
12815       assert(0, "NanoVega: framebuffer creation failed");
12816     }
12817   }
12818 
12819   // clear 'em all
12820   if (doclear) {
12821     glClearColor(0, 0, 0, 0);
12822     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12823   }
12824 
12825   // save texture ids
12826   gl.fbo.ptr[fidx] = fbo;
12827   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12828   version(nanovega_shared_stencil) {} else {
12829     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12830   }
12831 
12832   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12833 
12834   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12835 
12836   return true;
12837 }
12838 
12839 // will not unbind buffer
12840 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12841   assert(fidx >= 0 && fidx < gl.fbo.length);
12842   assert(gl.fboWidth > 0);
12843   assert(gl.fboHeight > 0);
12844   assert(gl.fbo.ptr[fidx] != 0);
12845   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12846   glClearColor(0, 0, 0, 0);
12847   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12848   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12849 }
12850 
12851 // will not unbind buffer
12852 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12853   import core.stdc..string : memset;
12854   assert(didx >= 0 && didx < gl.fbo.length);
12855   assert(sidx >= 0 && sidx < gl.fbo.length);
12856   assert(gl.fboWidth > 0);
12857   assert(gl.fboHeight > 0);
12858   assert(gl.fbo.ptr[didx] != 0);
12859   assert(gl.fbo.ptr[sidx] != 0);
12860   if (didx == sidx) return;
12861 
12862   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12863 
12864   glUseProgram(gl.shaderCopyFBO.prog);
12865 
12866   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12867   glDisable(GL_CULL_FACE);
12868   glDisable(GL_BLEND);
12869   glDisable(GL_SCISSOR_TEST);
12870   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12871   // copy texture by drawing full quad
12872   enum x = 0;
12873   enum y = 0;
12874   immutable int w = gl.fboWidth;
12875   immutable int h = gl.fboHeight;
12876   immutable(NVGVertex[4]) vertices =
12877    [NVGVertex(x, y), // top-left
12878     NVGVertex(w, y), // top-right
12879     NVGVertex(w, h), // bottom-right
12880     NVGVertex(x, h)]; // bottom-left
12881 
12882   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12883   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12884 
12885   // restore state (but don't unbind FBO)
12886   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12887   glEnable(GL_CULL_FACE);
12888   glEnable(GL_BLEND);
12889   glUseProgram(gl.shader.prog);
12890 }
12891 
12892 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12893   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12894   /*
12895   if (gl.lastClipFBO >= 0) {
12896     glActiveTexture(GL_TEXTURE1);
12897     glBindTexture(GL_TEXTURE_2D, 0);
12898     glActiveTexture(GL_TEXTURE0);
12899   }
12900   */
12901   gl.lastClipFBO = -666;
12902 }
12903 
12904 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12905   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12906   if (gl.lastClipFBO != -666) {
12907     // cached
12908     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12909     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12910     return;
12911   }
12912 
12913   // no cache
12914   int fboidx = -1;
12915   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12916     final switch (mst) {
12917       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12918       case GLMaskState.Uninitialized: break;
12919       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12920       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12921     }
12922   }
12923 
12924   if (fboidx < 0) {
12925     // don't mask
12926     gl.lastClipFBO = -1;
12927     frag.doclip = 0;
12928   } else {
12929     // do masking
12930     assert(gl.fbo.ptr[fboidx] != 0);
12931     gl.lastClipFBO = fboidx;
12932     frag.doclip = 1;
12933   }
12934 
12935   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12936 
12937   if (gl.lastClipFBO >= 0) {
12938     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12939     glActiveTexture(GL_TEXTURE1);
12940     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12941     glActiveTexture(GL_TEXTURE0);
12942   }
12943 }
12944 
12945 // returns index in `gl.fbo`, or -1 for "don't mask"
12946 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12947   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12948   // we need initialized FBO, even for "don't mask" case
12949   // for this, look back in stack, and either copy initialized FBO,
12950   // or stop at first uninitialized one, and clear it
12951   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12952     // shortcut
12953     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generation of new texture is skipped (already initialized)\n", gl.msp-1); }
12954     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
12955     return gl.msp-1;
12956   }
12957   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
12958     final switch (gl.maskStack.ptr[sp]) {
12959       case GLMaskState.DontMask:
12960         // clear it
12961         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
12962         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
12963         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
12964         return gl.msp-1;
12965       case GLMaskState.Uninitialized: break; // do nothing
12966       case GLMaskState.Initialized:
12967         // i found her! copy to TOS
12968         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
12969         glnvg__allocFBO(gl, gl.msp-1, false);
12970         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
12971         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
12972         return gl.msp-1;
12973       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
12974     }
12975   }
12976   // nothing was initialized, lol
12977   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
12978   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
12979   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
12980   return gl.msp-1;
12981 }
12982 
12983 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
12984   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12985   GLNVGcall* call = glnvg__allocCall(gl);
12986   if (call is null) return;
12987   call.type = GLNVG_PUSHCLIP;
12988 }
12989 
12990 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
12991   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12992   GLNVGcall* call = glnvg__allocCall(gl);
12993   if (call is null) return;
12994   call.type = GLNVG_POPCLIP;
12995 }
12996 
12997 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
12998   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
12999   GLNVGcall* call = glnvg__allocCall(gl);
13000   if (call is null) return;
13001   call.type = GLNVG_RESETCLIP;
13002 }
13003 
13004 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13005   version(nanovega_debug_clipping) {
13006     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13007     GLNVGcall* call = glnvg__allocCall(gl);
13008     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13009   }
13010 }
13011 
13012 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13013   import core.stdc.stdio : snprintf;
13014 
13015   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13016   enum align_ = 4;
13017 
13018   char[64] shaderHeader = void;
13019   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13020   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13021 
13022   enum fillVertShader = q{
13023     uniform vec2 viewSize;
13024     attribute vec2 vertex;
13025     attribute vec2 tcoord;
13026     varying vec2 ftcoord;
13027     varying vec2 fpos;
13028     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13029     uniform vec2 ttr; /* tx and ty of affine matrix */
13030     void main (void) {
13031       /* affine transformation */
13032       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13033       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13034       ftcoord = tcoord;
13035       fpos = vec2(nx, ny);
13036       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13037     }
13038   };
13039 
13040   enum fillFragShader = `
13041     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13042     uniform sampler2D tex;
13043     uniform sampler2D clipTex;
13044     uniform vec2 viewSize;
13045     varying vec2 ftcoord;
13046     varying vec2 fpos;
13047     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13048     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13049     #define innerCol frag[6]
13050     #define middleCol frag[7]
13051     #define outerCol frag[7+1]
13052     #define scissorExt frag[8+1].xy
13053     #define scissorScale frag[8+1].zw
13054     #define extent frag[9+1].xy
13055     #define radius frag[9+1].z
13056     #define feather frag[9+1].w
13057     #define strokeMult frag[10+1].x
13058     #define strokeThr frag[10+1].y
13059     #define texType int(frag[10+1].z)
13060     #define type int(frag[10+1].w)
13061     #define doclip int(frag[11+1].x)
13062     #define midp frag[11+1].y
13063 
13064     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13065       vec2 ext2 = ext-vec2(rad, rad);
13066       vec2 d = abs(pt)-ext2;
13067       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13068     }
13069 
13070     // Scissoring
13071     float scissorMask (in vec2 p) {
13072       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13073       sc = vec2(0.5, 0.5)-sc*scissorScale;
13074       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13075     }
13076 
13077     #ifdef EDGE_AA
13078     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13079     float strokeMask () {
13080       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13081     }
13082     #endif
13083 
13084     void main (void) {
13085       // clipping
13086       if (doclip != 0) {
13087         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13088         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13089         if (clr.r == 0.0) discard;
13090       }
13091       float scissor = scissorMask(fpos);
13092       if (scissor <= 0.0) discard; //k8: is it really faster?
13093       #ifdef EDGE_AA
13094       float strokeAlpha = strokeMask();
13095       if (strokeAlpha < strokeThr) discard;
13096       #else
13097       float strokeAlpha = 1.0;
13098       #endif
13099       // rendering
13100       vec4 color;
13101       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13102         color = innerCol;
13103         // Combine alpha
13104         color *= strokeAlpha*scissor;
13105       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13106         // Gradient
13107         // Calculate gradient color using box gradient
13108         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13109         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13110         if (midp <= 0.0) {
13111           color = mix(innerCol, outerCol, d);
13112         } else {
13113           float gdst = min(midp, 1.0);
13114           if (d < gdst) {
13115             color = mix(innerCol, middleCol, d/gdst);
13116           } else {
13117             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13118           }
13119         }
13120         // Combine alpha
13121         color *= strokeAlpha*scissor;
13122       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13123         // Image
13124         // Calculate color from texture
13125         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13126         color = texture2D(tex, pt);
13127         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13128         if (texType == 2) color = vec4(color.x);
13129         // Apply color tint and alpha
13130         color *= innerCol;
13131         // Combine alpha
13132         color *= strokeAlpha*scissor;
13133       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13134         // Stencil fill
13135         color = vec4(1, 1, 1, 1);
13136       } else if (type == 4) { /* NSVG_SHADER_IMG */
13137         // Textured tris
13138         color = texture2D(tex, ftcoord);
13139         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13140         if (texType == 2) color = vec4(color.x);
13141         color *= scissor;
13142         color *= innerCol; // Apply color tint
13143       }
13144       gl_FragColor = color;
13145     }
13146   `;
13147 
13148   enum clipVertShaderFill = q{
13149     uniform vec2 viewSize;
13150     attribute vec2 vertex;
13151     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13152     uniform vec2 ttr; /* tx and ty of affine matrix */
13153     void main (void) {
13154       /* affine transformation */
13155       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13156       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13157       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13158     }
13159   };
13160 
13161   enum clipFragShaderFill = q{
13162     uniform vec2 viewSize;
13163     void main (void) {
13164       gl_FragColor = vec4(1, 1, 1, 1);
13165     }
13166   };
13167 
13168   enum clipVertShaderCopy = q{
13169     uniform vec2 viewSize;
13170     attribute vec2 vertex;
13171     void main (void) {
13172       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13173     }
13174   };
13175 
13176   enum clipFragShaderCopy = q{
13177     uniform sampler2D tex;
13178     uniform vec2 viewSize;
13179     void main (void) {
13180       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13181       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13182     }
13183   };
13184 
13185   glnvg__checkError(gl, "init");
13186 
13187   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13188   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13189   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13190   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13191 
13192   glnvg__checkError(gl, "uniform locations");
13193   glnvg__getUniforms(&gl.shader);
13194   glnvg__getUniforms(&gl.shaderFillFBO);
13195   glnvg__getUniforms(&gl.shaderCopyFBO);
13196 
13197   // Create dynamic vertex array
13198   glGenBuffers(1, &gl.vertBuf);
13199 
13200   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13201 
13202   glnvg__checkError(gl, "create done");
13203 
13204   glFinish();
13205 
13206   return true;
13207 }
13208 
13209 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13210   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13211   GLNVGtexture* tex = glnvg__allocTexture(gl);
13212 
13213   if (tex is null) return 0;
13214 
13215   glGenTextures(1, &tex.tex);
13216   tex.width = w;
13217   tex.height = h;
13218   tex.type = type;
13219   tex.flags = imageFlags;
13220   glnvg__bindTexture(gl, tex.tex);
13221 
13222   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13223 
13224   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13225   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13226   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13227   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13228 
13229 
13230 
13231   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13232   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13233   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13234   // it needs to be called after the glTexImage2D call
13235   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13236     glGenerateMipmap(GL_TEXTURE_2D);
13237 
13238   immutable tfmin =
13239     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13240      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13241      GL_LINEAR);
13242   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13243   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13244   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13245 
13246   int flag;
13247   if (imageFlags&NVGImageFlag.RepeatX)
13248     flag = GL_REPEAT;
13249   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13250     flag = GL_CLAMP_TO_BORDER;
13251   else 
13252     flag = GL_CLAMP_TO_EDGE;
13253   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13254 
13255 
13256   if (imageFlags&NVGImageFlag.RepeatY)
13257     flag = GL_REPEAT;
13258   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13259     flag = GL_CLAMP_TO_BORDER;
13260   else 
13261     flag = GL_CLAMP_TO_EDGE;
13262   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13263 
13264   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13265   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13266   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13267   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13268 
13269   glnvg__checkError(gl, "create tex");
13270   glnvg__bindTexture(gl, 0);
13271 
13272   return tex.id;
13273 }
13274 
13275 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13276   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13277   return glnvg__deleteTexture(gl, image);
13278 }
13279 
13280 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13281   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13282   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13283   if (tex is null) {
13284     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13285     return false;
13286   }
13287   import core.atomic : atomicOp;
13288   atomicOp!"+="(tex.rc, 1);
13289   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13290   return true;
13291 }
13292 
13293 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13294   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13295   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13296 
13297   if (tex is null) {
13298     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13299     return false;
13300   }
13301 
13302   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13303 
13304   glnvg__bindTexture(gl, tex.tex);
13305 
13306   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13307   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13308   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13309   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13310 
13311   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13312   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13313 
13314   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13315   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13316   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13317   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13318 
13319   glnvg__bindTexture(gl, 0);
13320 
13321   return true;
13322 }
13323 
13324 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13325   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13326   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13327   if (tex is null) {
13328     if (w !is null) *w = 0;
13329     if (h !is null) *h = 0;
13330     return false;
13331   } else {
13332     if (w !is null) *w = tex.width;
13333     if (h !is null) *h = tex.height;
13334     return true;
13335   }
13336 }
13337 
13338 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13339   assert(t.length >= 6);
13340   assert(m3.length >= 12);
13341   m3.ptr[0] = t.ptr[0];
13342   m3.ptr[1] = t.ptr[1];
13343   m3.ptr[2] = 0.0f;
13344   m3.ptr[3] = 0.0f;
13345   m3.ptr[4] = t.ptr[2];
13346   m3.ptr[5] = t.ptr[3];
13347   m3.ptr[6] = 0.0f;
13348   m3.ptr[7] = 0.0f;
13349   m3.ptr[8] = t.ptr[4];
13350   m3.ptr[9] = t.ptr[5];
13351   m3.ptr[10] = 1.0f;
13352   m3.ptr[11] = 0.0f;
13353 }
13354 
13355 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc {
13356   //pragma(inline, true);
13357   NVGColor res = void;
13358   res.r = c.r*c.a;
13359   res.g = c.g*c.a;
13360   res.b = c.b*c.a;
13361   res.a = c.a;
13362   return res;
13363 }
13364 
13365 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13366   import core.stdc.math : sqrtf;
13367   GLNVGtexture* tex = null;
13368   NVGMatrix invxform = void;
13369 
13370   memset(frag, 0, (*frag).sizeof);
13371 
13372   frag.innerCol = glnvg__premulColor(paint.innerColor);
13373   frag.middleCol = glnvg__premulColor(paint.middleColor);
13374   frag.outerCol = glnvg__premulColor(paint.outerColor);
13375   frag.midp = paint.midp;
13376 
13377   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13378     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13379     frag.scissorExt.ptr[0] = 1.0f;
13380     frag.scissorExt.ptr[1] = 1.0f;
13381     frag.scissorScale.ptr[0] = 1.0f;
13382     frag.scissorScale.ptr[1] = 1.0f;
13383   } else {
13384     //nvgTransformInverse(invxform[], scissor.xform[]);
13385     invxform = scissor.xform.inverted;
13386     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13387     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13388     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13389     frag.scissorScale.ptr[0] = sqrtf(scissor.xform.mat.ptr[0]*scissor.xform.mat.ptr[0]+scissor.xform.mat.ptr[2]*scissor.xform.mat.ptr[2])/fringe;
13390     frag.scissorScale.ptr[1] = sqrtf(scissor.xform.mat.ptr[1]*scissor.xform.mat.ptr[1]+scissor.xform.mat.ptr[3]*scissor.xform.mat.ptr[3])/fringe;
13391   }
13392 
13393   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13394   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13395   frag.strokeThr = strokeThr;
13396 
13397   if (paint.image.valid) {
13398     tex = glnvg__findTexture(gl, paint.image.id);
13399     if (tex is null) return false;
13400     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13401       /*
13402       NVGMatrix flipped;
13403       nvgTransformScale(flipped[], 1.0f, -1.0f);
13404       nvgTransformMultiply(flipped[], paint.xform[]);
13405       nvgTransformInverse(invxform[], flipped[]);
13406       */
13407       /*
13408       NVGMatrix m1 = void, m2 = void;
13409       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13410       nvgTransformMultiply(m1[], paint.xform[]);
13411       nvgTransformScale(m2[], 1.0f, -1.0f);
13412       nvgTransformMultiply(m2[], m1[]);
13413       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13414       nvgTransformMultiply(m1[], m2[]);
13415       nvgTransformInverse(invxform[], m1[]);
13416       */
13417       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13418       m1.mul(paint.xform);
13419       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13420       m2.mul(m1);
13421       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13422       m1.mul(m2);
13423       invxform = m1.inverted;
13424     } else {
13425       //nvgTransformInverse(invxform[], paint.xform[]);
13426       invxform = paint.xform.inverted;
13427     }
13428     frag.type = NSVG_SHADER_FILLIMG;
13429 
13430     if (tex.type == NVGtexture.RGBA) {
13431       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13432     } else {
13433       frag.texType = 2;
13434     }
13435     //printf("frag.texType = %d\n", frag.texType);
13436   } else {
13437     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13438     frag.radius = paint.radius;
13439     frag.feather = paint.feather;
13440     //nvgTransformInverse(invxform[], paint.xform[]);
13441     invxform = paint.xform.inverted;
13442   }
13443 
13444   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13445 
13446   return true;
13447 }
13448 
13449 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13450   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13451   glnvg__setFBOClipTexture(gl, frag);
13452   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13453   glnvg__checkError(gl, "glnvg__setUniforms");
13454   if (image != 0) {
13455     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13456     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13457     glnvg__checkError(gl, "tex paint tex");
13458   } else {
13459     glnvg__bindTexture(gl, 0);
13460   }
13461 }
13462 
13463 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13464   assert(clipmode != NVGClipMode.None);
13465 
13466   // fill FBO, clear stencil buffer
13467   //TODO: optimize with bounds?
13468   version(all) {
13469     //glnvg__resetAffine(gl);
13470     //glUseProgram(gl.shaderFillFBO.prog);
13471     glDisable(GL_CULL_FACE);
13472     glDisable(GL_BLEND);
13473     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13474     glEnable(GL_STENCIL_TEST);
13475     if (gl.doClipUnion) {
13476       // for "and" we should clear everything that is NOT stencil-masked
13477       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13478       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13479     } else {
13480       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13481       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13482     }
13483 
13484     immutable(NVGVertex[4]) vertices =
13485      [NVGVertex(0, 0, 0, 0),
13486       NVGVertex(0, gl.fboHeight, 0, 0),
13487       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13488       NVGVertex(gl.fboWidth, 0, 0, 0)];
13489 
13490     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13491     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13492 
13493     //glnvg__restoreAffine(gl);
13494   }
13495 
13496   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13497   glDisable(GL_COLOR_LOGIC_OP);
13498   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13499   glEnable(GL_BLEND);
13500   glDisable(GL_STENCIL_TEST);
13501   glEnable(GL_CULL_FACE);
13502   glUseProgram(gl.shader.prog);
13503 
13504   // set current FBO as used one
13505   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13506   if (gl.lastClipFBO != gl.msp-1) {
13507     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache from changed mask (old:%d; new:%d)\n", gl.msp-1, gl.lastClipFBO, gl.msp-1); }
13508     gl.lastClipFBO = gl.msp-1;
13509     glActiveTexture(GL_TEXTURE1);
13510     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13511     glActiveTexture(GL_TEXTURE0);
13512   }
13513 }
13514 
13515 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13516   assert(clipmode != NVGClipMode.None);
13517   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13518   // save uniform offset for `glnvg__finishClip()`
13519   gl.lastClipUniOfs = uniformOffset;
13520   // get FBO index, bind this FBO
13521   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13522   assert(clipTexId >= 0);
13523   glUseProgram(gl.shaderFillFBO.prog);
13524   glnvg__checkError(gl, "use");
13525   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13526   // set logic op for clip
13527   gl.doClipUnion = false;
13528   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13529     // it is cleared to zero, we can just draw a path
13530     glDisable(GL_COLOR_LOGIC_OP);
13531     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13532   } else {
13533     glEnable(GL_COLOR_LOGIC_OP);
13534     final switch (clipmode) {
13535       case NVGClipMode.None: assert(0, "wtf?!");
13536       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13537       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13538       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13539       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13540       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13541     }
13542   }
13543   // set affine matrix
13544   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13545   glnvg__checkError(gl, "affine 0");
13546   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13547   glnvg__checkError(gl, "affine 1");
13548   // setup common OpenGL parameters
13549   glDisable(GL_BLEND);
13550   glDisable(GL_CULL_FACE);
13551   glEnable(GL_STENCIL_TEST);
13552   glnvg__stencilMask(gl, 0xff);
13553   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13554   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13555   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13556 }
13557 
13558 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13559   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13560   gl.inFrame = true;
13561   gl.view.ptr[0] = cast(float)width;
13562   gl.view.ptr[1] = cast(float)height;
13563   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13564   if (width != gl.fboWidth || height != gl.fboHeight) {
13565     glnvg__killFBOs(gl);
13566     gl.fboWidth = width;
13567     gl.fboHeight = height;
13568   }
13569   gl.msp = 1;
13570   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13571   // texture cleanup
13572   import core.atomic : atomicLoad;
13573   if (atomicLoad(gl.mustCleanTextures)) {
13574     try {
13575       import core.thread : Thread;
13576       static if (__VERSION__ < 2076) {
13577         DGNoThrowNoGC(() {
13578           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13579         })();
13580       } else {
13581         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13582       }
13583       synchronized(GLNVGTextureLocker.classinfo) {
13584         gl.mustCleanTextures = false;
13585         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13586           // no need to use atomic ops here, as we're locked
13587           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13588             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13589             import core.stdc..string : memset;
13590             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13591             memset(&tex, 0, tex.sizeof);
13592             tex.nextfree = gl.freetexid;
13593             gl.freetexid = cast(int)tidx;
13594           }
13595         }
13596       }
13597     } catch (Exception e) {}
13598   }
13599 }
13600 
13601 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13602   GLNVGpath* paths = &gl.paths[call.pathOffset];
13603   int npaths = call.pathCount;
13604 
13605   if (call.clipmode == NVGClipMode.None) {
13606     // Draw shapes
13607     glEnable(GL_STENCIL_TEST);
13608     glnvg__stencilMask(gl, 0xffU);
13609     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13610 
13611     glnvg__setUniforms(gl, call.uniformOffset, 0);
13612     glnvg__checkError(gl, "fill simple");
13613 
13614     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13615     if (call.evenOdd) {
13616       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13617       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13618       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13619     } else {
13620       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13621       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13622     }
13623     glDisable(GL_CULL_FACE);
13624     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13625     glEnable(GL_CULL_FACE);
13626 
13627     // Draw anti-aliased pixels
13628     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13629     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13630     glnvg__checkError(gl, "fill fill");
13631 
13632     if (gl.flags&NVGContextFlag.Antialias) {
13633       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13634       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13635       // Draw fringes
13636       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13637     }
13638 
13639     // Draw fill
13640     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13641     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13642     if (call.evenOdd) {
13643       glDisable(GL_CULL_FACE);
13644       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13645       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13646       glEnable(GL_CULL_FACE);
13647     } else {
13648       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13649     }
13650 
13651     glDisable(GL_STENCIL_TEST);
13652   } else {
13653     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13654     glnvg__checkError(gl, "fillclip simple");
13655     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13656     if (call.evenOdd) {
13657       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13658       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13659       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13660     } else {
13661       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13662       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13663     }
13664     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13665     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13666   }
13667 }
13668 
13669 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13670   GLNVGpath* paths = &gl.paths[call.pathOffset];
13671   int npaths = call.pathCount;
13672 
13673   if (call.clipmode == NVGClipMode.None) {
13674     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13675     glnvg__checkError(gl, "convex fill");
13676     if (call.evenOdd) glDisable(GL_CULL_FACE);
13677     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13678     if (gl.flags&NVGContextFlag.Antialias) {
13679       // Draw fringes
13680       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13681     }
13682     if (call.evenOdd) glEnable(GL_CULL_FACE);
13683   } else {
13684     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13685     glnvg__checkError(gl, "clip convex fill");
13686     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13687     if (gl.flags&NVGContextFlag.Antialias) {
13688       // Draw fringes
13689       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13690     }
13691     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13692   }
13693 }
13694 
13695 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13696   GLNVGpath* paths = &gl.paths[call.pathOffset];
13697   int npaths = call.pathCount;
13698 
13699   if (call.clipmode == NVGClipMode.None) {
13700     if (gl.flags&NVGContextFlag.StencilStrokes) {
13701       glEnable(GL_STENCIL_TEST);
13702       glnvg__stencilMask(gl, 0xff);
13703 
13704       // Fill the stroke base without overlap
13705       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13706       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13707       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13708       glnvg__checkError(gl, "stroke fill 0");
13709       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13710 
13711       // Draw anti-aliased pixels.
13712       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13713       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13714       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13715       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13716 
13717       // Clear stencil buffer.
13718       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13719       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13720       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13721       glnvg__checkError(gl, "stroke fill 1");
13722       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13723       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13724 
13725       glDisable(GL_STENCIL_TEST);
13726 
13727       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13728     } else {
13729       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13730       glnvg__checkError(gl, "stroke fill");
13731       // Draw Strokes
13732       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13733     }
13734   } else {
13735     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13736     glnvg__checkError(gl, "stroke fill 0");
13737     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13738     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13739   }
13740 }
13741 
13742 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13743   if (call.clipmode == NVGClipMode.None) {
13744     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13745     glnvg__checkError(gl, "triangles fill");
13746     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13747   } else {
13748     //TODO(?): use texture as mask?
13749   }
13750 }
13751 
13752 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13753   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13754   glnvg__checkError(gl, "affine");
13755   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13756   glnvg__checkError(gl, "affine");
13757   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13758 }
13759 
13760 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13761   scope(exit) gl.inFrame = false;
13762   if (clearTextures && gl.inFrame) {
13763     try {
13764       import core.thread : Thread;
13765       static if (__VERSION__ < 2076) {
13766         DGNoThrowNoGC(() {
13767           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13768         })();
13769       } else {
13770         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13771       }
13772     } catch (Exception e) {}
13773     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13774   }
13775   gl.nverts = 0;
13776   gl.npaths = 0;
13777   gl.ncalls = 0;
13778   gl.nuniforms = 0;
13779   gl.msp = 1;
13780   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13781 }
13782 
13783 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13784   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13785 }
13786 
13787 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13788   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13789   if (factor == NVGBlendFactor.One) return GL_ONE;
13790   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13791   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13792   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13793   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13794   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13795   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13796   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13797   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13798   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13799   return GL_INVALID_ENUM;
13800 }
13801 
13802 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13803   GLNVGblend res;
13804   res.simple = op.simple;
13805   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13806   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13807   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13808   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13809   if (res.simple) {
13810     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13811       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13812     }
13813   } else {
13814     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13815       res.simple = true;
13816       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13817     }
13818   }
13819   return res;
13820 }
13821 
13822 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc {
13823   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13824   static if (NANOVG_GL_USE_STATE_FILTER) {
13825     if (gl.blendFunc.simple == op.simple) {
13826       if (op.simple) {
13827         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13828       } else {
13829         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13830       }
13831     }
13832     gl.blendFunc = op;
13833   }
13834   if (op.simple) {
13835     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13836       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13837     } else {
13838       glBlendFunc(op.srcAlpha, op.dstAlpha);
13839     }
13840   } else {
13841     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13842       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13843     } else {
13844       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13845     }
13846   }
13847 }
13848 
13849 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc {
13850   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13851   GLNVGcall* call;
13852   // if last operation was GLNVG_AFFINE, simply replace the matrix
13853   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13854     call = &gl.calls[gl.ncalls-1];
13855   } else {
13856     call = glnvg__allocCall(gl);
13857     if (call is null) return;
13858     call.type = GLNVG_AFFINE;
13859   }
13860   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13861 }
13862 
13863 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13864 
13865 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13866   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13867   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13868   try {
13869     import core.thread : Thread;
13870     static if (__VERSION__ < 2076) {
13871       DGNoThrowNoGC(() {
13872         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13873       })();
13874     } else {
13875       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13876     }
13877   } catch (Exception e) {}
13878   scope(exit) gl.inFrame = false;
13879 
13880   glnvg__resetError!true(gl);
13881   {
13882     int vv = 0;
13883     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13884     if (glGetError() || vv < 0) vv = 0;
13885     gl.mainFBO = cast(uint)vv;
13886   }
13887 
13888   enum ShaderType { None, Fill, Clip }
13889   auto lastShader = ShaderType.None;
13890   if (gl.ncalls > 0) {
13891     gl.msp = 1;
13892     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13893 
13894     // Setup require GL state.
13895     glUseProgram(gl.shader.prog);
13896 
13897     glActiveTexture(GL_TEXTURE1);
13898     glBindTexture(GL_TEXTURE_2D, 0);
13899     glActiveTexture(GL_TEXTURE0);
13900     glnvg__resetFBOClipTextureCache(gl);
13901 
13902     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13903     static if (NANOVG_GL_USE_STATE_FILTER) {
13904       gl.blendFunc.simple = true;
13905       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13906     }
13907     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13908     glEnable(GL_CULL_FACE);
13909     glCullFace(GL_BACK);
13910     glFrontFace(GL_CCW);
13911     glEnable(GL_BLEND);
13912     glDisable(GL_DEPTH_TEST);
13913     glDisable(GL_SCISSOR_TEST);
13914     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13915     glStencilMask(0xffffffff);
13916     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13917     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13918     glActiveTexture(GL_TEXTURE0);
13919     glBindTexture(GL_TEXTURE_2D, 0);
13920     static if (NANOVG_GL_USE_STATE_FILTER) {
13921       gl.boundTexture = 0;
13922       gl.stencilMask = 0xffffffff;
13923       gl.stencilFunc = GL_ALWAYS;
13924       gl.stencilFuncRef = 0;
13925       gl.stencilFuncMask = 0xffffffff;
13926     }
13927     glnvg__checkError(gl, "OpenGL setup");
13928 
13929     // Upload vertex data
13930     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13931     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
13932     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13933     glEnableVertexAttribArray(0);
13934     glEnableVertexAttribArray(1);
13935     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13936     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13937     glnvg__checkError(gl, "vertex data uploading");
13938 
13939     // Set view and texture just once per frame.
13940     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13941     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13942       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13943       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13944     }
13945     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13946     glnvg__checkError(gl, "render shader setup");
13947 
13948     // Reset affine transformations.
13949     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13950     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13951     glnvg__checkError(gl, "affine setup");
13952 
13953     // set clip shaders params
13954     // fill
13955     glUseProgram(gl.shaderFillFBO.prog);
13956     glnvg__checkError(gl, "clip shaders setup (fill 0)");
13957     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13958     glnvg__checkError(gl, "clip shaders setup (fill 1)");
13959     // copy
13960     glUseProgram(gl.shaderCopyFBO.prog);
13961     glnvg__checkError(gl, "clip shaders setup (copy 0)");
13962     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13963     glnvg__checkError(gl, "clip shaders setup (copy 1)");
13964     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
13965     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
13966     glnvg__checkError(gl, "clip shaders setup (copy 2)");
13967     // restore render shader
13968     glUseProgram(gl.shader.prog);
13969 
13970     //{ import core.stdc.stdio; printf("ViewSize=%u %u %u\n", gl.shader.loc[GLNVGuniformLoc.ViewSize], gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize]); }
13971 
13972     gl.lastAffine.identity;
13973 
13974     foreach (int i; 0..gl.ncalls) {
13975       GLNVGcall* call = &gl.calls[i];
13976       switch (call.type) {
13977         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
13978         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
13979         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
13980         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
13981         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
13982         // clip region management
13983         case GLNVG_PUSHCLIP:
13984           version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): push clip (cache:%d); current state is %d\n", gl.msp-1, gl.lastClipFBO, gl.maskStack.ptr[gl.msp-1]); }
13985           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
13986           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
13987             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
13988           } else {
13989             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
13990           }
13991           // no need to reset FBO cache here, as nothing was changed
13992           break;
13993         case GLNVG_POPCLIP:
13994           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
13995           version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): pop clip (cache:%d); current state is %d; previous state is %d\n", gl.msp-1, gl.lastClipFBO, gl.maskStack.ptr[gl.msp-1], gl.maskStack.ptr[gl.msp-2]); }
13996           --gl.msp;
13997           assert(gl.msp > 0);
13998           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
13999           // check popped item
14000           final switch (gl.maskStack.ptr[gl.msp]) {
14001             case GLMaskState.DontMask:
14002               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14003               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14004                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14005                 glnvg__resetFBOClipTextureCache(gl);
14006               }
14007               break;
14008             case GLMaskState.Uninitialized:
14009               // if last FBO texture was uninitialized, it means that nothing was changed,
14010               // so we can keep using cached FBO
14011               break;
14012             case GLMaskState.Initialized:
14013               // if last FBO was initialized, it means that something was definitely changed
14014               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14015               glnvg__resetFBOClipTextureCache(gl);
14016               break;
14017             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14018           }
14019           break;
14020         case GLNVG_RESETCLIP:
14021           // mark current mask as "don't mask"
14022           version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): reset clip (cache:%d); current state is %d\n", gl.msp-1, gl.lastClipFBO, gl.maskStack.ptr[gl.msp-1]); }
14023           if (gl.msp > 0) {
14024             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14025               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14026               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14027               glnvg__resetFBOClipTextureCache(gl);
14028             }
14029           }
14030           break;
14031         case GLNVG_CLIP_DDUMP_ON:
14032           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14033           break;
14034         case GLNVG_CLIP_DDUMP_OFF:
14035           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14036           break;
14037         case GLNVG_NONE: break;
14038         default:
14039           {
14040             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14041           }
14042           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14043       }
14044       // and free texture, why not
14045       glnvg__deleteTexture(gl, call.image);
14046     }
14047 
14048     glDisableVertexAttribArray(0);
14049     glDisableVertexAttribArray(1);
14050     glDisable(GL_CULL_FACE);
14051     glBindBuffer(GL_ARRAY_BUFFER, 0);
14052     glUseProgram(0);
14053     glnvg__bindTexture(gl, 0);
14054   }
14055 
14056   // this will do all necessary cleanup
14057   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14058 }
14059 
14060 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14061   int count = 0;
14062   foreach (int i; 0..npaths) {
14063     count += paths[i].nfill;
14064     count += paths[i].nstroke;
14065   }
14066   return count;
14067 }
14068 
14069 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14070   GLNVGcall* ret = null;
14071   if (gl.ncalls+1 > gl.ccalls) {
14072     GLNVGcall* calls;
14073     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14074     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14075     if (calls is null) return null;
14076     gl.calls = calls;
14077     gl.ccalls = ccalls;
14078   }
14079   ret = &gl.calls[gl.ncalls++];
14080   memset(ret, 0, GLNVGcall.sizeof);
14081   return ret;
14082 }
14083 
14084 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14085   int ret = 0;
14086   if (gl.npaths+n > gl.cpaths) {
14087     GLNVGpath* paths;
14088     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14089     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14090     if (paths is null) return -1;
14091     gl.paths = paths;
14092     gl.cpaths = cpaths;
14093   }
14094   ret = gl.npaths;
14095   gl.npaths += n;
14096   return ret;
14097 }
14098 
14099 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14100   int ret = 0;
14101   if (gl.nverts+n > gl.cverts) {
14102     NVGVertex* verts;
14103     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14104     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14105     if (verts is null) return -1;
14106     gl.verts = verts;
14107     gl.cverts = cverts;
14108   }
14109   ret = gl.nverts;
14110   gl.nverts += n;
14111   return ret;
14112 }
14113 
14114 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14115   int ret = 0, structSize = gl.fragSize;
14116   if (gl.nuniforms+n > gl.cuniforms) {
14117     ubyte* uniforms;
14118     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14119     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14120     if (uniforms is null) return -1;
14121     gl.uniforms = uniforms;
14122     gl.cuniforms = cuniforms;
14123   }
14124   ret = gl.nuniforms*structSize;
14125   gl.nuniforms += n;
14126   return ret;
14127 }
14128 
14129 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14130   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14131 }
14132 
14133 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14134   vtx.x = x;
14135   vtx.y = y;
14136   vtx.u = u;
14137   vtx.v = v;
14138 }
14139 
14140 void glnvg__renderFill (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, float fringe, const(float)* bounds, const(NVGpath)* paths, int npaths, bool evenOdd) nothrow @trusted @nogc {
14141   if (npaths < 1) return;
14142 
14143   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14144   GLNVGcall* call = glnvg__allocCall(gl);
14145   NVGVertex* quad;
14146   GLNVGfragUniforms* frag;
14147   int maxverts, offset;
14148 
14149   if (call is null) return;
14150 
14151   call.type = GLNVG_FILL;
14152   call.evenOdd = evenOdd;
14153   call.clipmode = clipmode;
14154   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14155   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14156   call.triangleCount = 4;
14157   call.pathOffset = glnvg__allocPaths(gl, npaths);
14158   if (call.pathOffset == -1) goto error;
14159   call.pathCount = npaths;
14160   call.image = paint.image.id;
14161   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14162 
14163   if (npaths == 1 && paths[0].convex) {
14164     call.type = GLNVG_CONVEXFILL;
14165     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14166   }
14167 
14168   // Allocate vertices for all the paths.
14169   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14170   offset = glnvg__allocVerts(gl, maxverts);
14171   if (offset == -1) goto error;
14172 
14173   foreach (int i; 0..npaths) {
14174     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14175     const(NVGpath)* path = &paths[i];
14176     memset(copy, 0, GLNVGpath.sizeof);
14177     if (path.nfill > 0) {
14178       copy.fillOffset = offset;
14179       copy.fillCount = path.nfill;
14180       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14181       offset += path.nfill;
14182     }
14183     if (path.nstroke > 0) {
14184       copy.strokeOffset = offset;
14185       copy.strokeCount = path.nstroke;
14186       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14187       offset += path.nstroke;
14188     }
14189   }
14190 
14191   // Setup uniforms for draw calls
14192   if (call.type == GLNVG_FILL) {
14193     import core.stdc..string : memcpy;
14194     // Quad
14195     call.triangleOffset = offset;
14196     quad = &gl.verts[call.triangleOffset];
14197     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14198     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14199     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14200     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14201     // Get uniform
14202     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14203     if (call.uniformOffset == -1) goto error;
14204     // Simple shader for stencil
14205     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14206     memset(frag, 0, (*frag).sizeof);
14207     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14208     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14209     frag.strokeThr = -1.0f;
14210     frag.type = NSVG_SHADER_SIMPLE;
14211     // Fill shader
14212     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14213   } else {
14214     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14215     if (call.uniformOffset == -1) goto error;
14216     // Fill shader
14217     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14218   }
14219 
14220   return;
14221 
14222 error:
14223   // We get here if call alloc was ok, but something else is not.
14224   // Roll back the last call to prevent drawing it.
14225   if (gl.ncalls > 0) --gl.ncalls;
14226 }
14227 
14228 void glnvg__renderStroke (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14229   if (npaths < 1) return;
14230 
14231   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14232   GLNVGcall* call = glnvg__allocCall(gl);
14233   int maxverts, offset;
14234 
14235   if (call is null) return;
14236 
14237   call.type = GLNVG_STROKE;
14238   call.clipmode = clipmode;
14239   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14240   call.pathOffset = glnvg__allocPaths(gl, npaths);
14241   if (call.pathOffset == -1) goto error;
14242   call.pathCount = npaths;
14243   call.image = paint.image.id;
14244   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14245 
14246   // Allocate vertices for all the paths.
14247   maxverts = glnvg__maxVertCount(paths, npaths);
14248   offset = glnvg__allocVerts(gl, maxverts);
14249   if (offset == -1) goto error;
14250 
14251   foreach (int i; 0..npaths) {
14252     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14253     const(NVGpath)* path = &paths[i];
14254     memset(copy, 0, GLNVGpath.sizeof);
14255     if (path.nstroke) {
14256       copy.strokeOffset = offset;
14257       copy.strokeCount = path.nstroke;
14258       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14259       offset += path.nstroke;
14260     }
14261   }
14262 
14263   if (gl.flags&NVGContextFlag.StencilStrokes) {
14264     // Fill shader
14265     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14266     if (call.uniformOffset == -1) goto error;
14267     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14268     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14269   } else {
14270     // Fill shader
14271     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14272     if (call.uniformOffset == -1) goto error;
14273     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14274   }
14275 
14276   return;
14277 
14278 error:
14279   // We get here if call alloc was ok, but something else is not.
14280   // Roll back the last call to prevent drawing it.
14281   if (gl.ncalls > 0) --gl.ncalls;
14282 }
14283 
14284 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14285   if (nverts < 1) return;
14286 
14287   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14288   GLNVGcall* call = glnvg__allocCall(gl);
14289   GLNVGfragUniforms* frag;
14290 
14291   if (call is null) return;
14292 
14293   call.type = GLNVG_TRIANGLES;
14294   call.clipmode = clipmode;
14295   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14296   call.image = paint.image.id;
14297   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14298 
14299   // Allocate vertices for all the paths.
14300   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14301   if (call.triangleOffset == -1) goto error;
14302   call.triangleCount = nverts;
14303 
14304   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14305 
14306   // Fill shader
14307   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14308   if (call.uniformOffset == -1) goto error;
14309   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14310   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14311   frag.type = NSVG_SHADER_IMG;
14312 
14313   return;
14314 
14315 error:
14316   // We get here if call alloc was ok, but something else is not.
14317   // Roll back the last call to prevent drawing it.
14318   if (gl.ncalls > 0) --gl.ncalls;
14319 }
14320 
14321 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14322   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14323   if (gl is null) return;
14324 
14325   glnvg__killFBOs(gl);
14326   glnvg__deleteShader(&gl.shader);
14327   glnvg__deleteShader(&gl.shaderFillFBO);
14328   glnvg__deleteShader(&gl.shaderCopyFBO);
14329 
14330   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14331 
14332   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14333     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14334       assert(tex.tex != 0);
14335       glDeleteTextures(1, &tex.tex);
14336     }
14337   }
14338   free(gl.textures);
14339 
14340   free(gl.paths);
14341   free(gl.verts);
14342   free(gl.uniforms);
14343   free(gl.calls);
14344 
14345   free(gl);
14346 }
14347 
14348 
14349 /** Creates NanoVega contexts for OpenGL2+.
14350  *
14351  * Specify creation flags as additional arguments, like this:
14352  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14353  *
14354  * If you won't specify any flags, defaults will be used:
14355  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14356  *
14357  * Group: context_management
14358  */
14359 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14360   version(aliced) {
14361     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14362   } else {
14363     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14364   }
14365   uint flags = 0;
14366   if (flagList.length != 0) {
14367     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14368   } else {
14369     flags = DefaultFlags;
14370   }
14371   NVGparams params = void;
14372   NVGContext ctx = null;
14373   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14374   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14375   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14376   if (gl is null) goto error;
14377   memset(gl, 0, GLNVGcontext.sizeof);
14378 
14379   memset(&params, 0, params.sizeof);
14380   params.renderCreate = &glnvg__renderCreate;
14381   params.renderCreateTexture = &glnvg__renderCreateTexture;
14382   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14383   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14384   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14385   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14386   params.renderViewport = &glnvg__renderViewport;
14387   params.renderCancel = &glnvg__renderCancel;
14388   params.renderFlush = &glnvg__renderFlush;
14389   params.renderPushClip = &glnvg__renderPushClip;
14390   params.renderPopClip = &glnvg__renderPopClip;
14391   params.renderResetClip = &glnvg__renderResetClip;
14392   params.renderFill = &glnvg__renderFill;
14393   params.renderStroke = &glnvg__renderStroke;
14394   params.renderTriangles = &glnvg__renderTriangles;
14395   params.renderSetAffine = &glnvg__renderSetAffine;
14396   params.renderDelete = &glnvg__renderDelete;
14397   params.userPtr = gl;
14398   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14399   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14400     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14401   } else {
14402     params.fontAA = NVG_INVERT_FONT_AA;
14403   }
14404 
14405   gl.flags = flags;
14406   gl.freetexid = -1;
14407 
14408   ctx = createInternal(&params);
14409   if (ctx is null) goto error;
14410 
14411   static if (__VERSION__ < 2076) {
14412     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14413   } else {
14414     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14415   }
14416 
14417   return ctx;
14418 
14419 error:
14420   // 'gl' is freed by nvgDeleteInternal.
14421   if (ctx !is null) ctx.deleteInternal();
14422   return null;
14423 }
14424 
14425 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14426 /// Group: images
14427 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14428   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14429   GLNVGtexture* tex = glnvg__allocTexture(gl);
14430 
14431   if (tex is null) return 0;
14432 
14433   tex.type = NVGtexture.RGBA;
14434   tex.tex = textureId;
14435   tex.flags = imageFlags;
14436   tex.width = w;
14437   tex.height = h;
14438 
14439   return tex.id;
14440 }
14441 
14442 /// Create NVGImage from OpenGL texture id.
14443 /// Group: images
14444 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14445   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14446 
14447   NVGImage res;
14448   if (id > 0) {
14449     res.id = id;
14450     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14451     res.ctx = ctx;
14452     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14453   }
14454   return res;
14455 }
14456 
14457 /// Returns OpenGL texture id for NanoVega image.
14458 /// Group: images
14459 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14460   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14461   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14462   return tex.tex;
14463 }
14464 
14465 
14466 // ////////////////////////////////////////////////////////////////////////// //
14467 private:
14468 
14469 static if (NanoVegaHasFontConfig) {
14470   version(nanovg_builtin_fontconfig_bindings) {
14471     pragma(lib, "fontconfig");
14472 
14473     private extern(C) nothrow @trusted @nogc {
14474       enum FC_FILE = "file"; /* String */
14475       alias FcBool = int;
14476       alias FcChar8 = char;
14477       struct FcConfig;
14478       struct FcPattern;
14479       alias FcMatchKind = int;
14480       enum : FcMatchKind {
14481         FcMatchPattern,
14482         FcMatchFont,
14483         FcMatchScan
14484       }
14485       alias FcResult = int;
14486       enum : FcResult {
14487         FcResultMatch,
14488         FcResultNoMatch,
14489         FcResultTypeMismatch,
14490         FcResultNoId,
14491         FcResultOutOfMemory
14492       }
14493       FcBool FcInit ();
14494       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14495       void FcDefaultSubstitute (FcPattern* pattern);
14496       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14497       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14498       FcPattern* FcNameParse (const(FcChar8)* name);
14499       void FcPatternDestroy (FcPattern* p);
14500       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14501     }
14502   }
14503 
14504   __gshared bool fontconfigAvailable = false;
14505   // initialize fontconfig
14506   shared static this () {
14507     if (FcInit()) {
14508       fontconfigAvailable = true;
14509     } else {
14510       import core.stdc.stdio : stderr, fprintf;
14511       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14512     }
14513   }
14514 }
14515 
14516 
14517 // ////////////////////////////////////////////////////////////////////////// //
14518 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14519 
14520 private static immutable ubyte[7641] baphometPath = [
14521   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14522   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14523   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14524   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14525   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14526   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14527   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14528   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14529   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14530   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14531   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14532   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14533   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14534   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14535   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14536   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14537   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14538   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14539   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14540   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14541   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14542   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14543   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14544   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14545   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14546   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14547   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14548   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14549   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14550   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14551   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14552   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14553   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14554   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14555   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14556   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14557   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14558   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14559   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14560   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14561   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14562   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14563   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14564   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14565   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14566   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14567   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14568   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14569   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14570   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14571   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14572   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14573   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14574   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14575   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14576   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14577   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14578   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14579   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14580   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14581   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14582   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14583   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14584   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14585   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14586   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14587   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14588   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14589   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14590   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14591   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14592   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14593   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14594   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14595   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14596   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14597   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14598   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14599   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14600   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14601   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14602   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14603   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14604   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14605   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14606   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14607   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14608   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14609   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14610   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14611   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14612   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14613   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14614   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14615   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14616   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14617   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14618   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14619   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14620   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14621   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14622   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14623   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14624   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14625   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14626   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14627   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14628   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14629   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14630   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14631   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14632   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14633   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14634   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14635   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14636   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14637   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14638   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14639   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14640   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14641   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14642   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14643   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14644   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14645   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14646   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14647   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14648   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14649   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14650   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14651   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14652   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14653   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14654   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14655   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14656   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14657   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14658   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14659   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14660   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14661   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14662   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14663   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14664   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14665   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14666   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14667   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14668   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14669   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14670   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14671   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14672   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14673   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14674   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14675   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14676   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14677   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14678   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14679   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14680   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14681   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14682   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14683   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14684   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14685   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14686   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14687   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14688   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14689   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14690   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14691   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14692   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14693   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14694   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14695   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14696   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14697   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14698   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14699   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14700   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14701   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14702   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14703   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14704   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14705   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14706   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14707   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14708   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14709   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14710   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14711   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14712   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14713   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14714   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14715   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14716   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14717   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14718   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14719   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14720   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14721   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14722   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14723   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14724   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14725   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14726   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14727   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14728   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14729   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14730   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14731   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14732   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14733   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14734   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14735   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14736   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14737   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14738   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14739   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14740   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14741   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14742   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14743   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14744   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14745   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14746   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14747   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14748   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14749   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14750   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14751   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14752   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14753   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14754   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14755   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14756   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14757   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14758   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14759   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14760   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14761   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14762   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14763   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14764   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14765   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14766   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14767   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14768   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14769   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14770   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14771   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14772   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14773   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14774   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14775   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14776   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14777   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14778   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14779   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14780   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14781   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14782   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14783   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14784   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14785   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14786   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14787   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14788   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14789   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14790   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14791   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14792   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14793   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14794   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14795   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14796   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14797   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14798   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14799   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14800   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14801   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14802   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14803   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14804   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14805   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14806   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14807   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14808   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14809   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14810   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14811   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14812   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14813   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14814   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14815   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14816   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14817   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14818   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14819   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14820   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14821   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14822   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14823   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14824   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14825   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14826   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14827   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14828   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14829   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14830   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14831   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14832   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14833   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14834   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14835   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14836   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14837   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14838   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14839   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14840   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14841   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14842   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14843   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14844   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14845   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14846   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14847   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14848   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14849   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14850   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14851   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14852   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14853   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14854   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14855   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14856   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14857   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14858   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14859   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14860   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14861   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14862   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14863   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14864   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14865   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14866   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14867   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14868   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14869   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14870   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14871   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14872   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14873   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14874   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14875   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14876   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14877   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14878   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14879   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14880   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14881   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14882   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14883   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14884   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14885   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14886   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14887   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14888   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14889   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14890   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14891   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14892   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14893   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14894   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14895   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14896   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14897   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14898   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14899   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14900   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14901   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14902   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14903   0x09,];
14904 
14905 private struct ThePath {
14906 public:
14907   enum Command {
14908     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14909     StrokeMode,
14910     FillMode,
14911     StrokeFillMode,
14912     NormalStroke,
14913     ThinStroke,
14914     MoveTo,
14915     LineTo,
14916     CubicTo, // cubic bezier
14917     EndPath,
14918   }
14919 
14920 public:
14921   const(ubyte)[] path;
14922   uint ppos;
14923 
14924 public:
14925   this (const(void)[] apath) pure nothrow @trusted @nogc {
14926     path = cast(const(ubyte)[])apath;
14927   }
14928 
14929   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14930 
14931   Command getCommand () nothrow @trusted @nogc {
14932     pragma(inline, true);
14933     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14934     return cast(Command)(path.ptr[ppos++]);
14935   }
14936 
14937   // number of (x,y) pairs for this command
14938   static int argCount (in Command cmd) nothrow @safe @nogc {
14939     version(aliced) pragma(inline, true);
14940          if (cmd == Command.Bounds) return 2;
14941     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14942     else if (cmd == Command.CubicTo) return 3;
14943     else return 0;
14944   }
14945 
14946   void skipArgs (int argc) nothrow @trusted @nogc {
14947     pragma(inline, true);
14948     ppos += cast(uint)(float.sizeof*2*argc);
14949   }
14950 
14951   float getFloat () nothrow @trusted @nogc {
14952     pragma(inline, true);
14953     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
14954     version(LittleEndian) {
14955       float res = *cast(const(float)*)(&path.ptr[ppos]);
14956       ppos += cast(uint)float.sizeof;
14957       return res;
14958     } else {
14959       static assert(float.sizeof == 4);
14960       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
14961       ppos += cast(uint)float.sizeof;
14962       return *cast(const(float)*)(&xp);
14963     }
14964   }
14965 }
14966 
14967 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
14968 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
14969   if (nvg is null) return;
14970 
14971   auto path = ThePath(baphometPath);
14972 
14973   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
14974   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
14975 
14976   bool inPath = false;
14977   while (!path.empty) {
14978     auto cmd = path.getCommand();
14979     switch (cmd) {
14980       case ThePath.Command.MoveTo:
14981         inPath = true;
14982         immutable float ex = getScaledX();
14983         immutable float ey = getScaledY();
14984         nvg.moveTo(ex, ey);
14985         break;
14986       case ThePath.Command.LineTo:
14987         inPath = true;
14988         immutable float ex = getScaledX();
14989         immutable float ey = getScaledY();
14990         nvg.lineTo(ex, ey);
14991         break;
14992       case ThePath.Command.CubicTo: // cubic bezier
14993         inPath = true;
14994         immutable float x1 = getScaledX();
14995         immutable float y1 = getScaledY();
14996         immutable float x2 = getScaledX();
14997         immutable float y2 = getScaledY();
14998         immutable float ex = getScaledX();
14999         immutable float ey = getScaledY();
15000         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15001         break;
15002       case ThePath.Command.EndPath:
15003         if (inPath) return;
15004         break;
15005       default:
15006         path.skipArgs(path.argCount(cmd));
15007         break;
15008     }
15009   }
15010 }
15011 
15012 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15013 public void addBaphometPupils(bool left=true, bool right=true) (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15014   // pupils starts with "fill-and-stroke" mode
15015   if (nvg is null) return;
15016 
15017   auto path = ThePath(baphometPath);
15018 
15019   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15020   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15021 
15022   bool inPath = false;
15023   bool pupLeft = true;
15024   while (!path.empty) {
15025     auto cmd = path.getCommand();
15026     switch (cmd) {
15027       case ThePath.Command.StrokeFillMode: inPath = true; break;
15028       case ThePath.Command.MoveTo:
15029         if (!inPath) goto default;
15030         static if (!left) { if (pupLeft) goto default; }
15031         static if (!right) { if (!pupLeft) goto default; }
15032         immutable float ex = getScaledX();
15033         immutable float ey = getScaledY();
15034         nvg.moveTo(ex, ey);
15035         break;
15036       case ThePath.Command.LineTo:
15037         if (!inPath) goto default;
15038         static if (!left) { if (pupLeft) goto default; }
15039         static if (!right) { if (!pupLeft) goto default; }
15040         immutable float ex = getScaledX();
15041         immutable float ey = getScaledY();
15042         nvg.lineTo(ex, ey);
15043         break;
15044       case ThePath.Command.CubicTo: // cubic bezier
15045         if (!inPath) goto default;
15046         static if (!left) { if (pupLeft) goto default; }
15047         static if (!right) { if (!pupLeft) goto default; }
15048         immutable float x1 = getScaledX();
15049         immutable float y1 = getScaledY();
15050         immutable float x2 = getScaledX();
15051         immutable float y2 = getScaledY();
15052         immutable float ex = getScaledX();
15053         immutable float ey = getScaledY();
15054         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15055         break;
15056       case ThePath.Command.EndPath:
15057         if (inPath) {
15058           if (pupLeft) pupLeft = false; else return;
15059         }
15060         break;
15061       default:
15062         path.skipArgs(path.argCount(cmd));
15063         break;
15064     }
15065   }
15066 }
15067 
15068 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15069 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15070   template hasChar(char ch, string s) {
15071          static if (s.length == 0) enum hasChar = false;
15072     else static if (s[0] == ch) enum hasChar = true;
15073     else enum hasChar = hasChar!(ch, s[1..$]);
15074   }
15075   enum AllowStroke = hasChar!('s', mode);
15076   enum AllowFill = hasChar!('f', mode);
15077   enum AllowWidth = hasChar!('w', mode);
15078   enum Contour = hasChar!('c', mode);
15079   //static assert(AllowWidth || AllowFill);
15080 
15081   if (nvg is null) return;
15082 
15083   auto path = ThePath(baphometPath);
15084 
15085   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15086   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15087 
15088   int mode = 0;
15089   int sw = ThePath.Command.NormalStroke;
15090   nvg.beginPath();
15091   while (!path.empty) {
15092     auto cmd = path.getCommand();
15093     switch (cmd) {
15094       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15095       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15096       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15097       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15098       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15099       case ThePath.Command.MoveTo:
15100         immutable float ex = getScaledX();
15101         immutable float ey = getScaledY();
15102         nvg.moveTo(ex, ey);
15103         break;
15104       case ThePath.Command.LineTo:
15105         immutable float ex = getScaledX();
15106         immutable float ey = getScaledY();
15107         nvg.lineTo(ex, ey);
15108         break;
15109       case ThePath.Command.CubicTo: // cubic bezier
15110         immutable float x1 = getScaledX();
15111         immutable float y1 = getScaledY();
15112         immutable float x2 = getScaledX();
15113         immutable float y2 = getScaledY();
15114         immutable float ex = getScaledX();
15115         immutable float ey = getScaledY();
15116         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15117         break;
15118       case ThePath.Command.EndPath:
15119         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15120           static if (AllowFill || Contour) {
15121             static if (Contour) {
15122               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15123             } else {
15124               nvg.fill();
15125             }
15126           }
15127         }
15128         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15129           static if (AllowStroke || Contour) {
15130             static if (AllowWidth) {
15131                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15132               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15133               else assert(0, "wtf?!");
15134             }
15135             nvg.stroke();
15136           }
15137         }
15138         nvg.newPath();
15139         break;
15140       default:
15141         path.skipArgs(path.argCount(cmd));
15142         break;
15143     }
15144   }
15145   nvg.newPath();
15146 }
Suggestion Box / Bug Report