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 		D code with nanovega:
32 
33 		---
34 		import arsd.nanovega;
35 		import arsd.simpledisplay;
36 
37 		void main () {
38 			// The NVGWindow class creates a window and sets up the nvg context for you
39 			// you can also do these steps yourself, see the other examples in these docs
40 			auto window = new NVGWindow(800, 600, "NanoVega Simple Sample");
41 
42 			window.redrawNVGScene = delegate (nvg) {
43 				nvg.beginPath(); // start new path
44 				nvg.roundedRect(20.5, 30.5, window.width-40, window.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
45 				// now set filling mode for our rectangle
46 				// you can create colors using HTML syntax, or with convenient constants
47 				nvg.fillPaint = nvg.linearGradient(20.5, 30.5, window.width-40, window.height-60,
48 				NVGColor("#f70"), NVGColor.green);
49 				// now fill our rect
50 				nvg.fill();
51 				// and draw a nice outline
52 				nvg.strokeColor = NVGColor.white;
53 				nvg.strokeWidth = 2;
54 				nvg.stroke();
55 				// that's all, folks!
56 			};
57 
58 			window.eventLoop(0,
59 				delegate (KeyEvent event) {
60 					if (event == "*-Q" || event == "Escape") { window.close(); return; } // quit on Q, Ctrl+Q, and so on
61 				},
62 			);
63 		}
64 		---
65 	)
66 
67 	$(COLUMN
68 		Javascript code with HTML5 Canvas
69 
70 		```html
71 		<!DOCTYPE html>
72 		<html>
73 		<head>
74 			<title>NanoVega Simple Sample (HTML5 Translation)</title>
75 			<style>
76 				body { background-color: black; }
77 			</style>
78 		</head>
79 		<body>
80 			<canvas id="my-canvas" width="800" height="600"></canvas>
81 		<script>
82 			var canvas = document.getElementById("my-canvas");
83 			var context = canvas.getContext("2d");
84 
85 			context.beginPath();
86 
87 			context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60);
88 
89 			var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60);
90 			gradient.addColorStop(0, "#f70");
91 			gradient.addColorStop(1, "green");
92 
93 			context.fillStyle = gradient;
94 			context.fill();
95 			context.closePath();
96 			context.strokeStyle = "white";
97 			context.lineWidth = 2;
98 			context.stroke();
99 		</script>
100 		</body>
101 		</html>
102 		```
103 	)
104 )
105 
106 $(TIP
107     This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType.
108     Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler.
109 )
110 
111 
112 Creating drawing context
113 ========================
114 
115 The drawing context is created using platform specific constructor function.
116 
117   ---
118   NVGContext vg = nvgCreateContext();
119   ---
120 
121 $(WARNING You must use created context ONLY in that thread where you created it.
122           There is no way to "transfer" context between threads. Trying to do so
123           will lead to UB.)
124 
125 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
126           do so will lead to UB.)
127 
128 
129 Drawing shapes with NanoVega
130 ============================
131 
132 Drawing a simple shape using NanoVega consists of four steps:
133 $(LIST
134   * begin a new shape,
135   * define the path to draw,
136   * set fill or stroke,
137   * and finally fill or stroke the path.
138 )
139 
140   ---
141   vg.beginPath();
142   vg.rect(100, 100, 120, 30);
143   vg.fillColor(nvgRGBA(255, 192, 0, 255));
144   vg.fill();
145   ---
146 
147 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
148 There are number of number of functions to define the path to draw, such as rectangle,
149 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
150 arcTo API to compose the paths step by step.
151 
152 
153 Understanding Composite Paths
154 =============================
155 
156 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
157 that is path consisting from multiple paths defining holes and fills, is a bit more
158 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
159 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
160 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
161 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
162 
163   ---
164   vg.beginPath();
165   vg.rect(100, 100, 120, 30);
166   vg.circle(120, 120, 5);
167   vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
168   vg.fillColor(nvgRGBA(255, 192, 0, 255));
169   vg.fill();
170   ---
171 
172 
173 Rendering is wrong, what to do?
174 ===============================
175 
176 $(LIST
177   * make sure you have created NanoVega context using [nvgCreateContext] call
178   * make sure you have initialised OpenGL with $(B stencil buffer)
179   * make sure you have cleared stencil buffer
180   * make sure all rendering calls happen between [beginFrame] and [endFrame]
181   * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
182 )
183 
184 
185 OpenGL state touched by the backend
186 ===================================
187 
188 The OpenGL back-end touches following states:
189 
190 When textures are uploaded or updated, the following pixel store is set to defaults:
191 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
192 Texture binding is also affected. Texture updates can happen when the user loads images,
193 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
194 and [endFrame].
195 
196 The data for the whole frame is buffered and flushed in [endFrame].
197 The following code illustrates the OpenGL state touched by the rendering code:
198 
199   ---
200   glUseProgram(prog);
201   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
202   glEnable(GL_CULL_FACE);
203   glCullFace(GL_BACK);
204   glFrontFace(GL_CCW);
205   glEnable(GL_BLEND);
206   glDisable(GL_DEPTH_TEST);
207   glDisable(GL_SCISSOR_TEST);
208   glDisable(GL_COLOR_LOGIC_OP);
209   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
210   glStencilMask(0xffffffff);
211   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
212   glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
213   glActiveTexture(GL_TEXTURE1);
214   glActiveTexture(GL_TEXTURE0);
215   glBindBuffer(GL_UNIFORM_BUFFER, buf);
216   glBindVertexArray(arr);
217   glBindBuffer(GL_ARRAY_BUFFER, buf);
218   glBindTexture(GL_TEXTURE_2D, tex);
219   glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
220   ---
221 
222   Symbol_groups:
223 
224   context_management =
225     ## Context Management
226 
227     Functions to create and destory NanoVega context.
228 
229   frame_management =
230     ## Frame Management
231 
232     To start drawing with NanoVega context, you have to "begin frame", and then
233     "end frame" to flush your rendering commands to GPU.
234 
235   composite_operation =
236     ## Composite Operation
237 
238     The composite operations in NanoVega are modeled after HTML Canvas API, and
239     the blend func is based on OpenGL (see corresponding manuals for more info).
240     The colors in the blending state have premultiplied alpha.
241 
242   color_utils =
243     ## Color Utils
244 
245     Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
246 
247   matrices =
248     ## Matrices and Transformations
249 
250     The paths, gradients, patterns and scissor region are transformed by an transformation
251     matrix at the time when they are passed to the API.
252     The current transformation matrix is an affine matrix:
253 
254     ----------------------
255       [sx kx tx]
256       [ky sy ty]
257       [ 0  0  1]
258     ----------------------
259 
260     Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
261     The last row is assumed to be (0, 0, 1) and is not stored.
262 
263     Apart from [resetTransform], each transformation function first creates
264     specific transformation matrix and pre-multiplies the current transformation by it.
265 
266     Current coordinate system (transformation) can be saved and restored using [save] and [restore].
267 
268     The following functions can be used to make calculations on 2x3 transformation matrices.
269     A 2x3 matrix is represented as float[6].
270 
271   state_handling =
272     ## State Handling
273 
274     NanoVega contains state which represents how paths will be rendered.
275     The state contains transform, fill and stroke styles, text and font styles,
276     and scissor clipping.
277 
278   render_styles =
279     ## Render Styles
280 
281     Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
282     Solid color is simply defined as a color value, different kinds of paints can be created
283     using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
284 
285     Current render style can be saved and restored using [save] and [restore].
286 
287     Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
288     and use `integerCoord+0.5f` as pixel coordinates.
289 
290   render_transformations =
291     ## Render Transformations
292 
293     Transformation matrix management for the current rendering style. Transformations are applied in
294     backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
295     it's origin, and then translated to the destination point.
296 
297   scissoring =
298     ## Scissoring
299 
300     Scissoring allows you to clip the rendering into a rectangle. This is useful for various
301     user interface cases like rendering a text edit or a timeline.
302 
303   images =
304     ## Images
305 
306     NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
307     In addition you can upload your own image.
308     The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
309 
310     If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
311     [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
312 
313   paints =
314     ## Paints
315 
316     NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
317     These can be used as paints for strokes and fills.
318 
319   gpu_affine =
320     ## Render-Time Affine Transformations
321 
322     It is possible to set affine transformation matrix for GPU. That matrix will
323     be applied by the shader code. This can be used to quickly translate and rotate
324     saved paths. Call this $(B only) between [beginFrame] and [endFrame].
325 
326     Note that [beginFrame] resets this matrix to identity one.
327 
328     $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
329 
330   paths =
331     ## Paths
332 
333     Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
334     Then you define one or more paths and sub-paths which describe the shape. The are functions
335     to draw common shapes like rectangles and circles, and lower level step-by-step functions,
336     which allow to define a path curve by curve.
337 
338     NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
339     winding and holes should have counter clockwise order. To specify winding of a path you can
340     call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
341 
342     Finally you can fill the path using current fill style by calling [fill], and stroke it
343     with current stroke style by calling [stroke].
344 
345     The curve segments and sub-paths are transformed by the current transform.
346 
347   picking_api =
348     ## Picking API
349 
350     This is picking API that works directly on paths, without rasterizing them first.
351 
352     [beginFrame] resets picking state. Then you can create paths as usual, but
353     there is a possibility to perform hit checks $(B before) rasterizing a path.
354     Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
355     immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
356     before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
357     effects, for example.
358 
359     Also note that picking API is ignoring GPU affine transformation matrix.
360     You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
361 
362     $(WARNING Picking API completely ignores clipping. If you want to check for
363               clip regions, you have to manually register them as fill/stroke paths,
364               and perform the necessary logic. See [hitTestForId] function.)
365 
366   clipping =
367     ## Clipping with paths
368 
369     If scissoring is not enough for you, you can clip rendering with arbitrary path,
370     or with combination of paths. Clip region is saved by [save] and restored by
371     [restore] NanoVega functions. You can combine clip paths with various logic
372     operations, see [NVGClipMode].
373 
374     Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
375     is created as if there was no scissor set). Actual rendering is affected by
376     scissors, though.
377 
378   text_api =
379     ## Text
380 
381     NanoVega allows you to load .ttf files and use the font to render text.
382     You have to load some font, and set proper font size before doing anything
383     with text, as there is no "default" font provided by NanoVega. Also, don't
384     forget to check return value of `createFont()`, 'cause NanoVega won't fail
385     if it cannot load font, it will silently try to render nothing.
386 
387     The appearance of the text can be defined by setting the current text style
388     and by specifying the fill color. Common text and font settings such as
389     font size, letter spacing and text align are supported. Font blur allows you
390     to create simple text effects such as drop shadows.
391 
392     At render time the font face can be set based on the font handles or name.
393 
394     Font measure functions return values in local space, the calculations are
395     carried in the same resolution as the final rendering. This is done because
396     the text glyph positions are snapped to the nearest pixels sharp rendering.
397 
398     The local space means that values are not rotated or scale as per the current
399     transformation. For example if you set font size to 12, which would mean that
400     line height is 16, then regardless of the current scaling and rotation, the
401     returned line height is always 16. Some measures may vary because of the scaling
402     since aforementioned pixel snapping.
403 
404     While this may sound a little odd, the setup allows you to always render the
405     same way regardless of scaling. I.e. following works regardless of scaling:
406 
407     ----------------------
408        string txt = "Text me up.";
409        vg.textBounds(x, y, txt, bounds);
410        vg.beginPath();
411        vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
412        vg.fill();
413     ----------------------
414 
415     Note: currently only solid color fill is supported for text.
416 
417   font_stash =
418     ## Low-Level Font Engine (FontStash)
419 
420     FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
421     You don't need any graphics context to use FontStash, so you can do things like text
422     layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
423     to create new FontStash, copy fonts from NanoVega context into it, and use that new
424     FontStash to do some UI layouting, for example. Also note that you can get text metrics
425     without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
426     you don't need to waste CPU and memory resources to render unneeded images into font atlas,
427     and you can layout alot of text very fast.
428 
429     Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
430     word "fons" in it, this is not a typo, and it should not read "font" intead.
431 
432     TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
433  */
434 module arsd.nanovega;
435 
436 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class.
437 unittest {
438 	import arsd.simpledisplay;
439 
440 	import arsd.nanovega;
441 
442 	void main () {
443 	  NVGContext nvg; // our NanoVega context
444 
445 	  // we need at least OpenGL3 with GLSL to use NanoVega,
446 	  // so let's tell simpledisplay about that
447 	  setOpenGLContextVersion(3, 0);
448 
449 	  // now create OpenGL window
450 	  auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing);
451 
452 	  // we need to destroy NanoVega context on window close
453 	  // stricly speaking, it is not necessary, as nothing fatal
454 	  // will happen if you'll forget it, but let's be polite.
455 	  // note that we cannot do that *after* our window was closed,
456 	  // as we need alive OpenGL context to do proper cleanup.
457 	  sdmain.onClosing = delegate () {
458 	    nvg.kill();
459 	  };
460 
461 	  // this is called just before our window will be shown for the first time.
462 	  // we must create NanoVega context here, as it needs to initialize
463 	  // internal OpenGL subsystem with valid OpenGL context.
464 	  sdmain.visibleForTheFirstTime = delegate () {
465 	    // yes, that's all
466 	    nvg = nvgCreateContext();
467 	    if (nvg is null) assert(0, "cannot initialize NanoVega");
468 	  };
469 
470 	  // this callback will be called when we will need to repaint our window
471 	  sdmain.redrawOpenGlScene = delegate () {
472 	    // fix viewport (we can do this in resize event, or here, it doesn't matter)
473 	    glViewport(0, 0, sdmain.width, sdmain.height);
474 
475 	    // clear window
476 	    glClearColor(0, 0, 0, 0);
477 	    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
478 
479 	    {
480 	      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
481 	      scope(exit) nvg.endFrame(); // and flush render queue on exit
482 
483 	      nvg.beginPath(); // start new path
484 	      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
485 	      // now set filling mode for our rectangle
486 	      // you can create colors using HTML syntax, or with convenient constants
487 	      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
488 	      // now fill our rect
489 	      nvg.fill();
490 	      // and draw a nice outline
491 	      nvg.strokeColor = NVGColor.white;
492 	      nvg.strokeWidth = 2;
493 	      nvg.stroke();
494 	      // that's all, folks!
495 	    }
496 	  };
497 
498 	  sdmain.eventLoop(0, // no pulse timer required
499 	    delegate (KeyEvent event) {
500 	      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
501 	    },
502 	  );
503 
504 	  flushGui(); // let OS do it's cleanup
505 	}
506 }
507 
508 private:
509 
510 version(aliced) {
511   import iv.meta;
512   import iv.vfs;
513 } else {
514   private alias usize = size_t;
515   // i fear phobos!
516   private template Unqual(T) {
517          static if (is(T U ==          immutable U)) alias Unqual = U;
518     else static if (is(T U == shared inout const U)) alias Unqual = U;
519     else static if (is(T U == shared inout       U)) alias Unqual = U;
520     else static if (is(T U == shared       const U)) alias Unqual = U;
521     else static if (is(T U == shared             U)) alias Unqual = U;
522     else static if (is(T U ==        inout const U)) alias Unqual = U;
523     else static if (is(T U ==        inout       U)) alias Unqual = U;
524     else static if (is(T U ==              const U)) alias Unqual = U;
525     else alias Unqual = T;
526   }
527   private template isAnyCharType(T, bool unqual=false) {
528     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
529     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
530   }
531   private template isWideCharType(T, bool unqual=false) {
532     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
533     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
534   }
535 }
536 version(nanovg_disable_vfs) {
537   enum NanoVegaHasIVVFS = false;
538 } else {
539   static if (is(typeof((){import iv.vfs;}))) {
540     enum NanoVegaHasIVVFS = true;
541     import iv.vfs;
542   } else {
543     enum NanoVegaHasIVVFS = false;
544   }
545 }
546 
547 // ////////////////////////////////////////////////////////////////////////// //
548 // engine
549 // ////////////////////////////////////////////////////////////////////////// //
550 import core.stdc.stdlib : malloc, realloc, free;
551 import core.stdc.string : memset, memcpy, strlen;
552 import std.math : PI;
553 
554 //version = nanovg_force_stb_ttf;
555 
556 version(Posix) {
557   version = nanovg_use_freetype;
558 } else {
559   version = nanovg_disable_fontconfig;
560 }
561 
562 version (bindbc) {
563   version = nanovg_builtin_fontconfig_bindings;
564   version = nanovg_bindbc_opengl_bindings;
565   version = nanovg_bindbc_freetype_bindings;
566   version(BindFT_Dynamic)
567     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
568   else {
569     version(BindFT_Static) {}
570     else
571       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
572   }
573 } else version(aliced) {
574   version = nanovg_default_no_font_aa;
575   version = nanovg_builtin_fontconfig_bindings;
576   version = nanovg_builtin_freetype_bindings;
577   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
578 } else {
579   version (Have_bindbc_opengl)
580     version = nanovg_bindbc_opengl_bindings;
581    else
582     version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
583   version (Have_bindbc_freetype)
584     version = nanovg_bindbc_freetype_bindings;
585    else
586     version = nanovg_builtin_freetype_bindings;
587   version = nanovg_builtin_fontconfig_bindings;
588 }
589 
590 version(nanovg_disable_fontconfig) {
591   public enum NanoVegaHasFontConfig = false;
592 } else {
593   public enum NanoVegaHasFontConfig = true;
594   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
595 }
596 
597 //version = nanovg_bench_flatten;
598 
599 /++
600 	Annotation to indicate the marked function is compatible with [arsd.script].
601 
602 
603 	Any function that takes a [Color] argument will be passed a string instead.
604 
605 	Scriptable Functions
606 	====================
607 
608 	$(UDA_USES)
609 
610 	$(ALWAYS_DOCUMENT)
611 +/
612 private enum scriptable = "arsd_jsvar_compatible";
613 
614 public:
615 alias NVG_PI = PI;
616 
617 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
618 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
619 
620 static if (NanoVegaHasArsdColor) private import arsd.color;
621 static if (NanoVegaHasArsdImage) {
622   private import arsd.image;
623 } else {
624   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
625   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
626   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
627   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
628   void stbi_image_free (void* retval_from_stbi_load) {}
629 }
630 
631 version(nanovg_default_no_font_aa) {
632   __gshared bool NVG_INVERT_FONT_AA = false;
633 } else {
634   __gshared bool NVG_INVERT_FONT_AA = true;
635 }
636 
637 
638 /// this is branchless for ints on x86, and even for longs on x86_64
639 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
640   static if (__VERSION__ > 2067) pragma(inline, true);
641   static if (T.sizeof == 2 || T.sizeof == 4) {
642     static if (__traits(isUnsigned, T)) {
643       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
644     } else {
645       n &= -cast(int)(n >= 0);
646       return cast(ubyte)(n|((255-cast(int)n)>>31));
647     }
648   } else static if (T.sizeof == 1) {
649     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
650     return cast(ubyte)n;
651   } else static if (T.sizeof == 8) {
652     static if (__traits(isUnsigned, T)) {
653       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
654     } else {
655       n &= -cast(long)(n >= 0);
656       return cast(ubyte)(n|((255-cast(long)n)>>63));
657     }
658   } else {
659     static assert(false, "clampToByte: integer too big");
660   }
661 }
662 
663 
664 /// NanoVega RGBA color
665 /// Group: color_utils
666 public align(1) struct NVGColor {
667 align(1):
668 public:
669   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
670 
671 public:
672   @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
673 
674 public:
675   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
676   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
677 
678   enum aliceblue = NVGColor(240, 248, 255);
679   enum antiquewhite = NVGColor(250, 235, 215);
680   enum aqua = NVGColor(0, 255, 255);
681   enum aquamarine = NVGColor(127, 255, 212);
682   enum azure = NVGColor(240, 255, 255);
683   enum beige = NVGColor(245, 245, 220);
684   enum bisque = NVGColor(255, 228, 196);
685   enum black = NVGColor(0, 0, 0); // basic color
686   enum blanchedalmond = NVGColor(255, 235, 205);
687   enum blue = NVGColor(0, 0, 255); // basic color
688   enum blueviolet = NVGColor(138, 43, 226);
689   enum brown = NVGColor(165, 42, 42);
690   enum burlywood = NVGColor(222, 184, 135);
691   enum cadetblue = NVGColor(95, 158, 160);
692   enum chartreuse = NVGColor(127, 255, 0);
693   enum chocolate = NVGColor(210, 105, 30);
694   enum coral = NVGColor(255, 127, 80);
695   enum cornflowerblue = NVGColor(100, 149, 237);
696   enum cornsilk = NVGColor(255, 248, 220);
697   enum crimson = NVGColor(220, 20, 60);
698   enum cyan = NVGColor(0, 255, 255); // basic color
699   enum darkblue = NVGColor(0, 0, 139);
700   enum darkcyan = NVGColor(0, 139, 139);
701   enum darkgoldenrod = NVGColor(184, 134, 11);
702   enum darkgray = NVGColor(169, 169, 169);
703   enum darkgreen = NVGColor(0, 100, 0);
704   enum darkgrey = NVGColor(169, 169, 169);
705   enum darkkhaki = NVGColor(189, 183, 107);
706   enum darkmagenta = NVGColor(139, 0, 139);
707   enum darkolivegreen = NVGColor(85, 107, 47);
708   enum darkorange = NVGColor(255, 140, 0);
709   enum darkorchid = NVGColor(153, 50, 204);
710   enum darkred = NVGColor(139, 0, 0);
711   enum darksalmon = NVGColor(233, 150, 122);
712   enum darkseagreen = NVGColor(143, 188, 143);
713   enum darkslateblue = NVGColor(72, 61, 139);
714   enum darkslategray = NVGColor(47, 79, 79);
715   enum darkslategrey = NVGColor(47, 79, 79);
716   enum darkturquoise = NVGColor(0, 206, 209);
717   enum darkviolet = NVGColor(148, 0, 211);
718   enum deeppink = NVGColor(255, 20, 147);
719   enum deepskyblue = NVGColor(0, 191, 255);
720   enum dimgray = NVGColor(105, 105, 105);
721   enum dimgrey = NVGColor(105, 105, 105);
722   enum dodgerblue = NVGColor(30, 144, 255);
723   enum firebrick = NVGColor(178, 34, 34);
724   enum floralwhite = NVGColor(255, 250, 240);
725   enum forestgreen = NVGColor(34, 139, 34);
726   enum fuchsia = NVGColor(255, 0, 255);
727   enum gainsboro = NVGColor(220, 220, 220);
728   enum ghostwhite = NVGColor(248, 248, 255);
729   enum gold = NVGColor(255, 215, 0);
730   enum goldenrod = NVGColor(218, 165, 32);
731   enum gray = NVGColor(128, 128, 128); // basic color
732   enum green = NVGColor(0, 128, 0); // basic color
733   enum greenyellow = NVGColor(173, 255, 47);
734   enum grey = NVGColor(128, 128, 128); // basic color
735   enum honeydew = NVGColor(240, 255, 240);
736   enum hotpink = NVGColor(255, 105, 180);
737   enum indianred = NVGColor(205, 92, 92);
738   enum indigo = NVGColor(75, 0, 130);
739   enum ivory = NVGColor(255, 255, 240);
740   enum khaki = NVGColor(240, 230, 140);
741   enum lavender = NVGColor(230, 230, 250);
742   enum lavenderblush = NVGColor(255, 240, 245);
743   enum lawngreen = NVGColor(124, 252, 0);
744   enum lemonchiffon = NVGColor(255, 250, 205);
745   enum lightblue = NVGColor(173, 216, 230);
746   enum lightcoral = NVGColor(240, 128, 128);
747   enum lightcyan = NVGColor(224, 255, 255);
748   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
749   enum lightgray = NVGColor(211, 211, 211);
750   enum lightgreen = NVGColor(144, 238, 144);
751   enum lightgrey = NVGColor(211, 211, 211);
752   enum lightpink = NVGColor(255, 182, 193);
753   enum lightsalmon = NVGColor(255, 160, 122);
754   enum lightseagreen = NVGColor(32, 178, 170);
755   enum lightskyblue = NVGColor(135, 206, 250);
756   enum lightslategray = NVGColor(119, 136, 153);
757   enum lightslategrey = NVGColor(119, 136, 153);
758   enum lightsteelblue = NVGColor(176, 196, 222);
759   enum lightyellow = NVGColor(255, 255, 224);
760   enum lime = NVGColor(0, 255, 0);
761   enum limegreen = NVGColor(50, 205, 50);
762   enum linen = NVGColor(250, 240, 230);
763   enum magenta = NVGColor(255, 0, 255); // basic color
764   enum maroon = NVGColor(128, 0, 0);
765   enum mediumaquamarine = NVGColor(102, 205, 170);
766   enum mediumblue = NVGColor(0, 0, 205);
767   enum mediumorchid = NVGColor(186, 85, 211);
768   enum mediumpurple = NVGColor(147, 112, 219);
769   enum mediumseagreen = NVGColor(60, 179, 113);
770   enum mediumslateblue = NVGColor(123, 104, 238);
771   enum mediumspringgreen = NVGColor(0, 250, 154);
772   enum mediumturquoise = NVGColor(72, 209, 204);
773   enum mediumvioletred = NVGColor(199, 21, 133);
774   enum midnightblue = NVGColor(25, 25, 112);
775   enum mintcream = NVGColor(245, 255, 250);
776   enum mistyrose = NVGColor(255, 228, 225);
777   enum moccasin = NVGColor(255, 228, 181);
778   enum navajowhite = NVGColor(255, 222, 173);
779   enum navy = NVGColor(0, 0, 128);
780   enum oldlace = NVGColor(253, 245, 230);
781   enum olive = NVGColor(128, 128, 0);
782   enum olivedrab = NVGColor(107, 142, 35);
783   enum orange = NVGColor(255, 165, 0);
784   enum orangered = NVGColor(255, 69, 0);
785   enum orchid = NVGColor(218, 112, 214);
786   enum palegoldenrod = NVGColor(238, 232, 170);
787   enum palegreen = NVGColor(152, 251, 152);
788   enum paleturquoise = NVGColor(175, 238, 238);
789   enum palevioletred = NVGColor(219, 112, 147);
790   enum papayawhip = NVGColor(255, 239, 213);
791   enum peachpuff = NVGColor(255, 218, 185);
792   enum peru = NVGColor(205, 133, 63);
793   enum pink = NVGColor(255, 192, 203);
794   enum plum = NVGColor(221, 160, 221);
795   enum powderblue = NVGColor(176, 224, 230);
796   enum purple = NVGColor(128, 0, 128);
797   enum red = NVGColor(255, 0, 0); // basic color
798   enum rosybrown = NVGColor(188, 143, 143);
799   enum royalblue = NVGColor(65, 105, 225);
800   enum saddlebrown = NVGColor(139, 69, 19);
801   enum salmon = NVGColor(250, 128, 114);
802   enum sandybrown = NVGColor(244, 164, 96);
803   enum seagreen = NVGColor(46, 139, 87);
804   enum seashell = NVGColor(255, 245, 238);
805   enum sienna = NVGColor(160, 82, 45);
806   enum silver = NVGColor(192, 192, 192);
807   enum skyblue = NVGColor(135, 206, 235);
808   enum slateblue = NVGColor(106, 90, 205);
809   enum slategray = NVGColor(112, 128, 144);
810   enum slategrey = NVGColor(112, 128, 144);
811   enum snow = NVGColor(255, 250, 250);
812   enum springgreen = NVGColor(0, 255, 127);
813   enum steelblue = NVGColor(70, 130, 180);
814   enum tan = NVGColor(210, 180, 140);
815   enum teal = NVGColor(0, 128, 128);
816   enum thistle = NVGColor(216, 191, 216);
817   enum tomato = NVGColor(255, 99, 71);
818   enum turquoise = NVGColor(64, 224, 208);
819   enum violet = NVGColor(238, 130, 238);
820   enum wheat = NVGColor(245, 222, 179);
821   enum white = NVGColor(255, 255, 255); // basic color
822   enum whitesmoke = NVGColor(245, 245, 245);
823   enum yellow = NVGColor(255, 255, 0); // basic color
824   enum yellowgreen = NVGColor(154, 205, 50);
825 
826 nothrow @safe @nogc:
827 public:
828   ///
829   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
830     pragma(inline, true);
831     r = ar/255.0f;
832     g = ag/255.0f;
833     b = ab/255.0f;
834     a = aa/255.0f;
835   }
836 
837   ///
838   this (float ar, float ag, float ab, float aa=1.0f) pure {
839     pragma(inline, true);
840     r = ar;
841     g = ag;
842     b = ab;
843     a = aa;
844   }
845 
846   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
847   this (uint c) pure {
848     pragma(inline, true);
849     r = (c&0xff)/255.0f;
850     g = ((c>>8)&0xff)/255.0f;
851     b = ((c>>16)&0xff)/255.0f;
852     a = ((c>>24)&0xff)/255.0f;
853   }
854 
855   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
856   this (const(char)[] srgb) {
857     static int c2d (char ch) pure nothrow @safe @nogc {
858       pragma(inline, true);
859       return
860         ch >= '0' && ch <= '9' ? ch-'0' :
861         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
862         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
863         -1;
864     }
865     int[8] digs;
866     int dc = -1;
867     foreach (immutable char ch; srgb) {
868       if (ch <= ' ') continue;
869       if (ch == '#') {
870         if (dc != -1) { dc = -1; break; }
871         dc = 0;
872       } else {
873         if (dc >= digs.length) { dc = -1; break; }
874         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
875       }
876     }
877     switch (dc) {
878       case 3: // rgb
879         a = 1.0f;
880         r = digs[0]/15.0f;
881         g = digs[1]/15.0f;
882         b = digs[2]/15.0f;
883         break;
884       case 4: // argb
885         a = digs[0]/15.0f;
886         r = digs[1]/15.0f;
887         g = digs[2]/15.0f;
888         b = digs[3]/15.0f;
889         break;
890       case 6: // rrggbb
891         a = 1.0f;
892         r = (digs[0]*16+digs[1])/255.0f;
893         g = (digs[2]*16+digs[3])/255.0f;
894         b = (digs[4]*16+digs[5])/255.0f;
895         break;
896       case 8: // aarrggbb
897         a = (digs[0]*16+digs[1])/255.0f;
898         r = (digs[2]*16+digs[3])/255.0f;
899         g = (digs[4]*16+digs[5])/255.0f;
900         b = (digs[6]*16+digs[7])/255.0f;
901         break;
902       default:
903         break;
904     }
905   }
906 
907   /// Is this color completely opaque?
908   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
909   /// Is this color completely transparent?
910   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
911 
912   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
913   @property uint asUint () const pure {
914     pragma(inline, true);
915     return
916       cast(uint)(r*255)|
917       (cast(uint)(g*255)<<8)|
918       (cast(uint)(b*255)<<16)|
919       (cast(uint)(a*255)<<24);
920   }
921 
922   alias asUintABGR = asUint; /// Ditto.
923 
924   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
925   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
926 
927   alias fromUintABGR = fromUint; /// Ditto.
928 
929   /// AARRGGBB
930   @property uint asUintARGB () const pure {
931     pragma(inline, true);
932     return
933       cast(uint)(b*255)|
934       (cast(uint)(g*255)<<8)|
935       (cast(uint)(r*255)<<16)|
936       (cast(uint)(a*255)<<24);
937   }
938 
939   /// AARRGGBB
940   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
941 
942   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
943   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
944   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
945   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
946 
947   ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc {
948     if (tint.a == 0) return this;
949     foreach (immutable idx, ref float v; rgba[0..4]) {
950       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
951     }
952     return this;
953   }
954 
955   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
956   static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
957 
958   static if (NanoVegaHasArsdColor) {
959     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
960     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
961     ///
962     this (in Color c) {
963       version(aliced) pragma(inline, true);
964       r = c.r/255.0f;
965       g = c.g/255.0f;
966       b = c.b/255.0f;
967       a = c.a/255.0f;
968     }
969   }
970 }
971 
972 
973 /// NanoVega A-HSL color
974 /// Group: color_utils
975 public align(1) struct NVGHSL {
976 align(1):
977   float h=0, s=0, l=1, a=1; ///
978 
979   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)); }
980 
981 nothrow @safe @nogc:
982 public:
983   ///
984   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
985 
986   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
987 
988   // taken from Adam's arsd.color
989   /** Converts an RGB color into an HSL triplet.
990    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
991    * which is more sensitive to green than red and more to red than blue.
992    * If it is false, it just does average of the rgb. */
993   static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure {
994     NVGHSL res;
995     res.a = c.a;
996     float r1 = c.r;
997     float g1 = c.g;
998     float b1 = c.b;
999 
1000     float maxColor = r1;
1001     if (g1 > maxColor) maxColor = g1;
1002     if (b1 > maxColor) maxColor = b1;
1003     float minColor = r1;
1004     if (g1 < minColor) minColor = g1;
1005     if (b1 < minColor) minColor = b1;
1006 
1007     res.l = (maxColor+minColor)/2;
1008     if (useWeightedLightness) {
1009       // the colors don't affect the eye equally
1010       // this is a little more accurate than plain HSL numbers
1011       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
1012     }
1013     if (maxColor != minColor) {
1014       if (res.l < 0.5) {
1015         res.s = (maxColor-minColor)/(maxColor+minColor);
1016       } else {
1017         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
1018       }
1019       if (r1 == maxColor) {
1020         res.h = (g1-b1)/(maxColor-minColor);
1021       } else if(g1 == maxColor) {
1022         res.h = 2.0+(b1-r1)/(maxColor-minColor);
1023       } else {
1024         res.h = 4.0+(r1-g1)/(maxColor-minColor);
1025       }
1026     }
1027 
1028     res.h = res.h*60;
1029     if (res.h < 0) res.h += 360;
1030     res.h /= 360;
1031 
1032     return res;
1033   }
1034 }
1035 
1036 
1037 //version = nanovega_debug_image_manager;
1038 //version = nanovega_debug_image_manager_rc;
1039 
1040 /** NanoVega image handle.
1041  *
1042  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1043  *
1044  * Group: images
1045  */
1046 struct NVGImage {
1047 	enum isOpaqueStruct = true;
1048 private:
1049   NVGContext ctx;
1050   int id; // backend image id
1051 
1052 public:
1053   ///
1054   this() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1055     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); }
1056     if (src.id > 0 && src.ctx !is null) {
1057       ctx = cast(NVGContext)src.ctx;
1058       id = src.id;
1059       ctx.nvg__imageIncRef(id);
1060     }
1061   }
1062 
1063   ///
1064   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1065 
1066   ///
1067   this (this) nothrow @trusted @nogc {
1068     version(aliced) pragma(inline, true);
1069     if (id > 0 && ctx !is null) {
1070       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1071       ctx.nvg__imageIncRef(id);
1072     }
1073   }
1074 
1075   ///
1076   void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc {
1077     if (src.id <= 0 || src.ctx is null) {
1078       clear();
1079     } else {
1080       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); }
1081       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1082       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1083       ctx = cast(NVGContext)src.ctx;
1084       id = src.id;
1085     }
1086   }
1087 
1088   /// Free this image.
1089   void clear () nothrow @trusted @nogc {
1090     if (id > 0 && ctx !is null) {
1091       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1092       ctx.nvg__imageDecRef(id);
1093     }
1094     id = 0;
1095     ctx = null;
1096   }
1097 
1098   /// Is this image valid?
1099   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1100 
1101   /// Is the given image valid and comes from the same context?
1102   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1103 
1104   /// Returns image width, or zero for invalid image.
1105   int width () const nothrow @trusted @nogc {
1106     int w = 0;
1107     if (valid) {
1108       int h = void;
1109       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1110     }
1111     return w;
1112   }
1113 
1114   /// Returns image height, or zero for invalid image.
1115   int height () const nothrow @trusted @nogc {
1116     int h = 0;
1117     if (valid) {
1118       int w = void;
1119       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1120     }
1121     return h;
1122   }
1123 }
1124 
1125 
1126 /// Paint parameters for various fills. Don't change anything here!
1127 /// Group: render_styles
1128 public struct NVGPaint {
1129   enum isOpaqueStruct = true;
1130 
1131   NVGMatrix xform;
1132   float[2] extent = 0.0f;
1133   float radius = 0.0f;
1134   float feather = 0.0f;
1135   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1136   NVGColor middleColor;
1137   NVGColor outerColor;
1138   float midp = -1; // middle stop for 3-color gradient
1139   NVGImage image;
1140   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1141 
1142   this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1143     xform = p.xform;
1144     extent[] = p.extent[];
1145     radius = p.radius;
1146     feather = p.feather;
1147     innerColor = p.innerColor;
1148     middleColor = p.middleColor;
1149     midp = p.midp;
1150     outerColor = p.outerColor;
1151     image = p.image;
1152     simpleColor = p.simpleColor;
1153   }
1154 
1155   void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc {
1156     xform = p.xform;
1157     extent[] = p.extent[];
1158     radius = p.radius;
1159     feather = p.feather;
1160     innerColor = p.innerColor;
1161     middleColor = p.middleColor;
1162     midp = p.midp;
1163     outerColor = p.outerColor;
1164     image = p.image;
1165     simpleColor = p.simpleColor;
1166   }
1167 
1168   void clear () nothrow @trusted @nogc {
1169     version(aliced) pragma(inline, true);
1170     import core.stdc.string : memset;
1171     image.clear();
1172     memset(&this, 0, this.sizeof);
1173     simpleColor = true;
1174   }
1175 }
1176 
1177 /// Path winding.
1178 /// Group: paths
1179 public enum NVGWinding {
1180   CCW = 1, /// Winding for solid shapes
1181   CW = 2,  /// Winding for holes
1182 }
1183 
1184 /// Path solidity.
1185 /// Group: paths
1186 public enum NVGSolidity {
1187   Solid = 1, /// Solid shape (CCW winding).
1188   Hole = 2, /// Hole (CW winding).
1189 }
1190 
1191 /// Line cap style.
1192 /// Group: render_styles
1193 public enum NVGLineCap {
1194   Butt, ///
1195   Round, ///
1196   Square, ///
1197   Bevel, ///
1198   Miter, ///
1199 }
1200 
1201 /// Text align.
1202 /// Group: text_api
1203 public align(1) struct NVGTextAlign {
1204 align(1):
1205   /// Horizontal align.
1206   enum H : ubyte {
1207     Left   = 0, /// Default, align text horizontally to left.
1208     Center = 1, /// Align text horizontally to center.
1209     Right  = 2, /// Align text horizontally to right.
1210   }
1211 
1212   /// Vertical align.
1213   enum V : ubyte {
1214     Baseline = 0, /// Default, align text vertically to baseline.
1215     Top      = 1, /// Align text vertically to top.
1216     Middle   = 2, /// Align text vertically to middle.
1217     Bottom   = 3, /// Align text vertically to bottom.
1218   }
1219 
1220 pure nothrow @safe @nogc:
1221 public:
1222   this (H h) { pragma(inline, true); value = h; } ///
1223   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1224   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1225   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1226   void reset () { pragma(inline, true); value = 0; } ///
1227   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1228   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1229 @property:
1230   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1231   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1232   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1233   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1234   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1235   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1236   //
1237   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1238   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1239   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1240   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1241   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1242   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1243   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1244   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1245   //
1246   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1247   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1248   //
1249   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1250   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1251   //
1252 private:
1253   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1254 }
1255 
1256 /// Blending type.
1257 /// Group: composite_operation
1258 public enum NVGBlendFactor {
1259   Zero = 1<<0, ///
1260   One = 1<<1, ///
1261   SrcColor = 1<<2, ///
1262   OneMinusSrcColor = 1<<3, ///
1263   DstColor = 1<<4, ///
1264   OneMinusDstColor = 1<<5, ///
1265   SrcAlpha = 1<<6, ///
1266   OneMinusSrcAlpha = 1<<7, ///
1267   DstAlpha = 1<<8, ///
1268   OneMinusDstAlpha = 1<<9, ///
1269   SrcAlphaSaturate = 1<<10, ///
1270 }
1271 
1272 /// Composite operation (HTML5-alike).
1273 /// Group: composite_operation
1274 public enum NVGCompositeOperation {
1275   SourceOver, ///
1276   SourceIn, ///
1277   SourceOut, ///
1278   SourceAtop, ///
1279   DestinationOver, ///
1280   DestinationIn, ///
1281   DestinationOut, ///
1282   DestinationAtop, ///
1283   Lighter, ///
1284   Copy, ///
1285   Xor, ///
1286 }
1287 
1288 /// Composite operation state.
1289 /// Group: composite_operation
1290 public struct NVGCompositeOperationState {
1291   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1292   NVGBlendFactor srcRGB; ///
1293   NVGBlendFactor dstRGB; ///
1294   NVGBlendFactor srcAlpha; ///
1295   NVGBlendFactor dstAlpha; ///
1296 }
1297 
1298 /// Mask combining more
1299 /// Group: clipping
1300 public enum NVGClipMode {
1301   None, /// normal rendering (i.e. render path instead of modifying clip region)
1302   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1303   Or, /// new mask will be added to the current one (logical `OR` operation);
1304   Xor, /// new mask will be logically `XOR`ed with the current one
1305   Sub, /// "subtract" current path from mask
1306   Replace, /// replace current mask
1307   Add = Or, /// Synonym
1308 }
1309 
1310 /// Glyph position info.
1311 /// Group: text_api
1312 public struct NVGGlyphPosition {
1313   usize strpos;     /// Position of the glyph in the input string.
1314   float x;          /// The x-coordinate of the logical glyph position.
1315   float minx, maxx; /// The bounds of the glyph shape.
1316 }
1317 
1318 /// Text row storage.
1319 /// Group: text_api
1320 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1321   alias CharType = CT;
1322   const(CT)[] s;
1323   int start;        /// Index in the input text where the row starts.
1324   int end;          /// Index in the input text where the row ends (one past the last character).
1325   float width;      /// Logical width of the row.
1326   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1327   /// Get rest of the string.
1328   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1329   /// Get current row.
1330   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1331   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1332   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1333 }
1334 
1335 /// Image creation flags.
1336 /// Group: images
1337 public enum NVGImageFlag : uint {
1338   None            =    0, /// Nothing special.
1339   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1340   RepeatX         = 1<<1, /// Repeat image in X direction.
1341   RepeatY         = 1<<2, /// Repeat image in Y direction.
1342   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1343   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1344   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1345   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1346   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1347   Nearest = NoFiltering,  /// compatibility with original NanoVG
1348   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1349 }
1350 
1351 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1352 
1353 
1354 // ////////////////////////////////////////////////////////////////////////// //
1355 private:
1356 
1357 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1358   import core.stdc.stdlib : malloc;
1359   import core.stdc.string : memcpy;
1360   if (count == 0) return null;
1361   T* res = cast(T*)malloc(T.sizeof*count);
1362   if (res is null) assert(0, "NanoVega: out of memory");
1363   memcpy(res, ptr, T.sizeof*count);
1364   return res;
1365 }
1366 
1367 // Internal Render API
1368 enum NVGtexture {
1369   Alpha = 0x01,
1370   RGBA  = 0x02,
1371 }
1372 
1373 struct NVGscissor {
1374   NVGMatrix xform;
1375   float[2] extent = -1.0f;
1376 }
1377 
1378 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1379 public struct NVGVertex {
1380   float x, y, u, v;
1381 }
1382 
1383 struct NVGpath {
1384   int first;
1385   int count;
1386   bool closed;
1387   int nbevel;
1388   NVGVertex* fill;
1389   int nfill;
1390   NVGVertex* stroke;
1391   int nstroke;
1392   NVGWinding mWinding;
1393   bool convex;
1394   bool cloned;
1395 
1396   @disable this (this); // no copies
1397   void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); }
1398 
1399   void clear () nothrow @trusted @nogc {
1400     import core.stdc.stdlib : free;
1401     import core.stdc.string : memset;
1402     if (cloned) {
1403       if (stroke !is null && stroke !is fill) free(stroke);
1404       if (fill !is null) free(fill);
1405     }
1406     memset(&this, 0, this.sizeof);
1407   }
1408 
1409   // won't clear current path
1410   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1411     import core.stdc.string : memcpy;
1412     assert(src !is null);
1413     memcpy(&this, src, NVGpath.sizeof);
1414     this.fill = xdup(src.fill, src.nfill);
1415     if (src.stroke is src.fill) {
1416       this.stroke = this.fill;
1417     } else {
1418       this.stroke = xdup(src.stroke, src.nstroke);
1419     }
1420     this.cloned = true;
1421   }
1422 
1423   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1424     pragma(inline, true);
1425     return (nfill > 0 ? fill[0..nfill] : null);
1426   }
1427 
1428   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1429     pragma(inline, true);
1430     return (nstroke > 0 ? stroke[0..nstroke] : null);
1431   }
1432 
1433   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1434   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1435 }
1436 
1437 
1438 struct NVGparams {
1439   void* userPtr;
1440   bool edgeAntiAlias;
1441   bool fontAA;
1442   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1443   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1444   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1445   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
1446   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1447   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1448   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1449   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1450   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1451   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1452   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1453   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1454   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;
1455   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;
1456   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1457   void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1458   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1459 }
1460 
1461 // ////////////////////////////////////////////////////////////////////////// //
1462 private:
1463 
1464 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1465 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1466 enum NVG_MAX_FONTIMAGES      = 4;
1467 
1468 enum NVG_INIT_COMMANDS_SIZE = 256;
1469 enum NVG_INIT_POINTS_SIZE   = 128;
1470 enum NVG_INIT_PATHS_SIZE    = 16;
1471 enum NVG_INIT_VERTS_SIZE    = 256;
1472 enum NVG_MAX_STATES         = 32;
1473 
1474 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1475 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1476 
1477 enum Command {
1478   MoveTo = 0,
1479   LineTo = 1,
1480   BezierTo = 2,
1481   Close = 3,
1482   Winding = 4,
1483 }
1484 
1485 enum PointFlag : int {
1486   Corner = 0x01,
1487   Left = 0x02,
1488   Bevel = 0x04,
1489   InnerBevelPR = 0x08,
1490 }
1491 
1492 struct NVGstate {
1493   NVGCompositeOperationState compositeOperation;
1494   bool shapeAntiAlias = true;
1495   NVGPaint fill;
1496   NVGPaint stroke;
1497   float strokeWidth = 1.0f;
1498   float miterLimit = 10.0f;
1499   NVGLineCap lineJoin = NVGLineCap.Miter;
1500   NVGLineCap lineCap = NVGLineCap.Butt;
1501   float alpha = 1.0f;
1502   NVGMatrix xform;
1503   NVGscissor scissor;
1504   float fontSize = 16.0f;
1505   float letterSpacing = 0.0f;
1506   float lineHeight = 1.0f;
1507   float fontBlur = 0.0f;
1508   NVGTextAlign textAlign;
1509   int fontId = 0;
1510   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1511   // dashing
1512   enum MaxDashes = 32; // max 16 dashes
1513   float[MaxDashes] dashes;
1514   uint dashCount = 0;
1515   uint lastFlattenDashCount = 0;
1516   float dashStart = 0;
1517   float totalDashLen;
1518   bool firstDashIsGap = false;
1519   // dasher state for flattener
1520   bool dasherActive = false;
1521 
1522   void clearPaint () nothrow @trusted @nogc {
1523     fill.clear();
1524     stroke.clear();
1525   }
1526 }
1527 
1528 struct NVGpoint {
1529   float x, y;
1530   float dx, dy;
1531   float len;
1532   float dmx, dmy;
1533   ubyte flags;
1534 }
1535 
1536 struct NVGpathCache {
1537   NVGpoint* points;
1538   int npoints;
1539   int cpoints;
1540   NVGpath* paths;
1541   int npaths;
1542   int cpaths;
1543   NVGVertex* verts;
1544   int nverts;
1545   int cverts;
1546   float[4] bounds;
1547   // this is required for saved paths
1548   bool strokeReady;
1549   bool fillReady;
1550   float strokeAlphaMul;
1551   float strokeWidth;
1552   float fringeWidth;
1553   bool evenOddMode;
1554   NVGClipMode clipmode;
1555   // non-saved path will not have this
1556   float* commands;
1557   int ncommands;
1558 
1559   @disable this (this); // no copies
1560   void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1561 
1562   // won't clear current path
1563   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1564     import core.stdc.stdlib : malloc;
1565     import core.stdc.string : memcpy, memset;
1566     assert(src !is null);
1567     memcpy(&this, src, NVGpathCache.sizeof);
1568     this.points = xdup(src.points, src.npoints);
1569     this.cpoints = src.npoints;
1570     this.verts = xdup(src.verts, src.nverts);
1571     this.cverts = src.nverts;
1572     this.commands = xdup(src.commands, src.ncommands);
1573     if (src.npaths > 0) {
1574       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1575       memset(this.paths, 0, npaths*NVGpath.sizeof);
1576       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1577       this.cpaths = src.npaths;
1578     } else {
1579       this.npaths = this.cpaths = 0;
1580     }
1581   }
1582 
1583   void clear () nothrow @trusted @nogc {
1584     import core.stdc.stdlib : free;
1585     import core.stdc.string : memset;
1586     if (paths !is null) {
1587       foreach (ref p; paths[0..npaths]) p.clear();
1588       free(paths);
1589     }
1590     if (points !is null) free(points);
1591     if (verts !is null) free(verts);
1592     if (commands !is null) free(commands);
1593     memset(&this, 0, this.sizeof);
1594   }
1595 }
1596 
1597 /// Pointer to opaque NanoVega context structure.
1598 /// Group: context_management
1599 public alias NVGContext = NVGcontextinternal*;
1600 
1601 /// FontStash context
1602 /// Group: font_stash
1603 public alias FONSContext = FONScontextInternal*;
1604 
1605 /// Returns FontStash context of the given NanoVega context.
1606 /// Group: font_stash
1607 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1608 
1609 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1610 /// Group: font_stash
1611 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1612   pragma(inline, true);
1613   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1614 }
1615 
1616 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1617 /// Returns `false` if FontStash or NanoVega context is not active.
1618 /// Group: font_stash
1619 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1620   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1621   NVGstate* state = nvg__getState(ctx);
1622   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1623   stash.size = state.fontSize*scale;
1624   stash.spacing = state.letterSpacing*scale;
1625   stash.blur = state.fontBlur*scale;
1626   stash.textAlign = state.textAlign;
1627   stash.fontId = state.fontId;
1628   return true;
1629 }
1630 
1631 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1632 /// Returns `false` if FontStash or NanoVega context is not active.
1633 /// Group: font_stash
1634 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1635   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1636   NVGstate* state = nvg__getState(ctx);
1637   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1638   state.fontSize = stash.size;
1639   state.letterSpacing = stash.spacing;
1640   state.fontBlur = stash.blur;
1641   state.textAlign = stash.textAlign;
1642   state.fontId = stash.fontId;
1643   return true;
1644 }
1645 
1646 /** Bezier curve rasterizer.
1647  *
1648  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1649  * It doesn't really matter in practice.
1650  *
1651  * AFD tesselator is somewhat slower, but does cusps better.
1652  *
1653  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1654  * never notice any visial difference (and this code is not really debugged), so you probably should
1655  * not use it. It is there for further experiments.
1656  */
1657 public enum NVGTesselation {
1658   DeCasteljau, /// default: standard well-known tesselation algorithm
1659   AFD, /// adaptive forward differencing
1660   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1661 }
1662 
1663 /// Default tesselator for Bezier curves.
1664 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1665 
1666 
1667 // some public info
1668 
1669 /// valid only inside [beginFrame]/[endFrame]
1670 /// Group: context_management
1671 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1672 
1673 /// valid only inside [beginFrame]/[endFrame]
1674 /// Group: context_management
1675 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1676 
1677 /// valid only inside [beginFrame]/[endFrame]
1678 /// Group: context_management
1679 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1680 
1681 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1682 /// Group: context_management
1683 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1684 
1685 // path autoregistration
1686 
1687 /// [pickid] to stop autoregistration.
1688 /// Group: context_management
1689 public enum NVGNoPick = -1;
1690 
1691 /// >=0: this pickid will be assigned to all filled/stroked paths
1692 /// Group: context_management
1693 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1694 
1695 /// >=0: this pickid will be assigned to all filled/stroked paths
1696 /// Group: context_management
1697 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1698 
1699 /// pick autoregistration mode; see [NVGPickKind]
1700 /// Group: context_management
1701 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1702 
1703 /// pick autoregistration mode; see [NVGPickKind]
1704 /// Group: context_management
1705 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); }
1706 
1707 // tesselator options
1708 
1709 /// Get current Bezier tesselation mode. See [NVGTesselation].
1710 /// Group: context_management
1711 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1712 
1713 /// Set current Bezier tesselation mode. See [NVGTesselation].
1714 /// Group: context_management
1715 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1716 
1717 
1718 private struct NVGcontextinternal {
1719 private:
1720   NVGparams params;
1721   float* commands;
1722   int ccommands;
1723   int ncommands;
1724   float commandx, commandy;
1725   NVGstate[NVG_MAX_STATES] states;
1726   int nstates;
1727   NVGpathCache* cache;
1728   public float tessTol;
1729   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1730   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1731   float distTol;
1732   public float fringeWidth;
1733   float devicePxRatio;
1734   FONSContext fs;
1735   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1736   int fontImageIdx;
1737   int drawCallCount;
1738   int fillTriCount;
1739   int strokeTriCount;
1740   int textTriCount;
1741   NVGTesselation tesselatortype;
1742   // picking API
1743   NVGpickScene* pickScene;
1744   int pathPickId; // >=0: register all paths for picking using this id
1745   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
1746   // path recording
1747   NVGPathSet recset;
1748   int recstart; // used to cancel recording
1749   bool recblockdraw;
1750   // internals
1751   NVGMatrix gpuAffine;
1752   int mWidth, mHeight;
1753   // image manager
1754   shared int imageCount; // number of alive images in this context
1755   bool contextAlive; // context can be dead, but still contain some images
1756 
1757   @disable this (this); // no copies
1758   void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1759 
1760   // debug feature
1761   public @property int getImageCount () nothrow @trusted @nogc {
1762     import core.atomic;
1763     return atomicLoad(imageCount);
1764   }
1765 }
1766 
1767 /** Returns number of tesselated pathes in context.
1768  *
1769  * Tesselated pathes are either triangle strips (for strokes), or
1770  * triangle fans (for fills). Note that NanoVega can generate some
1771  * surprising pathes (like fringe stroke for antialiasing, for example).
1772  *
1773  * One render path can contain vertices both for fill, and for stroke triangles.
1774  */
1775 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1776   pragma(inline, true);
1777   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1778 }
1779 
1780 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1781  *
1782  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1783  *           (except the calls to render path accessors), and using it in such
1784  *           case is UB. So copy vertices to freshly allocated array if you want
1785  *           to keep them for further processing.)
1786  */
1787 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1788   pragma(inline, true);
1789   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1790 }
1791 
1792 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1793  *
1794  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1795  *           (except the calls to render path accessors), and using it in such
1796  *           case is UB. So copy vertices to freshly allocated array if you want
1797  *           to keep them for further processing.)
1798  */
1799 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1800   pragma(inline, true);
1801   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1802 
1803 }
1804 
1805 /// Returns winding for the given render path.
1806 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1807   pragma(inline, true);
1808   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1809 
1810 }
1811 
1812 /// Returns "complex path" flag for the given render path.
1813 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1814   pragma(inline, true);
1815   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1816 
1817 }
1818 
1819 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1820   if (ctx !is null && imgid > 0) {
1821     import core.atomic : atomicOp;
1822     atomicOp!"+="(ctx.imageCount, 1);
1823     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1824     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1825   }
1826 }
1827 
1828 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1829   if (ctx !is null && imgid > 0) {
1830     import core.atomic : atomicOp;
1831     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1832     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1833     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1834     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1835     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); }
1836     if (!ctx.contextAlive && icnt == 0) {
1837       // it is finally safe to free context memory
1838       import core.stdc.stdlib : free;
1839       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1840       free(ctx);
1841     }
1842   }
1843 }
1844 
1845 
1846 public import core.stdc.math :
1847   nvg__sqrtf = sqrtf,
1848   nvg__modf = fmodf,
1849   nvg__sinf = sinf,
1850   nvg__cosf = cosf,
1851   nvg__tanf = tanf,
1852   nvg__atan2f = atan2f,
1853   nvg__acosf = acosf,
1854   nvg__ceilf = ceilf;
1855 
1856 version(Windows) {
1857   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1858 } else {
1859   public import core.stdc.math : nvg__lrintf = lrintf;
1860 }
1861 
1862 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1863 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1864 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1865 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1866 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1867 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1868 
1869 //public import core.stdc.math : nvg__absf = fabsf;
1870 public import core.math : nvg__absf = fabs;
1871 
1872 
1873 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1874   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1875   if (d > 1e-6f) {
1876     immutable float id = 1.0f/d;
1877     *x *= id;
1878     *y *= id;
1879   }
1880   return d;
1881 }
1882 
1883 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1884   if (c !is null) {
1885     c.clear();
1886     free(c);
1887   }
1888 }
1889 
1890 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1891   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1892   if (c is null) goto error;
1893   memset(c, 0, NVGpathCache.sizeof);
1894 
1895   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1896   if (c.points is null) goto error;
1897   assert(c.npoints == 0);
1898   c.cpoints = NVG_INIT_POINTS_SIZE;
1899 
1900   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1901   if (c.paths is null) goto error;
1902   assert(c.npaths == 0);
1903   c.cpaths = NVG_INIT_PATHS_SIZE;
1904 
1905   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1906   if (c.verts is null) goto error;
1907   assert(c.nverts == 0);
1908   c.cverts = NVG_INIT_VERTS_SIZE;
1909 
1910   return c;
1911 
1912 error:
1913   nvg__deletePathCache(c);
1914   return null;
1915 }
1916 
1917 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1918   ctx.tessTol = 0.25f/ratio;
1919   ctx.distTol = 0.01f/ratio;
1920   ctx.fringeWidth = 1.0f/ratio;
1921   ctx.devicePxRatio = ratio;
1922 }
1923 
1924 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1925   NVGCompositeOperationState state;
1926   NVGBlendFactor sfactor, dfactor;
1927 
1928        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1929   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1930   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1931   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1932   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1933   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1934   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1935   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1936   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1937   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1938   else if (op == NVGCompositeOperation.Xor) {
1939     state.simple = false;
1940     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1941     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1942     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1943     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1944     return state;
1945   }
1946   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1947 
1948   state.simple = true;
1949   state.srcAlpha = sfactor;
1950   state.dstAlpha = dfactor;
1951   return state;
1952 }
1953 
1954 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1955   pragma(inline, true);
1956   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1957   return &ctx.states.ptr[ctx.nstates-1];
1958 }
1959 
1960 // Constructor called by the render back-end.
1961 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1962   FONSParams fontParams;
1963   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1964   if (ctx is null) goto error;
1965   memset(ctx, 0, NVGcontextinternal.sizeof);
1966 
1967   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1968   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1969 
1970   ctx.contextAlive = true;
1971 
1972   ctx.params = *params;
1973   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1974 
1975   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1976   if (ctx.commands is null) goto error;
1977   ctx.ncommands = 0;
1978   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1979 
1980   ctx.cache = nvg__allocPathCache();
1981   if (ctx.cache is null) goto error;
1982 
1983   ctx.save();
1984   ctx.reset();
1985 
1986   nvg__setDevicePixelRatio(ctx, 1.0f);
1987   ctx.mWidth = ctx.mHeight = 0;
1988 
1989   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1990 
1991   // init font rendering
1992   memset(&fontParams, 0, fontParams.sizeof);
1993   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1994   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1995   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1996   fontParams.renderCreate = null;
1997   fontParams.renderUpdate = null;
1998   fontParams.renderDelete = null;
1999   fontParams.userPtr = null;
2000   ctx.fs = FONSContext.create(fontParams);
2001   if (ctx.fs is null) goto error;
2002 
2003   // create font texture
2004   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
2005   if (ctx.fontImages[0].id == 0) goto error;
2006   ctx.fontImages[0].ctx = ctx;
2007   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
2008   ctx.fontImageIdx = 0;
2009 
2010   ctx.pathPickId = -1;
2011   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
2012 
2013   return ctx;
2014 
2015 error:
2016   ctx.deleteInternal();
2017   return null;
2018 }
2019 
2020 // Called by render backend.
2021 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
2022   return &ctx.params;
2023 }
2024 
2025 // Destructor called by the render back-end.
2026 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
2027   if (ctx is null) return;
2028   if (ctx.contextAlive) {
2029     if (ctx.commands !is null) free(ctx.commands);
2030     nvg__deletePathCache(ctx.cache);
2031 
2032     if (ctx.fs) ctx.fs.kill();
2033 
2034     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
2035 
2036     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
2037 
2038     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
2039 
2040     ctx.contextAlive = false;
2041 
2042     import core.atomic : atomicLoad;
2043     if (atomicLoad(ctx.imageCount) == 0) {
2044       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2045       free(ctx);
2046     } else {
2047       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2048     }
2049   }
2050 }
2051 
2052 /// Delete NanoVega context.
2053 /// Group: context_management
2054 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2055   if (ctx !is null) {
2056     ctx.deleteInternal();
2057     ctx = null;
2058   }
2059 }
2060 
2061 /// Returns `true` if the given context is not `null` and can be used for painting.
2062 /// Group: context_management
2063 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2064 
2065 
2066 // ////////////////////////////////////////////////////////////////////////// //
2067 // Frame Management
2068 
2069 /** Begin drawing a new frame.
2070  *
2071  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2072  *
2073  * [beginFrame] defines the size of the window to render to in relation currently
2074  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2075  * control the rendering on Hi-DPI devices.
2076  *
2077  * For example, GLFW returns two dimension for an opened window: window size and
2078  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2079  * devicePixelRatio to: `windowWidth/windowHeight`.
2080  *
2081  * Default ratio is `1`.
2082  *
2083  * Note that fractional ratio can (and will) distort your fonts and images.
2084  *
2085  * This call also resets pick marks (see picking API for non-rasterized paths),
2086  * path recording, and GPU affine transformatin matrix.
2087  *
2088  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2089  *
2090  * Group: frame_management
2091  */
2092 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2093   import std.math : isNaN;
2094   /*
2095   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2096          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2097          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2098   */
2099   if (ctx.nstates > 0) ctx.cancelFrame();
2100 
2101   if (windowWidth < 1) windowWidth = 1;
2102   if (windowHeight < 1) windowHeight = 1;
2103 
2104   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2105 
2106   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2107   ctx.nstates = 0;
2108   ctx.save();
2109   ctx.reset();
2110 
2111   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2112 
2113   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2114   ctx.mWidth = windowWidth;
2115   ctx.mHeight = windowHeight;
2116 
2117   ctx.recset = null;
2118   ctx.recstart = -1;
2119 
2120   ctx.pathPickId = NVGNoPick;
2121   ctx.pathPickRegistered = 0;
2122 
2123   ctx.drawCallCount = 0;
2124   ctx.fillTriCount = 0;
2125   ctx.strokeTriCount = 0;
2126   ctx.textTriCount = 0;
2127 
2128   ctx.ncommands = 0;
2129   ctx.pathPickRegistered = 0;
2130   nvg__clearPathCache(ctx);
2131 
2132   ctx.gpuAffine = NVGMatrix.Identity;
2133 
2134   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2135 }
2136 
2137 /// Cancels drawing the current frame. Cancels path recording.
2138 /// Group: frame_management
2139 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2140   ctx.cancelRecording();
2141   //ctx.mWidth = 0;
2142   //ctx.mHeight = 0;
2143   // cancel render queue
2144   ctx.params.renderCancel(ctx.params.userPtr);
2145   // clear saved states (this may free some textures)
2146   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2147   ctx.nstates = 0;
2148 }
2149 
2150 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2151 /// Group: frame_management
2152 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2153   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2154   ctx.stopRecording();
2155   //ctx.mWidth = 0;
2156   //ctx.mHeight = 0;
2157   // flush render queue
2158   NVGstate* state = nvg__getState(ctx);
2159   ctx.params.renderFlush(ctx.params.userPtr);
2160   if (ctx.fontImageIdx != 0) {
2161     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2162     int j = 0, iw, ih;
2163     // delete images that smaller than current one
2164     if (!fontImage.valid) return;
2165     ctx.imageSize(fontImage, iw, ih);
2166     foreach (int i; 0..ctx.fontImageIdx) {
2167       if (ctx.fontImages[i].valid) {
2168         int nw, nh;
2169         ctx.imageSize(ctx.fontImages[i], nw, nh);
2170         if (nw < iw || nh < ih) {
2171           ctx.deleteImage(ctx.fontImages[i]);
2172         } else {
2173           ctx.fontImages[j++] = ctx.fontImages[i];
2174         }
2175       }
2176     }
2177     // make current font image to first
2178     ctx.fontImages[j++] = ctx.fontImages[0];
2179     ctx.fontImages[0] = fontImage;
2180     ctx.fontImageIdx = 0;
2181     // clear all images after j
2182     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2183   }
2184   // clear saved states (this may free some textures)
2185   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2186   ctx.nstates = 0;
2187 }
2188 
2189 
2190 // ////////////////////////////////////////////////////////////////////////// //
2191 // Recording and Replaying Pathes
2192 
2193 // Saved path set.
2194 // Group: path_recording
2195 public alias NVGPathSet = NVGPathSetS*;
2196 
2197 
2198 //TODO: save scissor info?
2199 struct NVGPathSetS {
2200 private:
2201   // either path cache, or text item
2202   static struct Node {
2203     NVGPaint paint;
2204     NVGpathCache* path;
2205   }
2206 
2207 private:
2208   Node* nodes;
2209   int nnodes, cnodes;
2210   NVGpickScene* pickscene;
2211   //int npickscenes, cpickscenes;
2212   NVGContext svctx; // used to do some sanity checks, and to free resources
2213 
2214 private:
2215   Node* allocNode () nothrow @trusted @nogc {
2216     import core.stdc.string : memset;
2217     // grow buffer if necessary
2218     if (nnodes+1 > cnodes) {
2219       import core.stdc.stdlib : realloc;
2220       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2221       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2222       if (nodes is null) assert(0, "NanoVega: out of memory");
2223       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2224       cnodes = newsz;
2225     }
2226     assert(nnodes < cnodes);
2227     memset(nodes+nnodes, 0, Node.sizeof);
2228     return &nodes[nnodes++];
2229   }
2230 
2231   Node* allocPathNode () nothrow @trusted @nogc {
2232     import core.stdc.stdlib : malloc;
2233     import core.stdc.string : memset;
2234     auto node = allocNode();
2235     // allocate path cache
2236     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2237     if (pc is null) assert(0, "NanoVega: out of memory");
2238     node.path = pc;
2239     return node;
2240   }
2241 
2242   void clearNode (int idx) nothrow @trusted @nogc {
2243     if (idx < 0 || idx >= nnodes) return;
2244     Node* node = &nodes[idx];
2245     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2246     if (node.path !is null) node.path.clear();
2247   }
2248 
2249 private:
2250   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2251     NVGpickScene* ps = ctx.pickScene;
2252     if (ps is null) return; // nothing to do
2253     if (ps.npaths == 0) return; // pick scene is empty
2254     ctx.pickScene = null;
2255     pickscene = ps;
2256   }
2257 
2258   void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc {
2259     NVGstate* state = nvg__getState(ctx);
2260     foreach (ref node; nodes[0..nnodes]) {
2261       if (auto cc = node.path) {
2262         if (cc.npaths <= 0) continue;
2263 
2264         if (cc.fillReady) {
2265           NVGPaint fillPaint = node.paint;
2266 
2267           // apply global alpha
2268           fillPaint.innerColor.a *= state.alpha;
2269           fillPaint.middleColor.a *= state.alpha;
2270           fillPaint.outerColor.a *= state.alpha;
2271 
2272           fillPaint.innerColor.applyTint(fillTint);
2273           fillPaint.middleColor.applyTint(fillTint);
2274           fillPaint.outerColor.applyTint(fillTint);
2275 
2276           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2277 
2278           // count triangles
2279           foreach (int i; 0..cc.npaths) {
2280             NVGpath* path = &cc.paths[i];
2281             ctx.fillTriCount += path.nfill-2;
2282             ctx.fillTriCount += path.nstroke-2;
2283             ctx.drawCallCount += 2;
2284           }
2285         }
2286 
2287         if (cc.strokeReady) {
2288           NVGPaint strokePaint = node.paint;
2289 
2290           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2291           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2292           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2293 
2294           // apply global alpha
2295           strokePaint.innerColor.a *= state.alpha;
2296           strokePaint.middleColor.a *= state.alpha;
2297           strokePaint.outerColor.a *= state.alpha;
2298 
2299           strokePaint.innerColor.applyTint(strokeTint);
2300           strokePaint.middleColor.applyTint(strokeTint);
2301           strokePaint.outerColor.applyTint(strokeTint);
2302 
2303           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2304 
2305           // count triangles
2306           foreach (int i; 0..cc.npaths) {
2307             NVGpath* path = &cc.paths[i];
2308             ctx.strokeTriCount += path.nstroke-2;
2309             ++ctx.drawCallCount;
2310           }
2311         }
2312       }
2313     }
2314   }
2315 
2316 public:
2317   @disable this (this); // no copies
2318   void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2319 
2320   // pick test
2321   // Call delegate [dg] for each path under the specified position (in no particular order).
2322   // Returns the id of the path for which delegate [dg] returned true or -1.
2323   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2324   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2325     if (pickscene is null) return -1;
2326 
2327     NVGpickScene* ps = pickscene;
2328     int levelwidth = 1<<(ps.nlevels-1);
2329     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2330     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2331     int npicked = 0;
2332 
2333     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2334       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2335       while (pp !is null) {
2336         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2337           int hit = 0;
2338           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2339           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2340           if (hit) {
2341             static if (IsGoodHitTestDG!DG) {
2342               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2343                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2344               } else {
2345                 dg(pp.id, cast(int)pp.order);
2346               }
2347             } else {
2348               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2349                 if (dg(pp)) return pp.id;
2350               } else {
2351                 dg(pp);
2352               }
2353             }
2354           }
2355         }
2356         pp = pp.next;
2357       }
2358       cellx >>= 1;
2359       celly >>= 1;
2360       levelwidth >>= 1;
2361     }
2362 
2363     return -1;
2364   }
2365 
2366   // Fills ids with a list of the top most hit ids under the specified position.
2367   // Returns the slice of [ids].
2368   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2369     if (pickscene is null || ids.length == 0) return ids[0..0];
2370 
2371     int npicked = 0;
2372     NVGpickScene* ps = pickscene;
2373 
2374     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2375       if (npicked == ps.cpicked) {
2376         int cpicked = ps.cpicked+ps.cpicked;
2377         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2378         if (picked is null) return true; // abort
2379         ps.cpicked = cpicked;
2380         ps.picked = picked;
2381       }
2382       ps.picked[npicked] = pp;
2383       ++npicked;
2384       return false; // go on
2385     });
2386 
2387     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2388 
2389     assert(npicked >= 0);
2390     if (npicked > ids.length) npicked = cast(int)ids.length;
2391     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2392 
2393     return ids[0..npicked];
2394   }
2395 
2396   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2397   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2398     if (pickscene is null) return -1;
2399 
2400     int bestOrder = -1;
2401     int bestID = -1;
2402 
2403     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2404       if (pp.order > bestOrder) {
2405         bestOrder = pp.order;
2406         bestID = pp.id;
2407       }
2408     });
2409 
2410     return bestID;
2411   }
2412 }
2413 
2414 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2415 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc {
2416   if (ctx is null || svp is null) return;
2417   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2418   if (ctx.ncommands == 0) {
2419     assert(ctx.cache.npaths == 0);
2420     return;
2421   }
2422   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2423 
2424   // tesselate current path
2425   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2426   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2427 
2428   auto node = svp.allocPathNode();
2429   NVGpathCache* cc = node.path;
2430   cc.copyFrom(ctx.cache);
2431   node.paint = paint;
2432   // copy path commands (we may need 'em for picking)
2433   version(all) {
2434     cc.ncommands = ctx.ncommands;
2435     if (cc.ncommands) {
2436       import core.stdc.stdlib : malloc;
2437       import core.stdc.string : memcpy;
2438       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2439       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2440       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2441     } else {
2442       cc.commands = null;
2443     }
2444   }
2445 }
2446 
2447 // Create new empty path set.
2448 // Group: path_recording
2449 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2450   import core.stdc.stdlib : malloc;
2451   import core.stdc.string : memset;
2452   if (ctx is null) return null;
2453   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2454   if (res is null) assert(0, "NanoVega: out of memory");
2455   memset(res, 0, NVGPathSetS.sizeof);
2456   res.svctx = ctx;
2457   return res;
2458 }
2459 
2460 // Is the given path set empty? Empty path set can be `null`.
2461 // Group: path_recording
2462 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2463 
2464 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2465 // Group: path_recording
2466 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2467   if (svp !is null) {
2468     import core.stdc.stdlib : free;
2469     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2470     svp.nnodes = 0;
2471   }
2472 }
2473 
2474 // Destroy path set (frees all allocated memory).
2475 // Group: path_recording
2476 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2477   if (svp !is null) {
2478     import core.stdc.stdlib : free;
2479     svp.clear();
2480     if (svp.nodes !is null) free(svp.nodes);
2481     free(svp);
2482     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2483     svp = null;
2484   }
2485 }
2486 
2487 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2488 // Group: path_recording
2489 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2490   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2491   ctx.stopRecording();
2492   ctx.recset = svp;
2493   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2494   ctx.recblockdraw = false;
2495 }
2496 
2497 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2498  *
2499  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2500  * Commiting or cancelling will re-enable rendering.
2501  * You can call this with `null` svp to block rendering without recording any paths.
2502  *
2503  * Group: path_recording
2504  */
2505 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2506   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2507   ctx.stopRecording();
2508   ctx.recset = svp;
2509   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2510   ctx.recblockdraw = true;
2511 }
2512 
2513 // Commit recorded paths. It is safe to call this when recording is not started.
2514 // Group: path_recording
2515 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2516   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2517   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2518   ctx.recset = null;
2519   ctx.recstart = -1;
2520   ctx.recblockdraw = false;
2521 }
2522 
2523 // Cancel path recording.
2524 // Group: path_recording
2525 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2526   if (ctx.recset !is null) {
2527     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2528     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2529     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2530     ctx.recset.nnodes = ctx.recstart;
2531     ctx.recset = null;
2532     ctx.recstart = -1;
2533   }
2534   ctx.recblockdraw = false;
2535 }
2536 
2537 /* Replay saved path set.
2538  *
2539  * Replaying record while you're recording another one is undefined behavior.
2540  *
2541  * Group: path_recording
2542  */
2543 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2544   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2545   svp.replay(ctx, fillTint, strokeTint);
2546 }
2547 
2548 /// Ditto.
2549 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2550 
2551 /// Ditto.
2552 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2553 
2554 
2555 // ////////////////////////////////////////////////////////////////////////// //
2556 // Composite operation
2557 
2558 /// Sets the composite operation.
2559 /// Group: composite_operation
2560 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2561   NVGstate* state = nvg__getState(ctx);
2562   state.compositeOperation = nvg__compositeOperationState(op);
2563 }
2564 
2565 /// Sets the composite operation with custom pixel arithmetic.
2566 /// Group: composite_operation
2567 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2568   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2569 }
2570 
2571 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2572 /// Group: composite_operation
2573 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2574   NVGCompositeOperationState op;
2575   op.simple = false;
2576   op.srcRGB = srcRGB;
2577   op.dstRGB = dstRGB;
2578   op.srcAlpha = srcAlpha;
2579   op.dstAlpha = dstAlpha;
2580   NVGstate* state = nvg__getState(ctx);
2581   state.compositeOperation = op;
2582 }
2583 
2584 
2585 // ////////////////////////////////////////////////////////////////////////// //
2586 // Color utils
2587 
2588 /// Returns a color value from string form.
2589 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2590 /// Group: color_utils
2591 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2592 
2593 /// Ditto.
2594 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2595 
2596 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2597 /// Group: color_utils
2598 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2599 
2600 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2601 /// Group: color_utils
2602 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2603 
2604 /// Returns a color value from red, green, blue and alpha values.
2605 /// Group: color_utils
2606 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)); }
2607 
2608 /// Returns a color value from red, green, blue and alpha values.
2609 /// Group: color_utils
2610 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); }
2611 
2612 /// Returns new color with transparency (alpha) set to [a].
2613 /// Group: color_utils
2614 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2615   pragma(inline, true);
2616   c.a = a/255.0f;
2617   return c;
2618 }
2619 
2620 /// Ditto.
2621 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2622   pragma(inline, true);
2623   c.a = a;
2624   return c;
2625 }
2626 
2627 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2628 /// Group: color_utils
2629 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2630   NVGColor cint = void;
2631   u = nvg__clamp(u, 0.0f, 1.0f);
2632   float oneminu = 1.0f-u;
2633   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2634   return cint;
2635 }
2636 
2637 /* see below
2638 public NVGColor nvgHSL() (float h, float s, float l) {
2639   //pragma(inline, true); // alas
2640   return nvgHSLA(h, s, l, 255);
2641 }
2642 */
2643 
2644 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2645   if (h < 0) h += 1;
2646   if (h > 1) h -= 1;
2647   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2648   if (h < 3.0f/6.0f) return m2;
2649   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2650   return m1;
2651 }
2652 
2653 /// Returns color value specified by hue, saturation and lightness.
2654 /// HSL values are all in range [0..1], alpha will be set to 255.
2655 /// Group: color_utils
2656 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2657 
2658 /// Returns color value specified by hue, saturation and lightness and alpha.
2659 /// HSL values are all in range [0..1], alpha in range [0..255].
2660 /// Group: color_utils
2661 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2662   pragma(inline, true);
2663   NVGColor col = void;
2664   h = nvg__modf(h, 1.0f);
2665   if (h < 0.0f) h += 1.0f;
2666   s = nvg__clamp(s, 0.0f, 1.0f);
2667   l = nvg__clamp(l, 0.0f, 1.0f);
2668   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2669   immutable float m1 = 2*l-m2;
2670   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2671   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2672   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2673   col.a = a/255.0f;
2674   return col;
2675 }
2676 
2677 /// Returns color value specified by hue, saturation and lightness and alpha.
2678 /// HSL values and alpha are all in range [0..1].
2679 /// Group: color_utils
2680 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2681   // sorry for copypasta, it is for inliner
2682   static if (__VERSION__ >= 2072) pragma(inline, true);
2683   NVGColor col = void;
2684   h = nvg__modf(h, 1.0f);
2685   if (h < 0.0f) h += 1.0f;
2686   s = nvg__clamp(s, 0.0f, 1.0f);
2687   l = nvg__clamp(l, 0.0f, 1.0f);
2688   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2689   immutable m1 = 2*l-m2;
2690   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2691   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2692   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2693   col.a = a;
2694   return col;
2695 }
2696 
2697 
2698 // ////////////////////////////////////////////////////////////////////////// //
2699 // Matrices and Transformations
2700 
2701 /** Matrix class.
2702  *
2703  * Group: matrices
2704  */
2705 public align(1) struct NVGMatrix {
2706 align(1):
2707 private:
2708   static immutable float[6] IdentityMat = [
2709     1.0f, 0.0f,
2710     0.0f, 1.0f,
2711     0.0f, 0.0f,
2712   ];
2713 
2714 public:
2715   /// Matrix values. Initial value is identity matrix.
2716   float[6] mat = [
2717     1.0f, 0.0f,
2718     0.0f, 1.0f,
2719     0.0f, 0.0f,
2720   ];
2721 
2722 public nothrow @trusted @nogc:
2723   /// Create Matrix with the given values.
2724   this (const(float)[] amat...) {
2725     pragma(inline, true);
2726     if (amat.length >= 6) {
2727       mat.ptr[0..6] = amat.ptr[0..6];
2728     } else {
2729       mat.ptr[0..6] = 0;
2730       mat.ptr[0..amat.length] = amat[];
2731     }
2732   }
2733 
2734   /// Can be used to check validity of [inverted] result
2735   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2736 
2737   /// Returns `true` if this matrix is identity matrix.
2738   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2739 
2740   /// Returns new inverse matrix.
2741   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2742   NVGMatrix inverted () const {
2743     NVGMatrix res = this;
2744     res.invert;
2745     return res;
2746   }
2747 
2748   /// Inverts this matrix.
2749   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2750   ref NVGMatrix invert () return {
2751     float[6] inv = void;
2752     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2753     if (det > -1e-6 && det < 1e-6) {
2754       inv[] = float.nan;
2755     } else {
2756       immutable double invdet = 1.0/det;
2757       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2758       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2759       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2760       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2761       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2762       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2763     }
2764     mat.ptr[0..6] = inv.ptr[0..6];
2765     return this;
2766   }
2767 
2768   /// Sets this matrix to identity matrix.
2769   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2770 
2771   /// Translate this matrix.
2772   ref NVGMatrix translate (in float tx, in float ty) return {
2773     version(aliced) pragma(inline, true);
2774     return this.mul(Translated(tx, ty));
2775   }
2776 
2777   /// Scale this matrix.
2778   ref NVGMatrix scale (in float sx, in float sy) return {
2779     version(aliced) pragma(inline, true);
2780     return this.mul(Scaled(sx, sy));
2781   }
2782 
2783   /// Rotate this matrix.
2784   ref NVGMatrix rotate (in float a) return {
2785     version(aliced) pragma(inline, true);
2786     return this.mul(Rotated(a));
2787   }
2788 
2789   /// Skew this matrix by X axis.
2790   ref NVGMatrix skewX (in float a) return {
2791     version(aliced) pragma(inline, true);
2792     return this.mul(SkewedX(a));
2793   }
2794 
2795   /// Skew this matrix by Y axis.
2796   ref NVGMatrix skewY (in float a) return {
2797     version(aliced) pragma(inline, true);
2798     return this.mul(SkewedY(a));
2799   }
2800 
2801   /// Skew this matrix by both axes.
2802   ref NVGMatrix skewY (in float ax, in float ay) return {
2803     version(aliced) pragma(inline, true);
2804     return this.mul(SkewedXY(ax, ay));
2805   }
2806 
2807   /// Transform point with this matrix. `null` destinations are allowed.
2808   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2809   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2810     version(aliced) pragma(inline, true);
2811     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2812     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2813   }
2814 
2815   /// Transform point with this matrix.
2816   void point (ref float x, ref float y) nothrow @trusted @nogc {
2817     version(aliced) pragma(inline, true);
2818     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2819     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2820     x = nx;
2821     y = ny;
2822   }
2823 
2824   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2825   ref NVGMatrix mul() (const scope auto ref NVGMatrix s) {
2826     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2827     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2828     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2829     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2830     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2831     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2832     mat.ptr[0] = t0;
2833     mat.ptr[2] = t2;
2834     mat.ptr[4] = t4;
2835     return this;
2836   }
2837 
2838   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2839   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2840   /// Group: matrices
2841   ref NVGMatrix premul() (const scope auto ref NVGMatrix s) {
2842     NVGMatrix s2 = s;
2843     s2.mul(this);
2844     mat[] = s2.mat[];
2845     return this;
2846   }
2847 
2848   /// Multiply this matrix by [s], return result as new matrix.
2849   /// Performs operations in this left-to-right order.
2850   NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const {
2851     version(aliced) pragma(inline, true);
2852     NVGMatrix res = this;
2853     res.mul(s);
2854     return res;
2855   }
2856 
2857   /// Multiply this matrix by [s].
2858   /// Performs operations in this left-to-right order.
2859   ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) {
2860     version(aliced) pragma(inline, true);
2861     return this.mul(s);
2862   }
2863 
2864   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.
2865   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.
2866   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2867   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2868   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2869 
2870   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2871   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2872   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2873   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2874   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2875 
2876   /// Utility function to be used in `setXXX()`.
2877   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2878   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2879     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2880     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2881     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2882     mat.ptr[4] = tx; mat.ptr[5] = ty;
2883     return this;
2884   }
2885 
2886   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2887   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2888     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2889     mat.ptr[0] = cs; mat.ptr[1] = sn;
2890     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2891     mat.ptr[4] = tx; mat.ptr[5] = ty;
2892     return this;
2893   }
2894 
2895   /// Returns new identity matrix.
2896   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2897 
2898   /// Returns new translation matrix.
2899   static NVGMatrix Translated (in float tx, in float ty) {
2900     version(aliced) pragma(inline, true);
2901     NVGMatrix res = void;
2902     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2903     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2904     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2905     return res;
2906   }
2907 
2908   /// Returns new scaling matrix.
2909   static NVGMatrix Scaled (in float sx, in float sy) {
2910     version(aliced) pragma(inline, true);
2911     NVGMatrix res = void;
2912     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2913     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2914     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2915     return res;
2916   }
2917 
2918   /// Returns new rotation matrix. Angle is specified in radians.
2919   static NVGMatrix Rotated (in float a) {
2920     version(aliced) pragma(inline, true);
2921     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2922     NVGMatrix res = void;
2923     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2924     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2925     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2926     return res;
2927   }
2928 
2929   /// Returns new x-skewing matrix. Angle is specified in radians.
2930   static NVGMatrix SkewedX (in float a) {
2931     version(aliced) pragma(inline, true);
2932     NVGMatrix res = void;
2933     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2934     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2935     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2936     return res;
2937   }
2938 
2939   /// Returns new y-skewing matrix. Angle is specified in radians.
2940   static NVGMatrix SkewedY (in float a) {
2941     version(aliced) pragma(inline, true);
2942     NVGMatrix res = void;
2943     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2944     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2945     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2946     return res;
2947   }
2948 
2949   /// Returns new xy-skewing matrix. Angles are specified in radians.
2950   static NVGMatrix SkewedXY (in float ax, in float ay) {
2951     version(aliced) pragma(inline, true);
2952     NVGMatrix res = void;
2953     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2954     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2955     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2956     return res;
2957   }
2958 
2959   /// Utility function to be used in `setXXX()`.
2960   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2961   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2962     NVGMatrix res = void;
2963     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2964     return res;
2965   }
2966 
2967   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2968   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2969     NVGMatrix res = void;
2970     res.rotateTransform(a, tx, ty);
2971     return res;
2972   }
2973 }
2974 
2975 
2976 /// Converts degrees to radians.
2977 /// Group: matrices
2978 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2979 
2980 /// Converts radians to degrees.
2981 /// Group: matrices
2982 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2983 
2984 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2985 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2986 
2987 
2988 // ////////////////////////////////////////////////////////////////////////// //
2989 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc {
2990   p.clear();
2991   p.xform.identity;
2992   p.radius = 0.0f;
2993   p.feather = 1.0f;
2994   p.innerColor = p.middleColor = p.outerColor = color;
2995   p.midp = -1;
2996   p.simpleColor = true;
2997 }
2998 
2999 
3000 // ////////////////////////////////////////////////////////////////////////// //
3001 // State handling
3002 
3003 version(nanovega_debug_clipping) {
3004   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
3005   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
3006 }
3007 
3008 /** Pushes and saves the current render state into a state stack.
3009  * A matching [restore] must be used to restore the state.
3010  * Returns `false` if state stack overflowed.
3011  *
3012  * Group: state_handling
3013  */
3014 public bool save (NVGContext ctx) nothrow @trusted @nogc {
3015   if (ctx.nstates >= NVG_MAX_STATES) return false;
3016   if (ctx.nstates > 0) {
3017     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
3018     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
3019     ctx.params.renderPushClip(ctx.params.userPtr);
3020   }
3021   ++ctx.nstates;
3022   return true;
3023 }
3024 
3025 /// Pops and restores current render state.
3026 /// Group: state_handling
3027 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
3028   if (ctx.nstates <= 1) return false;
3029   ctx.states[ctx.nstates-1].clearPaint();
3030   ctx.params.renderPopClip(ctx.params.userPtr);
3031   --ctx.nstates;
3032   return true;
3033 }
3034 
3035 /// Resets current render state to default values. Does not affect the render state stack.
3036 /// Group: state_handling
3037 public void reset (NVGContext ctx) nothrow @trusted @nogc {
3038   NVGstate* state = nvg__getState(ctx);
3039   state.clearPaint();
3040 
3041   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3042   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3043   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3044   state.shapeAntiAlias = true;
3045   state.strokeWidth = 1.0f;
3046   state.miterLimit = 10.0f;
3047   state.lineCap = NVGLineCap.Butt;
3048   state.lineJoin = NVGLineCap.Miter;
3049   state.alpha = 1.0f;
3050   state.xform.identity;
3051 
3052   state.scissor.extent[] = -1.0f;
3053 
3054   state.fontSize = 16.0f;
3055   state.letterSpacing = 0.0f;
3056   state.lineHeight = 1.0f;
3057   state.fontBlur = 0.0f;
3058   state.textAlign.reset;
3059   state.fontId = 0;
3060   state.evenOddMode = false;
3061   state.dashCount = 0;
3062   state.lastFlattenDashCount = 0;
3063   state.dashStart = 0;
3064   state.firstDashIsGap = false;
3065   state.dasherActive = false;
3066 
3067   ctx.params.renderResetClip(ctx.params.userPtr);
3068 }
3069 
3070 /** Returns `true` if we have any room in state stack.
3071  * It is guaranteed to have at least 32 stack slots.
3072  *
3073  * Group: state_handling
3074  */
3075 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3076 
3077 /** Returns `true` if we have any saved state.
3078  *
3079  * Group: state_handling
3080  */
3081 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3082 
3083 /// Returns `true` if rendering is currently blocked.
3084 /// Group: state_handling
3085 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3086 
3087 /// Blocks/unblocks rendering
3088 /// Group: state_handling
3089 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3090 
3091 /// Blocks/unblocks rendering; returns previous state.
3092 /// Group: state_handling
3093 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; }
3094 
3095 
3096 // ////////////////////////////////////////////////////////////////////////// //
3097 // Render styles
3098 
3099 /// Sets filling mode to "even-odd".
3100 /// Group: render_styles
3101 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3102   NVGstate* state = nvg__getState(ctx);
3103   state.evenOddMode = true;
3104 }
3105 
3106 /// Sets filling mode to "non-zero" (this is default mode).
3107 /// Group: render_styles
3108 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3109   NVGstate* state = nvg__getState(ctx);
3110   state.evenOddMode = false;
3111 }
3112 
3113 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3114 /// Group: render_styles
3115 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3116   NVGstate* state = nvg__getState(ctx);
3117   state.shapeAntiAlias = enabled;
3118 }
3119 
3120 /// Sets the stroke width of the stroke style.
3121 /// Group: render_styles
3122 @scriptable
3123 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3124   NVGstate* state = nvg__getState(ctx);
3125   state.strokeWidth = width;
3126 }
3127 
3128 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3129 /// Group: render_styles
3130 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3131   NVGstate* state = nvg__getState(ctx);
3132   state.miterLimit = limit;
3133 }
3134 
3135 /// Sets how the end of the line (cap) is drawn,
3136 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3137 /// Group: render_styles
3138 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3139   NVGstate* state = nvg__getState(ctx);
3140   state.lineCap = cap;
3141 }
3142 
3143 /// Sets how sharp path corners are drawn.
3144 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3145 /// Group: render_styles
3146 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3147   NVGstate* state = nvg__getState(ctx);
3148   state.lineJoin = join;
3149 }
3150 
3151 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3152 /// Current limit is 16 pairs.
3153 /// Resets dash start to zero.
3154 /// Group: render_styles
3155 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3156   NVGstate* state = nvg__getState(ctx);
3157   state.dashCount = 0;
3158   state.dashStart = 0;
3159   state.firstDashIsGap = false;
3160   if (dashdata.length >= 2) {
3161     bool curFIsGap = true; // trick
3162     foreach (immutable idx, float f; dashdata) {
3163       curFIsGap = !curFIsGap;
3164       if (f < 0.01f) continue; // skip it
3165       if (idx == 0) {
3166         // register first dash
3167         state.firstDashIsGap = curFIsGap;
3168         state.dashes.ptr[state.dashCount++] = f;
3169       } else {
3170         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3171           // oops, continuation
3172           state.dashes[state.dashCount-1] += f;
3173         } else {
3174           if (state.dashCount == state.dashes.length) break;
3175           state.dashes[state.dashCount++] = f;
3176         }
3177       }
3178     }
3179     if (state.dashCount&1) {
3180       if (state.dashCount == 1) {
3181         state.dashCount = 0;
3182       } else {
3183         assert(state.dashCount < state.dashes.length);
3184         state.dashes[state.dashCount++] = 0;
3185       }
3186     }
3187     // calculate total dash path length
3188     state.totalDashLen = 0;
3189     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3190     if (state.totalDashLen < 0.01f) {
3191       state.dashCount = 0; // nothing to do
3192     } else {
3193       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3194     }
3195   }
3196 }
3197 
3198 public alias lineDash = setLineDash; /// Ditto.
3199 
3200 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3201 /// Current limit is 16 pairs.
3202 /// Group: render_styles
3203 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3204   NVGstate* state = nvg__getState(ctx);
3205   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3206     state.lastFlattenDashCount = uint.max; // force re-flattening
3207   }
3208   state.dashStart = dashStart;
3209 }
3210 
3211 public alias lineDashStart = setLineDashStart; /// Ditto.
3212 
3213 /// Sets the transparency applied to all rendered shapes.
3214 /// Already transparent paths will get proportionally more transparent as well.
3215 /// Group: render_styles
3216 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3217   NVGstate* state = nvg__getState(ctx);
3218   state.alpha = alpha;
3219 }
3220 
3221 private void strokeColor() {}
3222 
3223 static if (NanoVegaHasArsdColor) {
3224 /// Sets current stroke style to a solid color.
3225 /// Group: render_styles
3226 @scriptable
3227 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3228   NVGstate* state = nvg__getState(ctx);
3229   nvg__setPaintColor(state.stroke, NVGColor(color));
3230 }
3231 }
3232 
3233 /// Sets current stroke style to a solid color.
3234 /// Group: render_styles
3235 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3236   NVGstate* state = nvg__getState(ctx);
3237   nvg__setPaintColor(state.stroke, color);
3238 }
3239 
3240 @scriptable
3241 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3242 	strokePaint(ctx, *paint);
3243 }
3244 
3245 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3246 /// Group: render_styles
3247 @scriptable
3248 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3249   NVGstate* state = nvg__getState(ctx);
3250   state.stroke = paint;
3251   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3252   state.stroke.xform.mul(state.xform);
3253 }
3254 
3255 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3256 // for scriptable reflection. it just needs to be declared first among the overloads
3257 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3258 
3259 static if (NanoVegaHasArsdColor) {
3260 /// Sets current fill style to a solid color.
3261 /// Group: render_styles
3262 @scriptable
3263 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3264   NVGstate* state = nvg__getState(ctx);
3265   nvg__setPaintColor(state.fill, NVGColor(color));
3266 }
3267 }
3268 
3269 /// Sets current fill style to a solid color.
3270 /// Group: render_styles
3271 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc {
3272   NVGstate* state = nvg__getState(ctx);
3273   nvg__setPaintColor(state.fill, color);
3274 
3275 }
3276 
3277 @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)
3278 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3279 	fillPaint(ctx, *paint);
3280 }
3281 
3282 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3283 /// Group: render_styles
3284 @scriptable
3285 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc {
3286   NVGstate* state = nvg__getState(ctx);
3287   state.fill = paint;
3288   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3289   state.fill.xform.mul(state.xform);
3290 }
3291 
3292 /// Sets current fill style to a multistop linear gradient.
3293 /// Group: render_styles
3294 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3295   if (!lgs.valid) {
3296     NVGPaint p = void;
3297     memset(&p, 0, p.sizeof);
3298     nvg__setPaintColor(p, NVGColor.red);
3299     ctx.fillPaint = p;
3300   } else if (lgs.midp >= -1) {
3301     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3302     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3303   } else {
3304     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3305   }
3306 }
3307 
3308 /// Returns current transformation matrix.
3309 /// Group: render_transformations
3310 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3311   NVGstate* state = nvg__getState(ctx);
3312   return state.xform;
3313 }
3314 
3315 /// Sets current transformation matrix.
3316 /// Group: render_transformations
3317 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc {
3318   NVGstate* state = nvg__getState(ctx);
3319   state.xform = m;
3320 }
3321 
3322 /// Resets current transform to an identity matrix.
3323 /// Group: render_transformations
3324 @scriptable
3325 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3326   NVGstate* state = nvg__getState(ctx);
3327   state.xform.identity;
3328 }
3329 
3330 /// Premultiplies current coordinate system by specified matrix.
3331 /// Group: render_transformations
3332 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc {
3333   NVGstate* state = nvg__getState(ctx);
3334   //nvgTransformPremultiply(state.xform[], t[]);
3335   state.xform *= mt;
3336 }
3337 
3338 /// Translates current coordinate system.
3339 /// Group: render_transformations
3340 @scriptable
3341 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3342   NVGstate* state = nvg__getState(ctx);
3343   //NVGMatrix t = void;
3344   //nvgTransformTranslate(t[], x, y);
3345   //nvgTransformPremultiply(state.xform[], t[]);
3346   state.xform.premul(NVGMatrix.Translated(x, y));
3347 }
3348 
3349 /// Rotates current coordinate system. Angle is specified in radians.
3350 /// Group: render_transformations
3351 @scriptable
3352 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3353   NVGstate* state = nvg__getState(ctx);
3354   //NVGMatrix t = void;
3355   //nvgTransformRotate(t[], angle);
3356   //nvgTransformPremultiply(state.xform[], t[]);
3357   state.xform.premul(NVGMatrix.Rotated(angle));
3358 }
3359 
3360 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3361 /// Group: render_transformations
3362 @scriptable
3363 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3364   NVGstate* state = nvg__getState(ctx);
3365   //NVGMatrix t = void;
3366   //nvgTransformSkewX(t[], angle);
3367   //nvgTransformPremultiply(state.xform[], t[]);
3368   state.xform.premul(NVGMatrix.SkewedX(angle));
3369 }
3370 
3371 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3372 /// Group: render_transformations
3373 @scriptable
3374 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3375   NVGstate* state = nvg__getState(ctx);
3376   //NVGMatrix t = void;
3377   //nvgTransformSkewY(t[], angle);
3378   //nvgTransformPremultiply(state.xform[], t[]);
3379   state.xform.premul(NVGMatrix.SkewedY(angle));
3380 }
3381 
3382 /// Scales the current coordinate system.
3383 /// Group: render_transformations
3384 @scriptable
3385 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3386   NVGstate* state = nvg__getState(ctx);
3387   //NVGMatrix t = void;
3388   //nvgTransformScale(t[], x, y);
3389   //nvgTransformPremultiply(state.xform[], t[]);
3390   state.xform.premul(NVGMatrix.Scaled(x, y));
3391 }
3392 
3393 
3394 // ////////////////////////////////////////////////////////////////////////// //
3395 // Images
3396 
3397 /// Creates image by loading it from the disk from specified file name.
3398 /// Returns handle to the image or 0 on error.
3399 /// Group: images
3400 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3401   static if (NanoVegaHasArsdImage) {
3402     import arsd.image;
3403     // do we have new arsd API to load images?
3404     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3405       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3406     }
3407     try {
3408       auto oimg = MemoryImage.fromImageFile(filename);
3409       if (auto img = cast(TrueColorImage)oimg) {
3410         scope(exit) oimg.clearInternal();
3411         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3412       } else {
3413         TrueColorImage img = oimg.getAsTrueColorImage;
3414         scope(exit) img.clearInternal();
3415         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3416         oimg = null;
3417         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3418       }
3419     } catch (Exception) {}
3420     return NVGImage.init;
3421   } else {
3422     import std.internal.cstring;
3423     ubyte* img;
3424     int w, h, n;
3425     stbi_set_unpremultiply_on_load(1);
3426     stbi_convert_iphone_png_to_rgb(1);
3427     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3428     if (img is null) {
3429       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3430       return NVGImage.init;
3431     }
3432     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3433     stbi_image_free(img);
3434     return image;
3435   }
3436 }
3437 
3438 static if (NanoVegaHasArsdImage) {
3439   /// Creates image by loading it from the specified memory image.
3440   /// Returns handle to the image or 0 on error.
3441   /// Group: images
3442   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3443     if (img is null) return NVGImage.init;
3444     if (auto tc = cast(TrueColorImage)img) {
3445       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3446     } else {
3447       auto tc = img.getAsTrueColorImage;
3448       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3449       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3450     }
3451   }
3452 } else {
3453   /// Creates image by loading it from the specified chunk of memory.
3454   /// Returns handle to the image or 0 on error.
3455   /// Group: images
3456   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3457     int w, h, n, image;
3458     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3459     if (img is null) {
3460       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3461       return NVGImage.init;
3462     }
3463     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3464     stbi_image_free(img);
3465     return image;
3466   }
3467 }
3468 
3469 /// Creates image from specified image data.
3470 /// Returns handle to the image or 0 on error.
3471 /// Group: images
3472 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3473   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3474   uint imageFlags = 0;
3475   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3476   NVGImage res;
3477   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3478   if (res.id > 0) {
3479     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3480     res.ctx = ctx;
3481     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3482   }
3483   return res;
3484 }
3485 
3486 /// Updates image data specified by image handle.
3487 /// Group: images
3488 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3489   if (image.valid) {
3490     int w, h;
3491     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3492     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3493     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3494   }
3495 }
3496 
3497 /// Returns the dimensions of a created image.
3498 /// Group: images
3499 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3500   if (image.valid) {
3501     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3502     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3503   }
3504 }
3505 
3506 /// Deletes created image.
3507 /// Group: images
3508 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3509   if (ctx is null || !image.valid) return;
3510   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3511   image.clear();
3512 }
3513 
3514 
3515 // ////////////////////////////////////////////////////////////////////////// //
3516 // Paints
3517 
3518 private void linearGradient() {} // hack for dmd bug
3519 
3520 static if (NanoVegaHasArsdColor) {
3521 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3522  * of the linear gradient, icol specifies the start color and ocol the end color.
3523  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3524  *
3525  * Group: paints
3526  */
3527 @scriptable
3528 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 {
3529   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3530 }
3531 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3532  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3533  * range `(0..1)`, and ocol the end color.
3534  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3535  *
3536  * Group: paints
3537  */
3538 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 {
3539   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3540 }
3541 }
3542 
3543 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3544  * of the linear gradient, icol specifies the start color and ocol the end color.
3545  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3546  *
3547  * Group: paints
3548  */
3549 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3550   enum large = 1e5f;
3551 
3552   NVGPaint p = void;
3553   memset(&p, 0, p.sizeof);
3554   p.simpleColor = false;
3555 
3556   // Calculate transform aligned to the line
3557   float dx = ex-sx;
3558   float dy = ey-sy;
3559   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3560   if (d > 0.0001f) {
3561     dx /= d;
3562     dy /= d;
3563   } else {
3564     dx = 0;
3565     dy = 1;
3566   }
3567 
3568   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3569   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3570   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3571 
3572   p.extent.ptr[0] = large;
3573   p.extent.ptr[1] = large+d*0.5f;
3574 
3575   p.radius = 0.0f;
3576 
3577   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3578 
3579   p.innerColor = p.middleColor = icol;
3580   p.outerColor = ocol;
3581   p.midp = -1;
3582 
3583   return p;
3584 }
3585 
3586 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3587  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3588  * range `(0..1)`, and ocol the end color.
3589  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3590  *
3591  * Group: paints
3592  */
3593 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, const scope auto ref NVGColor icol, in float midp, const scope auto ref NVGColor mcol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3594   enum large = 1e5f;
3595 
3596   NVGPaint p = void;
3597   memset(&p, 0, p.sizeof);
3598   p.simpleColor = false;
3599 
3600   // Calculate transform aligned to the line
3601   float dx = ex-sx;
3602   float dy = ey-sy;
3603   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3604   if (d > 0.0001f) {
3605     dx /= d;
3606     dy /= d;
3607   } else {
3608     dx = 0;
3609     dy = 1;
3610   }
3611 
3612   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3613   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3614   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3615 
3616   p.extent.ptr[0] = large;
3617   p.extent.ptr[1] = large+d*0.5f;
3618 
3619   p.radius = 0.0f;
3620 
3621   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3622 
3623   if (midp <= 0) {
3624     p.innerColor = p.middleColor = mcol;
3625     p.midp = -1;
3626   } else if (midp > 1) {
3627     p.innerColor = p.middleColor = icol;
3628     p.midp = -1;
3629   } else {
3630     p.innerColor = icol;
3631     p.middleColor = mcol;
3632     p.midp = midp;
3633   }
3634   p.outerColor = ocol;
3635 
3636   return p;
3637 }
3638 
3639 static if (NanoVegaHasArsdColor) {
3640 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3641  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3642  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3643  *
3644  * Group: paints
3645  */
3646 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 {
3647   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3648 }
3649 }
3650 
3651 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3652  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3653  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3654  *
3655  * Group: paints
3656  */
3657 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3658   immutable float r = (inr+outr)*0.5f;
3659   immutable float f = (outr-inr);
3660 
3661   NVGPaint p = void;
3662   memset(&p, 0, p.sizeof);
3663   p.simpleColor = false;
3664 
3665   p.xform.identity;
3666   p.xform.mat.ptr[4] = cx;
3667   p.xform.mat.ptr[5] = cy;
3668 
3669   p.extent.ptr[0] = r;
3670   p.extent.ptr[1] = r;
3671 
3672   p.radius = r;
3673 
3674   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3675 
3676   p.innerColor = p.middleColor = icol;
3677   p.outerColor = ocol;
3678   p.midp = -1;
3679 
3680   return p;
3681 }
3682 
3683 static if (NanoVegaHasArsdColor) {
3684 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3685  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3686  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3687  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3688  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3689  *
3690  * Group: paints
3691  */
3692 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 {
3693   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3694 }
3695 }
3696 
3697 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3698  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3699  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3700  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3701  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3702  *
3703  * Group: paints
3704  */
3705 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, const scope auto ref NVGColor icol, const scope auto ref NVGColor ocol) nothrow @trusted @nogc {
3706   NVGPaint p = void;
3707   memset(&p, 0, p.sizeof);
3708   p.simpleColor = false;
3709 
3710   p.xform.identity;
3711   p.xform.mat.ptr[4] = x+w*0.5f;
3712   p.xform.mat.ptr[5] = y+h*0.5f;
3713 
3714   p.extent.ptr[0] = w*0.5f;
3715   p.extent.ptr[1] = h*0.5f;
3716 
3717   p.radius = r;
3718 
3719   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3720 
3721   p.innerColor = p.middleColor = icol;
3722   p.outerColor = ocol;
3723   p.midp = -1;
3724 
3725   return p;
3726 }
3727 
3728 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3729  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3730  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3731  *
3732  * Group: paints
3733  */
3734 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, const scope auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc {
3735   NVGPaint p = void;
3736   memset(&p, 0, p.sizeof);
3737   p.simpleColor = false;
3738 
3739   p.xform.identity.rotate(angle);
3740   p.xform.mat.ptr[4] = cx;
3741   p.xform.mat.ptr[5] = cy;
3742 
3743   p.extent.ptr[0] = w;
3744   p.extent.ptr[1] = h;
3745 
3746   p.image = image;
3747 
3748   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3749   p.midp = -1;
3750 
3751   return p;
3752 }
3753 
3754 /// Linear gradient with multiple stops.
3755 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3756 /// Group: paints
3757 public struct NVGLGS {
3758 private:
3759   NVGColor ic, mc, oc; // inner, middle, out
3760   float midp;
3761   NVGImage imgid;
3762   // [imagePattern] arguments
3763   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3764   public float angle; ///
3765 
3766 public:
3767   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3768   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3769 }
3770 
3771 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3772  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3773  *
3774  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3775  * Group: paints
3776  */
3777 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc {
3778   if (!lgs.valid) {
3779     NVGPaint p = void;
3780     memset(&p, 0, p.sizeof);
3781     nvg__setPaintColor(p, NVGColor.red);
3782     return p;
3783   } else if (lgs.midp >= -1) {
3784     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3785   } else {
3786     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3787   }
3788 }
3789 
3790 /// Gradient Stop Point.
3791 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3792 /// Group: paints
3793 public struct NVGGradientStop {
3794   float offset = 0; /// [0..1]
3795   NVGColor color; ///
3796 
3797   this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3798   static if (NanoVegaHasArsdColor) {
3799     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3800   }
3801 }
3802 
3803 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3804 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3805 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3806 /// Group: paints
3807 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3808   // based on the code by Jorge Acereda <jacereda@gmail.com>
3809   enum NVG_GRADIENT_SAMPLES = 1024;
3810   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3811     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3812     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3813     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3814     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3815     uint sc = 0xffffffffU;
3816     uint sh = 24;
3817     uint r = cast(uint)(s0.color.rgba[0]*sc);
3818     uint g = cast(uint)(s0.color.rgba[1]*sc);
3819     uint b = cast(uint)(s0.color.rgba[2]*sc);
3820     uint a = cast(uint)(s0.color.rgba[3]*sc);
3821     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3822     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3823     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3824     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3825     dst += s;
3826     foreach (immutable _; s..e) {
3827       version(BigEndian) {
3828         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3829       } else {
3830         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3831       }
3832       r += dr;
3833       g += dg;
3834       b += db;
3835       a += da;
3836     }
3837   }
3838 
3839   NVGLGS res;
3840   res.cx = sx;
3841   res.cy = sy;
3842 
3843   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3844     // create simple linear gradient
3845     res.ic = res.mc = stops.ptr[0].color;
3846     res.oc = stops.ptr[1].color;
3847     res.midp = -1;
3848     res.dimx = ex;
3849     res.dimy = ey;
3850   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3851     // create simple linear gradient with middle stop
3852     res.ic = stops.ptr[0].color;
3853     res.mc = stops.ptr[1].color;
3854     res.oc = stops.ptr[2].color;
3855     res.midp = stops.ptr[1].offset;
3856     res.dimx = ex;
3857     res.dimy = ey;
3858   } else {
3859     // create image gradient
3860     uint[NVG_GRADIENT_SAMPLES] data = void;
3861     immutable float w = ex-sx;
3862     immutable float h = ey-sy;
3863     res.dimx = nvg__sqrtf(w*w+h*h);
3864     res.dimy = 1; //???
3865 
3866     res.angle =
3867       (/*nvg__absf(h) < 0.0001 ? 0 :
3868        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3869        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3870 
3871     if (stops.length > 0) {
3872       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3873       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3874       if (stops.length > 64) stops = stops[0..64];
3875       if (stops.length) {
3876         s0.color = stops[0].color;
3877         s1.color = stops[$-1].color;
3878       }
3879       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3880       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3881       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3882       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3883     }
3884   }
3885   return res;
3886 }
3887 
3888 
3889 // ////////////////////////////////////////////////////////////////////////// //
3890 // Scissoring
3891 
3892 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3893 /// Group: scissoring
3894 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3895   NVGstate* state = nvg__getState(ctx);
3896 
3897   w = nvg__max(0.0f, w);
3898   h = nvg__max(0.0f, h);
3899 
3900   state.scissor.xform.identity;
3901   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3902   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3903   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3904   state.scissor.xform.mul(state.xform);
3905 
3906   state.scissor.extent.ptr[0] = w*0.5f;
3907   state.scissor.extent.ptr[1] = h*0.5f;
3908 }
3909 
3910 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3911 /// Arguments: [x, y, w, h]*
3912 /// Group: scissoring
3913 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3914   enum ArgC = 4;
3915   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3916   if (args.length < ArgC) return;
3917   NVGstate* state = nvg__getState(ctx);
3918   const(float)* aptr = args.ptr;
3919   foreach (immutable idx; 0..args.length/ArgC) {
3920     immutable x = *aptr++;
3921     immutable y = *aptr++;
3922     immutable w = nvg__max(0.0f, *aptr++);
3923     immutable h = nvg__max(0.0f, *aptr++);
3924 
3925     state.scissor.xform.identity;
3926     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3927     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3928     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3929     state.scissor.xform.mul(state.xform);
3930 
3931     state.scissor.extent.ptr[0] = w*0.5f;
3932     state.scissor.extent.ptr[1] = h*0.5f;
3933   }
3934 }
3935 
3936 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3937   immutable float minx = nvg__max(ax, bx);
3938   immutable float miny = nvg__max(ay, by);
3939   immutable float maxx = nvg__min(ax+aw, bx+bw);
3940   immutable float maxy = nvg__min(ay+ah, by+bh);
3941   dst[0] = minx;
3942   dst[1] = miny;
3943   dst[2] = nvg__max(0.0f, maxx-minx);
3944   dst[3] = nvg__max(0.0f, maxy-miny);
3945 }
3946 
3947 /** Intersects current scissor rectangle with the specified rectangle.
3948  * The scissor rectangle is transformed by the current transform.
3949  * Note: in case the rotation of previous scissor rect differs from
3950  * the current one, the intersection will be done between the specified
3951  * rectangle and the previous scissor rectangle transformed in the current
3952  * transform space. The resulting shape is always rectangle.
3953  *
3954  * Group: scissoring
3955  */
3956 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3957   NVGstate* state = nvg__getState(ctx);
3958 
3959   // If no previous scissor has been set, set the scissor as current scissor.
3960   if (state.scissor.extent.ptr[0] < 0) {
3961     ctx.scissor(x, y, w, h);
3962     return;
3963   }
3964 
3965   NVGMatrix pxform = void;
3966   NVGMatrix invxorm = void;
3967   float[4] rect = void;
3968 
3969   // Transform the current scissor rect into current transform space.
3970   // If there is difference in rotation, this will be approximation.
3971   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3972   pxform = state.scissor.xform;
3973   immutable float ex = state.scissor.extent.ptr[0];
3974   immutable float ey = state.scissor.extent.ptr[1];
3975   //nvgTransformInverse(invxorm[], state.xform[]);
3976   invxorm = state.xform.inverted;
3977   //nvgTransformMultiply(pxform[], invxorm[]);
3978   pxform.mul(invxorm);
3979   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3980   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3981 
3982   // Intersect rects.
3983   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3984 
3985   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3986   ctx.scissor(rect.ptr[0..4]);
3987 }
3988 
3989 /** Intersects current scissor rectangle with the specified rectangle.
3990  * The scissor rectangle is transformed by the current transform.
3991  * Note: in case the rotation of previous scissor rect differs from
3992  * the current one, the intersection will be done between the specified
3993  * rectangle and the previous scissor rectangle transformed in the current
3994  * transform space. The resulting shape is always rectangle.
3995  *
3996  * Arguments: [x, y, w, h]*
3997  *
3998  * Group: scissoring
3999  */
4000 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
4001   enum ArgC = 4;
4002   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
4003   if (args.length < ArgC) return;
4004   const(float)* aptr = args.ptr;
4005   foreach (immutable idx; 0..args.length/ArgC) {
4006     immutable x = *aptr++;
4007     immutable y = *aptr++;
4008     immutable w = *aptr++;
4009     immutable h = *aptr++;
4010     ctx.intersectScissor(x, y, w, h);
4011   }
4012 }
4013 
4014 /// Reset and disables scissoring.
4015 /// Group: scissoring
4016 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
4017   NVGstate* state = nvg__getState(ctx);
4018   state.scissor.xform.mat[] = 0.0f;
4019   state.scissor.extent[] = -1.0f;
4020 }
4021 
4022 
4023 // ////////////////////////////////////////////////////////////////////////// //
4024 // Render-Time Affine Transformations
4025 
4026 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
4027 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
4028 /// Group: gpu_affine
4029 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc {
4030   ctx.gpuAffine = mat;
4031   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
4032 }
4033 
4034 /// Get current GPU affine transformatin matrix.
4035 /// Group: gpu_affine
4036 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
4037   pragma(inline, true);
4038   return ctx.gpuAffine;
4039 }
4040 
4041 /// "Untransform" point using current GPU affine matrix.
4042 /// Group: gpu_affine
4043 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4044   if (ctx.gpuAffine.isIdentity) {
4045     if (dx !is null) *dx = x;
4046     if (dy !is null) *dy = y;
4047   } else {
4048     // inverse GPU transformation
4049     NVGMatrix igpu = ctx.gpuAffine.inverted;
4050     igpu.point(dx, dy, x, y);
4051   }
4052 }
4053 
4054 
4055 // ////////////////////////////////////////////////////////////////////////// //
4056 // rasterization (tesselation) code
4057 
4058 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4059   //pragma(inline, true);
4060   immutable float dx = x2-x1;
4061   immutable float dy = y2-y1;
4062   return dx*dx+dy*dy < tol*tol;
4063 }
4064 
4065 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4066   immutable float pqx = qx-px;
4067   immutable float pqy = qy-py;
4068   float dx = x-px;
4069   float dy = y-py;
4070   immutable float d = pqx*pqx+pqy*pqy;
4071   float t = pqx*dx+pqy*dy;
4072   if (d > 0) t /= d;
4073   if (t < 0) t = 0; else if (t > 1) t = 1;
4074   dx = px+t*pqx-x;
4075   dy = py+t*pqy-y;
4076   return dx*dx+dy*dy;
4077 }
4078 
4079 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4080   int nvals = cast(int)vals.length;
4081   static if (useCommand) {
4082     enum addon = 1;
4083   } else {
4084     enum addon = 0;
4085     if (nvals == 0) return; // nothing to do
4086   }
4087 
4088   NVGstate* state = nvg__getState(ctx);
4089 
4090   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4091     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4092     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4093     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4094     if (commands is null) assert(0, "NanoVega: out of memory");
4095     ctx.commands = commands;
4096     ctx.ccommands = ccommands;
4097     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4098   }
4099 
4100   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4101 
4102   if (acmd != Command.Close && acmd != Command.Winding) {
4103     //assert(nvals+addon >= 3);
4104     ctx.commandx = vals.ptr[nvals-2];
4105     ctx.commandy = vals.ptr[nvals-1];
4106   }
4107 
4108   // copy commands
4109   float* vp = ctx.commands+ctx.ncommands;
4110   static if (useCommand) {
4111     vp[0] = cast(float)acmd;
4112     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4113   } else {
4114     memcpy(vp, vals.ptr, nvals*float.sizeof);
4115   }
4116   ctx.ncommands += nvals+addon;
4117 
4118   // transform commands
4119   int i = nvals+addon;
4120   while (i > 0) {
4121     int nlen = 1;
4122     final switch (cast(Command)(*vp)) {
4123       case Command.MoveTo:
4124       case Command.LineTo:
4125         assert(i >= 3);
4126         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4127         nlen = 3;
4128         break;
4129       case Command.BezierTo:
4130         assert(i >= 7);
4131         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4132         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4133         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4134         nlen = 7;
4135         break;
4136       case Command.Close:
4137         nlen = 1;
4138         break;
4139       case Command.Winding:
4140         nlen = 2;
4141         break;
4142     }
4143     assert(nlen > 0 && nlen <= i);
4144     i -= nlen;
4145     vp += nlen;
4146   }
4147 }
4148 
4149 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4150   // no need to clear paths, as data is not copied there
4151   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4152   ctx.cache.npoints = 0;
4153   ctx.cache.npaths = 0;
4154   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4155   ctx.cache.clipmode = NVGClipMode.None;
4156 }
4157 
4158 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4159   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4160 }
4161 
4162 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4163   import core.stdc.stdlib : realloc;
4164   import core.stdc.string : memset;
4165 
4166   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4167     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4168     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4169     if (paths is null) assert(0, "NanoVega: out of memory");
4170     ctx.cache.paths = paths;
4171     ctx.cache.cpaths = cpaths;
4172   }
4173 
4174   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4175   memset(path, 0, NVGpath.sizeof);
4176   path.first = ctx.cache.npoints;
4177   path.mWinding = NVGWinding.CCW;
4178 }
4179 
4180 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4181   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4182 }
4183 
4184 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4185   NVGpath* path = nvg__lastPath(ctx);
4186   if (path is null) return;
4187 
4188   if (path.count > 0 && ctx.cache.npoints > 0) {
4189     NVGpoint* pt = nvg__lastPoint(ctx);
4190     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4191       pt.flags |= flags;
4192       return;
4193     }
4194   }
4195 
4196   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4197     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4198     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4199     if (points is null) return;
4200     ctx.cache.points = points;
4201     ctx.cache.cpoints = cpoints;
4202   }
4203 
4204   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4205   memset(pt, 0, (*pt).sizeof);
4206   pt.x = x;
4207   pt.y = y;
4208   pt.flags = cast(ubyte)flags;
4209 
4210   ++ctx.cache.npoints;
4211   ++path.count;
4212 }
4213 
4214 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4215   NVGpath* path = nvg__lastPath(ctx);
4216   if (path is null) return;
4217   path.closed = true;
4218 }
4219 
4220 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4221   NVGpath* path = nvg__lastPath(ctx);
4222   if (path is null) return;
4223   path.mWinding = winding;
4224 }
4225 
4226 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4227   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4228   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4229   return (sx+sy)*0.5f;
4230 }
4231 
4232 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4233   if (nverts > ctx.cache.cverts) {
4234     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4235     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4236     if (verts is null) return null;
4237     ctx.cache.verts = verts;
4238     ctx.cache.cverts = cverts;
4239   }
4240 
4241   return ctx.cache.verts;
4242 }
4243 
4244 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4245   immutable float abx = bx-ax;
4246   immutable float aby = by-ay;
4247   immutable float acx = cx-ax;
4248   immutable float acy = cy-ay;
4249   return acx*aby-abx*acy;
4250 }
4251 
4252 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4253   float area = 0;
4254   foreach (int i; 2..npts) {
4255     NVGpoint* a = &pts[0];
4256     NVGpoint* b = &pts[i-1];
4257     NVGpoint* c = &pts[i];
4258     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4259   }
4260   return area*0.5f;
4261 }
4262 
4263 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4264   NVGpoint tmp = void;
4265   int i = 0, j = npts-1;
4266   while (i < j) {
4267     tmp = pts[i];
4268     pts[i] = pts[j];
4269     pts[j] = tmp;
4270     ++i;
4271     --j;
4272   }
4273 }
4274 
4275 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4276   vtx.x = x;
4277   vtx.y = y;
4278   vtx.u = u;
4279   vtx.v = v;
4280 }
4281 
4282 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 {
4283   if (level > 10) return;
4284 
4285   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4286   /*
4287   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4288     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4289       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4290       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4291     }
4292     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4293       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4294       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4295       return;
4296     }
4297   }
4298   */
4299 
4300   immutable float x12 = (x1+x2)*0.5f;
4301   immutable float y12 = (y1+y2)*0.5f;
4302   immutable float x23 = (x2+x3)*0.5f;
4303   immutable float y23 = (y2+y3)*0.5f;
4304   immutable float x34 = (x3+x4)*0.5f;
4305   immutable float y34 = (y3+y4)*0.5f;
4306   immutable float x123 = (x12+x23)*0.5f;
4307   immutable float y123 = (y12+y23)*0.5f;
4308 
4309   immutable float dx = x4-x1;
4310   immutable float dy = y4-y1;
4311   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4312   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4313 
4314   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4315     nvg__addPoint(ctx, x4, y4, type);
4316     return;
4317   }
4318 
4319   immutable float x234 = (x23+x34)*0.5f;
4320   immutable float y234 = (y23+y34)*0.5f;
4321   immutable float x1234 = (x123+x234)*0.5f;
4322   immutable float y1234 = (y123+y234)*0.5f;
4323 
4324   // "taxicab" / "manhattan" check for flat curves
4325   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) {
4326     nvg__addPoint(ctx, x1234, y1234, type);
4327     return;
4328   }
4329 
4330   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4331   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4332 }
4333 
4334 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4335 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4336 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 {
4337   enum CollinearEPS = 0.00000001f; // 0.00001f;
4338   enum AngleTolEPS = 0.01f;
4339 
4340   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4341     pragma(inline, true);
4342     immutable float dx = x2-x1;
4343     immutable float dy = y2-y1;
4344     return dx*dx+dy*dy;
4345   }
4346 
4347   if (level == 0) {
4348     nvg__addPoint(ctx, x1, y1, 0);
4349     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4350     nvg__addPoint(ctx, x4, y4, type);
4351     return;
4352   }
4353 
4354   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4355 
4356   // calculate all the mid-points of the line segments
4357   immutable float x12 = (x1+x2)*0.5f;
4358   immutable float y12 = (y1+y2)*0.5f;
4359   immutable float x23 = (x2+x3)*0.5f;
4360   immutable float y23 = (y2+y3)*0.5f;
4361   immutable float x34 = (x3+x4)*0.5f;
4362   immutable float y34 = (y3+y4)*0.5f;
4363   immutable float x123 = (x12+x23)*0.5f;
4364   immutable float y123 = (y12+y23)*0.5f;
4365   immutable float x234 = (x23+x34)*0.5f;
4366   immutable float y234 = (y23+y34)*0.5f;
4367   immutable float x1234 = (x123+x234)*0.5f;
4368   immutable float y1234 = (y123+y234)*0.5f;
4369 
4370   // try to approximate the full cubic curve by a single straight line
4371   immutable float dx = x4-x1;
4372   immutable float dy = y4-y1;
4373 
4374   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4375   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4376   //immutable float da1, da2, k;
4377 
4378   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4379     case 0:
4380       // all collinear or p1 == p4
4381       float k = dx*dx+dy*dy;
4382       if (k == 0) {
4383         d2 = distSquared(x1, y1, x2, y2);
4384         d3 = distSquared(x4, y4, x3, y3);
4385       } else {
4386         k = 1.0f/k;
4387         float da1 = x2-x1;
4388         float da2 = y2-y1;
4389         d2 = k*(da1*dx+da2*dy);
4390         da1 = x3-x1;
4391         da2 = y3-y1;
4392         d3 = k*(da1*dx+da2*dy);
4393         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4394           // Simple collinear case, 1---2---3---4
4395           // We can leave just two endpoints
4396           return;
4397         }
4398              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4399         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4400         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4401 
4402              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4403         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4404         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4405       }
4406       if (d2 > d3) {
4407         if (d2 < ctx.tessTol) {
4408           nvg__addPoint(ctx, x2, y2, type);
4409           return;
4410         }
4411       } if (d3 < ctx.tessTol) {
4412         nvg__addPoint(ctx, x3, y3, type);
4413         return;
4414       }
4415       break;
4416     case 1:
4417       // p1,p2,p4 are collinear, p3 is significant
4418       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4419         if (ctx.angleTol < AngleTolEPS) {
4420           nvg__addPoint(ctx, x23, y23, type);
4421           return;
4422         } else {
4423           // angle condition
4424           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4425           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4426           if (da1 < ctx.angleTol) {
4427             nvg__addPoint(ctx, x2, y2, type);
4428             nvg__addPoint(ctx, x3, y3, type);
4429             return;
4430           }
4431           if (ctx.cuspLimit != 0.0) {
4432             if (da1 > ctx.cuspLimit) {
4433               nvg__addPoint(ctx, x3, y3, type);
4434               return;
4435             }
4436           }
4437         }
4438       }
4439       break;
4440     case 2:
4441       // p1,p3,p4 are collinear, p2 is significant
4442       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4443         if (ctx.angleTol < AngleTolEPS) {
4444           nvg__addPoint(ctx, x23, y23, type);
4445           return;
4446         } else {
4447           // angle condition
4448           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4449           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4450           if (da1 < ctx.angleTol) {
4451             nvg__addPoint(ctx, x2, y2, type);
4452             nvg__addPoint(ctx, x3, y3, type);
4453             return;
4454           }
4455           if (ctx.cuspLimit != 0.0) {
4456             if (da1 > ctx.cuspLimit) {
4457               nvg__addPoint(ctx, x2, y2, type);
4458               return;
4459             }
4460           }
4461         }
4462       }
4463       break;
4464     case 3:
4465       // regular case
4466       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4467         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4468         if (ctx.angleTol < AngleTolEPS) {
4469           nvg__addPoint(ctx, x23, y23, type);
4470           return;
4471         } else {
4472           // angle and cusp condition
4473           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4474           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4475           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4476           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4477           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4478           if (da1+da2 < ctx.angleTol) {
4479             // finally we can stop the recursion
4480             nvg__addPoint(ctx, x23, y23, type);
4481             return;
4482           }
4483           if (ctx.cuspLimit != 0.0) {
4484             if (da1 > ctx.cuspLimit) {
4485               nvg__addPoint(ctx, x2, y2, type);
4486               return;
4487             }
4488             if (da2 > ctx.cuspLimit) {
4489               nvg__addPoint(ctx, x3, y3, type);
4490               return;
4491             }
4492           }
4493         }
4494       }
4495       break;
4496   }
4497 
4498   // continue subdivision
4499   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4500   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4501 }
4502 
4503 
4504 // Adaptive forward differencing for bezier tesselation.
4505 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4506 // "Adaptive forward differencing for rendering curves and surfaces."
4507 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4508 // original code by Taylor Holliday <taylor@audulus.com>
4509 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 {
4510   enum AFD_ONE = (1<<10);
4511 
4512   // power basis
4513   immutable float ax = -x1+3*x2-3*x3+x4;
4514   immutable float ay = -y1+3*y2-3*y3+y4;
4515   immutable float bx = 3*x1-6*x2+3*x3;
4516   immutable float by = 3*y1-6*y2+3*y3;
4517   immutable float cx = -3*x1+3*x2;
4518   immutable float cy = -3*y1+3*y2;
4519 
4520   // Transform to forward difference basis (stepsize 1)
4521   float px = x1;
4522   float py = y1;
4523   float dx = ax+bx+cx;
4524   float dy = ay+by+cy;
4525   float ddx = 6*ax+2*bx;
4526   float ddy = 6*ay+2*by;
4527   float dddx = 6*ax;
4528   float dddy = 6*ay;
4529 
4530   //printf("dx: %f, dy: %f\n", dx, dy);
4531   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4532   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4533 
4534   int t = 0;
4535   int dt = AFD_ONE;
4536 
4537   immutable float tol = ctx.tessTol*4;
4538 
4539   while (t < AFD_ONE) {
4540     // Flatness measure.
4541     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4542 
4543     // printf("d: %f, th: %f\n", d, th);
4544 
4545     // Go to higher resolution if we're moving a lot or overshooting the end.
4546     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4547       // printf("up\n");
4548 
4549       // Apply L to the curve. Increase curve resolution.
4550       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4551       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4552       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4553       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4554       dddx = (1.0f/8.0f)*dddx;
4555       dddy = (1.0f/8.0f)*dddy;
4556 
4557       // Half the stepsize.
4558       dt >>= 1;
4559 
4560       // Recompute d
4561       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4562     }
4563 
4564     // Go to lower resolution if we're really flat
4565     // and we aren't going to overshoot the end.
4566     // XXX: tol/32 is just a guess for when we are too flat.
4567     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4568       // printf("down\n");
4569 
4570       // Apply L^(-1) to the curve. Decrease curve resolution.
4571       dx = 2*dx+ddx;
4572       dy = 2*dy+ddy;
4573       ddx = 4*ddx+4*dddx;
4574       ddy = 4*ddy+4*dddy;
4575       dddx = 8*dddx;
4576       dddy = 8*dddy;
4577 
4578       // Double the stepsize.
4579       dt <<= 1;
4580 
4581       // Recompute d
4582       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4583     }
4584 
4585     // Forward differencing.
4586     px += dx;
4587     py += dy;
4588     dx += ddx;
4589     dy += ddy;
4590     ddx += dddx;
4591     ddy += dddy;
4592 
4593     // Output a point.
4594     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4595 
4596     // Advance along the curve.
4597     t += dt;
4598 
4599     // Ensure we don't overshoot.
4600     assert(t <= AFD_ONE);
4601   }
4602 }
4603 
4604 
4605 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4606   import core.stdc.stdlib : realloc;
4607   import core.stdc.string : memcpy;
4608 
4609   NVGpathCache* cache = ctx.cache;
4610   if (cache.npaths == 0) return;
4611 
4612   NVGpath* path = nvg__lastPath(ctx);
4613   if (path is null) return;
4614 
4615   NVGstate* state = nvg__getState(ctx);
4616   if (!state.dasherActive) return;
4617 
4618   static NVGpoint* pts = null;
4619   static uint ptsCount = 0;
4620   static uint ptsSize = 0;
4621 
4622   if (path.count < 2) return; // just in case
4623 
4624   // copy path points (reserve one point for closed pathes)
4625   if (ptsSize < path.count+1) {
4626     ptsSize = cast(uint)(path.count+1);
4627     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4628     if (pts is null) assert(0, "NanoVega: out of memory");
4629   }
4630   ptsCount = cast(uint)path.count;
4631   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4632   // add closing point for closed pathes
4633   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4634     pts[ptsCount++] = pts[0];
4635   }
4636 
4637   // remove last path (with its points)
4638   --cache.npaths;
4639   cache.npoints -= path.count;
4640 
4641   // add stroked pathes
4642   const(float)* dashes = state.dashes.ptr;
4643   immutable uint dashCount = state.dashCount;
4644   float currDashStart = 0;
4645   uint currDashIdx = 0;
4646   immutable bool firstIsGap = state.firstDashIsGap;
4647 
4648   // calculate lengthes
4649   {
4650     NVGpoint* v1 = &pts[0];
4651     NVGpoint* v2 = &pts[1];
4652     foreach (immutable _; 0..ptsCount) {
4653       float dx = v2.x-v1.x;
4654       float dy = v2.y-v1.y;
4655       v1.len = nvg__normalize(&dx, &dy);
4656       v1 = v2++;
4657     }
4658   }
4659 
4660   void calcDashStart (float ds) {
4661     if (ds < 0) {
4662       ds = ds%state.totalDashLen;
4663       while (ds < 0) ds += state.totalDashLen;
4664     }
4665     currDashIdx = 0;
4666     currDashStart = 0;
4667     while (ds > 0) {
4668       if (ds > dashes[currDashIdx]) {
4669         ds -= dashes[currDashIdx];
4670         ++currDashIdx;
4671         currDashStart = 0;
4672         if (currDashIdx >= dashCount) currDashIdx = 0;
4673       } else {
4674         currDashStart = ds;
4675         ds = 0;
4676       }
4677     }
4678   }
4679 
4680   calcDashStart(state.dashStart);
4681 
4682   uint srcPointIdx = 1;
4683   const(NVGpoint)* v1 = &pts[0];
4684   const(NVGpoint)* v2 = &pts[1];
4685   float currRest = v1.len;
4686   nvg__addPath(ctx);
4687   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4688 
4689   void fixLastPoint () {
4690     auto lpt = nvg__lastPath(ctx);
4691     if (lpt !is null && lpt.count > 0) {
4692       // fix last point
4693       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4694       // fix first point
4695       NVGpathCache* cache = ctx.cache;
4696       cache.points[lpt.first].flags = PointFlag.Corner;
4697     }
4698   }
4699 
4700   for (;;) {
4701     immutable float dlen = dashes[currDashIdx];
4702     if (dlen == 0) {
4703       ++currDashIdx;
4704       if (currDashIdx >= dashCount) currDashIdx = 0;
4705       continue;
4706     }
4707     immutable float dashRest = dlen-currDashStart;
4708     if ((currDashIdx&1) != firstIsGap) {
4709       // this is "moveto" command, so create new path
4710       fixLastPoint();
4711       nvg__addPath(ctx);
4712     }
4713     if (currRest > dashRest) {
4714       currRest -= dashRest;
4715       ++currDashIdx;
4716       if (currDashIdx >= dashCount) currDashIdx = 0;
4717       currDashStart = 0;
4718       nvg__addPoint(ctx,
4719         v2.x-(v2.x-v1.x)*currRest/v1.len,
4720         v2.y-(v2.y-v1.y)*currRest/v1.len,
4721         PointFlag.Corner
4722       );
4723     } else {
4724       currDashStart += currRest;
4725       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4726       ++srcPointIdx;
4727       v1 = v2;
4728       currRest = v1.len;
4729       if (srcPointIdx >= ptsCount) break;
4730       v2 = &pts[srcPointIdx];
4731     }
4732   }
4733   fixLastPoint();
4734 }
4735 
4736 
4737 version(nanovg_bench_flatten) import iv.timer : Timer;
4738 
4739 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4740   version(nanovg_bench_flatten) {
4741     Timer timer;
4742     char[128] tmbuf;
4743     int bzcount;
4744   }
4745   NVGpathCache* cache = ctx.cache;
4746   NVGstate* state = nvg__getState(ctx);
4747 
4748   // check if we already did flattening
4749   static if (asStroke) {
4750     if (state.dashCount >= 2) {
4751       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4752       state.dasherActive = true;
4753       state.lastFlattenDashCount = state.dashCount;
4754     } else {
4755       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4756       state.dasherActive = false;
4757       state.lastFlattenDashCount = 0;
4758     }
4759   } else {
4760     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4761     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4762     state.dasherActive = false;
4763   }
4764 
4765   // clear path cache
4766   cache.npaths = 0;
4767   cache.npoints = 0;
4768 
4769   // flatten
4770   version(nanovg_bench_flatten) timer.restart();
4771   int i = 0;
4772   while (i < ctx.ncommands) {
4773     final switch (cast(Command)ctx.commands[i]) {
4774       case Command.MoveTo:
4775         //assert(i+3 <= ctx.ncommands);
4776         static if (asStroke) {
4777           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4778         }
4779         nvg__addPath(ctx);
4780         const p = &ctx.commands[i+1];
4781         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4782         i += 3;
4783         break;
4784       case Command.LineTo:
4785         //assert(i+3 <= ctx.ncommands);
4786         const p = &ctx.commands[i+1];
4787         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4788         i += 3;
4789         break;
4790       case Command.BezierTo:
4791         //assert(i+7 <= ctx.ncommands);
4792         const last = nvg__lastPoint(ctx);
4793         if (last !is null) {
4794           const cp1 = &ctx.commands[i+1];
4795           const cp2 = &ctx.commands[i+3];
4796           const p = &ctx.commands[i+5];
4797           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4798             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4799           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4800             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4801           } else {
4802             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4803           }
4804           version(nanovg_bench_flatten) ++bzcount;
4805         }
4806         i += 7;
4807         break;
4808       case Command.Close:
4809         //assert(i+1 <= ctx.ncommands);
4810         nvg__closePath(ctx);
4811         i += 1;
4812         break;
4813       case Command.Winding:
4814         //assert(i+2 <= ctx.ncommands);
4815         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4816         i += 2;
4817         break;
4818     }
4819   }
4820   static if (asStroke) {
4821     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4822   }
4823   version(nanovg_bench_flatten) {{
4824     timer.stop();
4825     auto xb = timer.toBuffer(tmbuf[]);
4826     import core.stdc.stdio : printf;
4827     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4828   }}
4829 
4830   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4831   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4832 
4833   // calculate the direction and length of line segments
4834   version(nanovg_bench_flatten) timer.restart();
4835   foreach (int j; 0..cache.npaths) {
4836     NVGpath* path = &cache.paths[j];
4837     NVGpoint* pts = &cache.points[path.first];
4838 
4839     // if the first and last points are the same, remove the last, mark as closed path
4840     NVGpoint* p0 = &pts[path.count-1];
4841     NVGpoint* p1 = &pts[0];
4842     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4843       --path.count;
4844       p0 = &pts[path.count-1];
4845       path.closed = true;
4846     }
4847 
4848     // enforce winding
4849     if (path.count > 2) {
4850       immutable float area = nvg__polyArea(pts, path.count);
4851       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4852       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4853     }
4854 
4855     foreach (immutable _; 0..path.count) {
4856       // calculate segment direction and length
4857       p0.dx = p1.x-p0.x;
4858       p0.dy = p1.y-p0.y;
4859       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4860       // update bounds
4861       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4862       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4863       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4864       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4865       // advance
4866       p0 = p1++;
4867     }
4868   }
4869   version(nanovg_bench_flatten) {{
4870     timer.stop();
4871     auto xb = timer.toBuffer(tmbuf[]);
4872     import core.stdc.stdio : printf;
4873     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4874   }}
4875 }
4876 
4877 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4878   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4879   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4880 }
4881 
4882 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4883   if (bevel) {
4884     *x0 = p1.x+p0.dy*w;
4885     *y0 = p1.y-p0.dx*w;
4886     *x1 = p1.x+p1.dy*w;
4887     *y1 = p1.y-p1.dx*w;
4888   } else {
4889     *x0 = p1.x+p1.dmx*w;
4890     *y0 = p1.y+p1.dmy*w;
4891     *x1 = p1.x+p1.dmx*w;
4892     *y1 = p1.y+p1.dmy*w;
4893   }
4894 }
4895 
4896 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4897   float dlx0 = p0.dy;
4898   float dly0 = -p0.dx;
4899   float dlx1 = p1.dy;
4900   float dly1 = -p1.dx;
4901   //NVG_NOTUSED(fringe);
4902 
4903   if (p1.flags&PointFlag.Left) {
4904     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4905     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4906     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4907     float a1 = nvg__atan2f(-dly1, -dlx1);
4908     if (a1 > a0) a1 -= NVG_PI*2;
4909 
4910     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4911     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4912 
4913     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4914     for (int i = 0; i < n; ++i) {
4915       float u = i/cast(float)(n-1);
4916       float a = a0+u*(a1-a0);
4917       float rx = p1.x+nvg__cosf(a)*rw;
4918       float ry = p1.y+nvg__sinf(a)*rw;
4919       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4920       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4921     }
4922 
4923     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4924     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4925 
4926   } else {
4927     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4928     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4929     immutable float a0 = nvg__atan2f(dly0, dlx0);
4930     float a1 = nvg__atan2f(dly1, dlx1);
4931     if (a1 < a0) a1 += NVG_PI*2;
4932 
4933     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4934     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4935 
4936     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4937     for (int i = 0; i < n; i++) {
4938       float u = i/cast(float)(n-1);
4939       float a = a0+u*(a1-a0);
4940       float lx = p1.x+nvg__cosf(a)*lw;
4941       float ly = p1.y+nvg__sinf(a)*lw;
4942       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4943       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4944     }
4945 
4946     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4947     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4948 
4949   }
4950   return dst;
4951 }
4952 
4953 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4954   float rx0, ry0, rx1, ry1;
4955   float lx0, ly0, lx1, ly1;
4956   float dlx0 = p0.dy;
4957   float dly0 = -p0.dx;
4958   float dlx1 = p1.dy;
4959   float dly1 = -p1.dx;
4960   //NVG_NOTUSED(fringe);
4961 
4962   if (p1.flags&PointFlag.Left) {
4963     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4964 
4965     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4966     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4967 
4968     if (p1.flags&PointFlag.Bevel) {
4969       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4970       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4971 
4972       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4973       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4974     } else {
4975       rx0 = p1.x-p1.dmx*rw;
4976       ry0 = p1.y-p1.dmy*rw;
4977 
4978       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4979       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4980 
4981       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4982       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4983 
4984       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4985       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4986     }
4987 
4988     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4989     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4990 
4991   } else {
4992     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4993 
4994     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4995     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4996 
4997     if (p1.flags&PointFlag.Bevel) {
4998       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4999       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
5000 
5001       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5002       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5003     } else {
5004       lx0 = p1.x+p1.dmx*lw;
5005       ly0 = p1.y+p1.dmy*lw;
5006 
5007       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5008       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5009 
5010       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5011       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5012 
5013       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5014       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5015     }
5016 
5017     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5018     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5019   }
5020 
5021   return dst;
5022 }
5023 
5024 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5025   immutable float px = p.x-dx*d;
5026   immutable float py = p.y-dy*d;
5027   immutable float dlx = dy;
5028   immutable float dly = -dx;
5029   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
5030   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
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   return dst;
5034 }
5035 
5036 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5037   immutable float px = p.x+dx*d;
5038   immutable float py = p.y+dy*d;
5039   immutable float dlx = dy;
5040   immutable float dly = -dx;
5041   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5042   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5043   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5044   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5045   return dst;
5046 }
5047 
5048 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5049   immutable float px = p.x;
5050   immutable float py = p.y;
5051   immutable float dlx = dy;
5052   immutable float dly = -dx;
5053   //NVG_NOTUSED(aa);
5054   immutable float ncpf = cast(float)(ncap-1);
5055   foreach (int i; 0..ncap) {
5056     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5057     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5058     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5059     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5060   }
5061   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5062   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5063   return dst;
5064 }
5065 
5066 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5067   immutable float px = p.x;
5068   immutable float py = p.y;
5069   immutable float dlx = dy;
5070   immutable float dly = -dx;
5071   //NVG_NOTUSED(aa);
5072   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5073   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5074   immutable float ncpf = cast(float)(ncap-1);
5075   foreach (int i; 0..ncap) {
5076     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5077     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5078     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5079     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5080   }
5081   return dst;
5082 }
5083 
5084 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5085   NVGpathCache* cache = ctx.cache;
5086   float iw = 0.0f;
5087 
5088   if (w > 0.0f) iw = 1.0f/w;
5089 
5090   // Calculate which joins needs extra vertices to append, and gather vertex count.
5091   foreach (int i; 0..cache.npaths) {
5092     NVGpath* path = &cache.paths[i];
5093     NVGpoint* pts = &cache.points[path.first];
5094     NVGpoint* p0 = &pts[path.count-1];
5095     NVGpoint* p1 = &pts[0];
5096     int nleft = 0;
5097 
5098     path.nbevel = 0;
5099 
5100     foreach (int j; 0..path.count) {
5101       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5102       immutable float dlx0 = p0.dy;
5103       immutable float dly0 = -p0.dx;
5104       immutable float dlx1 = p1.dy;
5105       immutable float dly1 = -p1.dx;
5106       // Calculate extrusions
5107       p1.dmx = (dlx0+dlx1)*0.5f;
5108       p1.dmy = (dly0+dly1)*0.5f;
5109       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5110       if (dmr2 > 0.000001f) {
5111         float scale = 1.0f/dmr2;
5112         if (scale > 600.0f) scale = 600.0f;
5113         p1.dmx *= scale;
5114         p1.dmy *= scale;
5115       }
5116 
5117       // Clear flags, but keep the corner.
5118       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5119 
5120       // Keep track of left turns.
5121       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5122       if (cross > 0.0f) {
5123         nleft++;
5124         p1.flags |= PointFlag.Left;
5125       }
5126 
5127       // Calculate if we should use bevel or miter for inner join.
5128       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5129       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5130 
5131       // Check to see if the corner needs to be beveled.
5132       if (p1.flags&PointFlag.Corner) {
5133         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5134           p1.flags |= PointFlag.Bevel;
5135         }
5136       }
5137 
5138       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5139 
5140       p0 = p1++;
5141     }
5142 
5143     path.convex = (nleft == path.count);
5144   }
5145 }
5146 
5147 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5148   NVGpathCache* cache = ctx.cache;
5149   immutable float aa = ctx.fringeWidth;
5150   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5151 
5152   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5153 
5154   // Calculate max vertex usage.
5155   int cverts = 0;
5156   foreach (int i; 0..cache.npaths) {
5157     NVGpath* path = &cache.paths[i];
5158     immutable bool loop = path.closed;
5159     if (lineJoin == NVGLineCap.Round) {
5160       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5161     } else {
5162       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5163     }
5164     if (!loop) {
5165       // space for caps
5166       if (lineCap == NVGLineCap.Round) {
5167         cverts += (ncap*2+2)*2;
5168       } else {
5169         cverts += (3+3)*2;
5170       }
5171     }
5172   }
5173 
5174   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5175   if (verts is null) return;
5176 
5177   foreach (int i; 0..cache.npaths) {
5178     NVGpath* path = &cache.paths[i];
5179     NVGpoint* pts = &cache.points[path.first];
5180     NVGpoint* p0;
5181     NVGpoint* p1;
5182     int s, e;
5183 
5184     path.fill = null;
5185     path.nfill = 0;
5186 
5187     // Calculate fringe or stroke
5188     immutable bool loop = path.closed;
5189     NVGVertex* dst = verts;
5190     path.stroke = dst;
5191 
5192     if (loop) {
5193       // Looping
5194       p0 = &pts[path.count-1];
5195       p1 = &pts[0];
5196       s = 0;
5197       e = path.count;
5198     } else {
5199       // Add cap
5200       p0 = &pts[0];
5201       p1 = &pts[1];
5202       s = 1;
5203       e = path.count-1;
5204     }
5205 
5206     if (!loop) {
5207       // Add cap
5208       float dx = p1.x-p0.x;
5209       float dy = p1.y-p0.y;
5210       nvg__normalize(&dx, &dy);
5211            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5212       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5213       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5214     }
5215 
5216     foreach (int j; s..e) {
5217       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5218         if (lineJoin == NVGLineCap.Round) {
5219           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5220         } else {
5221           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5222         }
5223       } else {
5224         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5225         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5226       }
5227       p0 = p1++;
5228     }
5229 
5230     if (loop) {
5231       // Loop it
5232       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5233       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5234     } else {
5235       // Add cap
5236       float dx = p1.x-p0.x;
5237       float dy = p1.y-p0.y;
5238       nvg__normalize(&dx, &dy);
5239            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5240       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5241       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5242     }
5243 
5244     path.nstroke = cast(int)(dst-verts);
5245 
5246     verts = dst;
5247   }
5248 }
5249 
5250 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5251   NVGpathCache* cache = ctx.cache;
5252   immutable float aa = ctx.fringeWidth;
5253   bool fringe = (w > 0.0f);
5254 
5255   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5256 
5257   // Calculate max vertex usage.
5258   int cverts = 0;
5259   foreach (int i; 0..cache.npaths) {
5260     NVGpath* path = &cache.paths[i];
5261     cverts += path.count+path.nbevel+1;
5262     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5263   }
5264 
5265   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5266   if (verts is null) return;
5267 
5268   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5269 
5270   foreach (int i; 0..cache.npaths) {
5271     NVGpath* path = &cache.paths[i];
5272     NVGpoint* pts = &cache.points[path.first];
5273 
5274     // Calculate shape vertices.
5275     immutable float woff = 0.5f*aa;
5276     NVGVertex* dst = verts;
5277     path.fill = dst;
5278 
5279     if (fringe) {
5280       // Looping
5281       NVGpoint* p0 = &pts[path.count-1];
5282       NVGpoint* p1 = &pts[0];
5283       foreach (int j; 0..path.count) {
5284         if (p1.flags&PointFlag.Bevel) {
5285           immutable float dlx0 = p0.dy;
5286           immutable float dly0 = -p0.dx;
5287           immutable float dlx1 = p1.dy;
5288           immutable float dly1 = -p1.dx;
5289           if (p1.flags&PointFlag.Left) {
5290             immutable float lx = p1.x+p1.dmx*woff;
5291             immutable float ly = p1.y+p1.dmy*woff;
5292             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5293           } else {
5294             immutable float lx0 = p1.x+dlx0*woff;
5295             immutable float ly0 = p1.y+dly0*woff;
5296             immutable float lx1 = p1.x+dlx1*woff;
5297             immutable float ly1 = p1.y+dly1*woff;
5298             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5299             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5300           }
5301         } else {
5302           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5303         }
5304         p0 = p1++;
5305       }
5306     } else {
5307       foreach (int j; 0..path.count) {
5308         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5309         ++dst;
5310       }
5311     }
5312 
5313     path.nfill = cast(int)(dst-verts);
5314     verts = dst;
5315 
5316     // Calculate fringe
5317     if (fringe) {
5318       float lw = w+woff;
5319       immutable float rw = w-woff;
5320       float lu = 0;
5321       immutable float ru = 1;
5322       dst = verts;
5323       path.stroke = dst;
5324 
5325       // Create only half a fringe for convex shapes so that
5326       // the shape can be rendered without stenciling.
5327       if (convex) {
5328         lw = woff; // This should generate the same vertex as fill inset above.
5329         lu = 0.5f; // Set outline fade at middle.
5330       }
5331 
5332       // Looping
5333       NVGpoint* p0 = &pts[path.count-1];
5334       NVGpoint* p1 = &pts[0];
5335 
5336       foreach (int j; 0..path.count) {
5337         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5338           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5339         } else {
5340           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5341           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5342         }
5343         p0 = p1++;
5344       }
5345 
5346       // Loop it
5347       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5348       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5349 
5350       path.nstroke = cast(int)(dst-verts);
5351       verts = dst;
5352     } else {
5353       path.stroke = null;
5354       path.nstroke = 0;
5355     }
5356   }
5357 }
5358 
5359 
5360 // ////////////////////////////////////////////////////////////////////////// //
5361 // Paths
5362 
5363 /// Clears the current path and sub-paths.
5364 /// Group: paths
5365 @scriptable
5366 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5367   ctx.ncommands = 0;
5368   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5369   nvg__clearPathCache(ctx);
5370 }
5371 
5372 public alias newPath = beginPath; /// Ditto.
5373 
5374 /// Starts new sub-path with specified point as first point.
5375 /// Group: paths
5376 @scriptable
5377 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5378   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5379 }
5380 
5381 /// Starts new sub-path with specified point as first point.
5382 /// Arguments: [x, y]*
5383 /// Group: paths
5384 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5385   enum ArgC = 2;
5386   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5387   if (args.length < ArgC) return;
5388   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5389 }
5390 
5391 /// Adds line segment from the last point in the path to the specified point.
5392 /// Group: paths
5393 @scriptable
5394 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5395   nvg__appendCommands(ctx, Command.LineTo, x, y);
5396 }
5397 
5398 /// Adds line segment from the last point in the path to the specified point.
5399 /// Arguments: [x, y]*
5400 /// Group: paths
5401 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5402   enum ArgC = 2;
5403   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5404   if (args.length < ArgC) return;
5405   foreach (immutable idx; 0..args.length/ArgC) {
5406     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5407   }
5408 }
5409 
5410 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5411 /// Group: paths
5412 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 {
5413   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5414 }
5415 
5416 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5417 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5418 /// Group: paths
5419 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5420   enum ArgC = 6;
5421   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5422   if (args.length < ArgC) return;
5423   foreach (immutable idx; 0..args.length/ArgC) {
5424     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5425   }
5426 }
5427 
5428 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5429 /// Group: paths
5430 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5431   immutable float x0 = ctx.commandx;
5432   immutable float y0 = ctx.commandy;
5433   nvg__appendCommands(ctx,
5434     Command.BezierTo,
5435     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5436     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5437     x, y,
5438   );
5439 }
5440 
5441 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5442 /// Arguments: [cx, cy, x, y]*
5443 /// Group: paths
5444 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5445   enum ArgC = 4;
5446   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5447   if (args.length < ArgC) return;
5448   const(float)* aptr = args.ptr;
5449   foreach (immutable idx; 0..args.length/ArgC) {
5450     immutable float x0 = ctx.commandx;
5451     immutable float y0 = ctx.commandy;
5452     immutable float cx = *aptr++;
5453     immutable float cy = *aptr++;
5454     immutable float x = *aptr++;
5455     immutable float y = *aptr++;
5456     nvg__appendCommands(ctx,
5457       Command.BezierTo,
5458       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5459       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5460       x, y,
5461     );
5462   }
5463 }
5464 
5465 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5466 /// Group: paths
5467 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5468   if (ctx.ncommands == 0) return;
5469 
5470   immutable float x0 = ctx.commandx;
5471   immutable float y0 = ctx.commandy;
5472 
5473   // handle degenerate cases
5474   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5475       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5476       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5477       radius < ctx.distTol)
5478   {
5479     ctx.lineTo(x1, y1);
5480     return;
5481   }
5482 
5483   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5484   float dx0 = x0-x1;
5485   float dy0 = y0-y1;
5486   float dx1 = x2-x1;
5487   float dy1 = y2-y1;
5488   nvg__normalize(&dx0, &dy0);
5489   nvg__normalize(&dx1, &dy1);
5490   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5491   immutable float d = radius/nvg__tanf(a/2.0f);
5492 
5493   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5494 
5495   if (d > 10000.0f) {
5496     ctx.lineTo(x1, y1);
5497     return;
5498   }
5499 
5500   float cx = void, cy = void, a0 = void, a1 = void;
5501   NVGWinding dir;
5502   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5503     cx = x1+dx0*d+dy0*radius;
5504     cy = y1+dy0*d+-dx0*radius;
5505     a0 = nvg__atan2f(dx0, -dy0);
5506     a1 = nvg__atan2f(-dx1, dy1);
5507     dir = NVGWinding.CW;
5508     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5509   } else {
5510     cx = x1+dx0*d+-dy0*radius;
5511     cy = y1+dy0*d+dx0*radius;
5512     a0 = nvg__atan2f(-dx0, dy0);
5513     a1 = nvg__atan2f(dx1, -dy1);
5514     dir = NVGWinding.CCW;
5515     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5516   }
5517 
5518   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5519 }
5520 
5521 
5522 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5523 /// Arguments: [x1, y1, x2, y2, radius]*
5524 /// Group: paths
5525 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5526   enum ArgC = 5;
5527   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5528   if (args.length < ArgC) return;
5529   if (ctx.ncommands == 0) return;
5530   const(float)* aptr = args.ptr;
5531   foreach (immutable idx; 0..args.length/ArgC) {
5532     immutable float x0 = ctx.commandx;
5533     immutable float y0 = ctx.commandy;
5534     immutable float x1 = *aptr++;
5535     immutable float y1 = *aptr++;
5536     immutable float x2 = *aptr++;
5537     immutable float y2 = *aptr++;
5538     immutable float radius = *aptr++;
5539     ctx.arcTo(x1, y1, x2, y2, radius);
5540   }
5541 }
5542 
5543 /// Closes current sub-path with a line segment.
5544 /// Group: paths
5545 @scriptable
5546 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5547   nvg__appendCommands(ctx, Command.Close);
5548 }
5549 
5550 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5551 /// Group: paths
5552 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5553   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5554 }
5555 
5556 /// Ditto.
5557 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5558   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5559 }
5560 
5561 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5562  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5563  * Angles are specified in radians.
5564  *
5565  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5566  *
5567  * Group: paths
5568  */
5569 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 {
5570   static assert(mode == "original" || mode == "move" || mode == "line");
5571 
5572   float[3+5*7+100] vals = void;
5573   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5574   static if (mode == "original") {
5575     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5576   } else static if (mode == "move") {
5577     enum move = Command.MoveTo;
5578   } else static if (mode == "line") {
5579     enum move = Command.LineTo;
5580   } else {
5581     static assert(0, "wtf?!");
5582   }
5583 
5584   // Clamp angles
5585   float da = a1-a0;
5586   if (dir == NVGWinding.CW) {
5587     if (nvg__absf(da) >= NVG_PI*2) {
5588       da = NVG_PI*2;
5589     } else {
5590       while (da < 0.0f) da += NVG_PI*2;
5591     }
5592   } else {
5593     if (nvg__absf(da) >= NVG_PI*2) {
5594       da = -NVG_PI*2;
5595     } else {
5596       while (da > 0.0f) da -= NVG_PI*2;
5597     }
5598   }
5599 
5600   // Split arc into max 90 degree segments.
5601   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5602   immutable float hda = (da/cast(float)ndivs)/2.0f;
5603   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5604 
5605   if (dir == NVGWinding.CCW) kappa = -kappa;
5606 
5607   int nvals = 0;
5608   float px = 0, py = 0, ptanx = 0, ptany = 0;
5609   foreach (int i; 0..ndivs+1) {
5610     immutable float a = a0+da*(i/cast(float)ndivs);
5611     immutable float dx = nvg__cosf(a);
5612     immutable float dy = nvg__sinf(a);
5613     immutable float x = cx+dx*r;
5614     immutable float y = cy+dy*r;
5615     immutable float tanx = -dy*r*kappa;
5616     immutable float tany = dx*r*kappa;
5617 
5618     if (i == 0) {
5619       if (vals.length-nvals < 3) {
5620         // flush
5621         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5622         nvals = 0;
5623       }
5624       vals.ptr[nvals++] = cast(float)move;
5625       vals.ptr[nvals++] = x;
5626       vals.ptr[nvals++] = y;
5627     } else {
5628       if (vals.length-nvals < 7) {
5629         // flush
5630         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5631         nvals = 0;
5632       }
5633       vals.ptr[nvals++] = Command.BezierTo;
5634       vals.ptr[nvals++] = px+ptanx;
5635       vals.ptr[nvals++] = py+ptany;
5636       vals.ptr[nvals++] = x-tanx;
5637       vals.ptr[nvals++] = y-tany;
5638       vals.ptr[nvals++] = x;
5639       vals.ptr[nvals++] = y;
5640     }
5641     px = x;
5642     py = y;
5643     ptanx = tanx;
5644     ptany = tany;
5645   }
5646 
5647   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5648 }
5649 
5650 
5651 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5652  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5653  * Angles are specified in radians.
5654  *
5655  * Arguments: [cx, cy, r, a0, a1]*
5656  *
5657  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5658  *
5659  * Group: paths
5660  */
5661 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5662   static assert(mode == "original" || mode == "move" || mode == "line");
5663   enum ArgC = 5;
5664   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5665   if (args.length < ArgC) return;
5666   const(float)* aptr = args.ptr;
5667   foreach (immutable idx; 0..args.length/ArgC) {
5668     immutable cx = *aptr++;
5669     immutable cy = *aptr++;
5670     immutable r = *aptr++;
5671     immutable a0 = *aptr++;
5672     immutable a1 = *aptr++;
5673     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5674   }
5675 }
5676 
5677 /// Creates new rectangle shaped sub-path.
5678 /// Group: paths
5679 @scriptable
5680 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5681   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5682     Command.MoveTo, x, y,
5683     Command.LineTo, x, y+h,
5684     Command.LineTo, x+w, y+h,
5685     Command.LineTo, x+w, y,
5686     Command.Close,
5687   );
5688 }
5689 
5690 /// Creates new rectangle shaped sub-path.
5691 /// Arguments: [x, y, w, h]*
5692 /// Group: paths
5693 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5694   enum ArgC = 4;
5695   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5696   if (args.length < ArgC) return;
5697   const(float)* aptr = args.ptr;
5698   foreach (immutable idx; 0..args.length/ArgC) {
5699     immutable x = *aptr++;
5700     immutable y = *aptr++;
5701     immutable w = *aptr++;
5702     immutable h = *aptr++;
5703     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5704       Command.MoveTo, x, y,
5705       Command.LineTo, x, y+h,
5706       Command.LineTo, x+w, y+h,
5707       Command.LineTo, x+w, y,
5708       Command.Close,
5709     );
5710   }
5711 }
5712 
5713 /// Creates new rounded rectangle shaped sub-path.
5714 /// Group: paths
5715 @scriptable
5716 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5717   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5718 }
5719 
5720 /// Creates new rounded rectangle shaped sub-path.
5721 /// Arguments: [x, y, w, h, radius]*
5722 /// Group: paths
5723 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5724   enum ArgC = 5;
5725   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5726   if (args.length < ArgC) return;
5727   const(float)* aptr = args.ptr;
5728   foreach (immutable idx; 0..args.length/ArgC) {
5729     immutable x = *aptr++;
5730     immutable y = *aptr++;
5731     immutable w = *aptr++;
5732     immutable h = *aptr++;
5733     immutable r = *aptr++;
5734     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5735   }
5736 }
5737 
5738 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5739 /// Group: paths
5740 @scriptable
5741 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 {
5742   if (rw < 0.1f || rh < 0.1f) {
5743     rect(ctx, x, y, w, h);
5744   } else {
5745     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5746       Command.MoveTo, x+rw, y,
5747       Command.LineTo, x+w-rw, y,
5748       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5749       Command.LineTo, x+w, y+h-rh,
5750       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5751       Command.LineTo, x+rw, y+h,
5752       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5753       Command.LineTo, x, y+rh,
5754       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5755       Command.Close,
5756     );
5757   }
5758 }
5759 
5760 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5761 /// Arguments: [x, y, w, h, rw, rh]*
5762 /// Group: paths
5763 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5764   enum ArgC = 6;
5765   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5766   if (args.length < ArgC) return;
5767   const(float)* aptr = args.ptr;
5768   foreach (immutable idx; 0..args.length/ArgC) {
5769     immutable x = *aptr++;
5770     immutable y = *aptr++;
5771     immutable w = *aptr++;
5772     immutable h = *aptr++;
5773     immutable rw = *aptr++;
5774     immutable rh = *aptr++;
5775     if (rw < 0.1f || rh < 0.1f) {
5776       rect(ctx, x, y, w, h);
5777     } else {
5778       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5779         Command.MoveTo, x+rw, y,
5780         Command.LineTo, x+w-rw, y,
5781         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5782         Command.LineTo, x+w, y+h-rh,
5783         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5784         Command.LineTo, x+rw, y+h,
5785         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5786         Command.LineTo, x, y+rh,
5787         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5788         Command.Close,
5789       );
5790     }
5791   }
5792 }
5793 
5794 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5795 /// Group: paths
5796 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 {
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 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5822 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5823 /// Group: paths
5824 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5825   enum ArgC = 8;
5826   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5827   if (args.length < ArgC) return;
5828   const(float)* aptr = args.ptr;
5829   foreach (immutable idx; 0..args.length/ArgC) {
5830     immutable x = *aptr++;
5831     immutable y = *aptr++;
5832     immutable w = *aptr++;
5833     immutable h = *aptr++;
5834     immutable radTopLeft = *aptr++;
5835     immutable radTopRight = *aptr++;
5836     immutable radBottomRight = *aptr++;
5837     immutable radBottomLeft = *aptr++;
5838     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5839       ctx.rect(x, y, w, h);
5840     } else {
5841       immutable float halfw = nvg__absf(w)*0.5f;
5842       immutable float halfh = nvg__absf(h)*0.5f;
5843       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5844       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5845       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5846       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5847       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5848         Command.MoveTo, x, y+ryTL,
5849         Command.LineTo, x, y+h-ryBL,
5850         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5851         Command.LineTo, x+w-rxBR, y+h,
5852         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5853         Command.LineTo, x+w, y+ryTR,
5854         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5855         Command.LineTo, x+rxTL, y,
5856         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5857         Command.Close,
5858       );
5859     }
5860   }
5861 }
5862 
5863 /// Creates new ellipse shaped sub-path.
5864 /// Group: paths
5865 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5866   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5867     Command.MoveTo, cx-rx, cy,
5868     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5869     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5870     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5871     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5872     Command.Close,
5873   );
5874 }
5875 
5876 /// Creates new ellipse shaped sub-path.
5877 /// Arguments: [cx, cy, rx, ry]*
5878 /// Group: paths
5879 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5880   enum ArgC = 4;
5881   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5882   if (args.length < ArgC) return;
5883   const(float)* aptr = args.ptr;
5884   foreach (immutable idx; 0..args.length/ArgC) {
5885     immutable cx = *aptr++;
5886     immutable cy = *aptr++;
5887     immutable rx = *aptr++;
5888     immutable ry = *aptr++;
5889     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5890       Command.MoveTo, cx-rx, cy,
5891       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5892       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5893       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5894       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5895       Command.Close,
5896     );
5897   }
5898 }
5899 
5900 /// Creates new circle shaped sub-path.
5901 /// Group: paths
5902 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5903   ctx.ellipse(cx, cy, r, r);
5904 }
5905 
5906 /// Creates new circle shaped sub-path.
5907 /// Arguments: [cx, cy, r]*
5908 /// Group: paths
5909 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5910   enum ArgC = 3;
5911   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5912   if (args.length < ArgC) return;
5913   const(float)* aptr = args.ptr;
5914   foreach (immutable idx; 0..args.length/ArgC) {
5915     immutable cx = *aptr++;
5916     immutable cy = *aptr++;
5917     immutable r = *aptr++;
5918     ctx.ellipse(cx, cy, r, r);
5919   }
5920 }
5921 
5922 // Debug function to dump cached path data.
5923 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5924   import core.stdc.stdio : printf;
5925   const(NVGpath)* path;
5926   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5927   for (int i = 0; i < ctx.cache.npaths; ++i) {
5928     path = &ctx.cache.paths[i];
5929     printf("-Path %d\n", i);
5930     if (path.nfill) {
5931       printf("-fill: %d\n", path.nfill);
5932       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5933     }
5934     if (path.nstroke) {
5935       printf("-stroke: %d\n", path.nstroke);
5936       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5937     }
5938   }
5939 }
5940 
5941 // Flatten path, prepare it for fill operation.
5942 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5943   NVGpathCache* cache = ctx.cache;
5944   NVGstate* state = nvg__getState(ctx);
5945 
5946   nvg__flattenPaths!false(ctx);
5947 
5948   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5949     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5950   } else {
5951     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5952   }
5953 
5954   cache.evenOddMode = state.evenOddMode;
5955   cache.fringeWidth = ctx.fringeWidth;
5956   cache.fillReady = true;
5957   cache.strokeReady = false;
5958   cache.clipmode = NVGClipMode.None;
5959 }
5960 
5961 // Flatten path, prepare it for stroke operation.
5962 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5963   NVGstate* state = nvg__getState(ctx);
5964   NVGpathCache* cache = ctx.cache;
5965 
5966   nvg__flattenPaths!true(ctx);
5967 
5968   immutable float scale = nvg__getAverageScale(state.xform);
5969   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5970 
5971   if (strokeWidth < ctx.fringeWidth) {
5972     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5973     // Since coverage is area, scale by alpha*alpha.
5974     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5975     cache.strokeAlphaMul = alpha*alpha;
5976     strokeWidth = ctx.fringeWidth;
5977   } else {
5978     cache.strokeAlphaMul = 1.0f;
5979   }
5980   cache.strokeWidth = strokeWidth;
5981 
5982   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5983     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5984   } else {
5985     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5986   }
5987 
5988   cache.fringeWidth = ctx.fringeWidth;
5989   cache.fillReady = false;
5990   cache.strokeReady = true;
5991   cache.clipmode = NVGClipMode.None;
5992 }
5993 
5994 /// Fills the current path with current fill style.
5995 /// Group: paths
5996 @scriptable
5997 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5998   NVGstate* state = nvg__getState(ctx);
5999 
6000   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6001     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6002     ctx.currFillHitId = ctx.pathPickId;
6003   }
6004 
6005   nvg__prepareFill(ctx);
6006 
6007   // apply global alpha
6008   NVGPaint fillPaint = state.fill;
6009   fillPaint.innerColor.a *= state.alpha;
6010   fillPaint.middleColor.a *= state.alpha;
6011   fillPaint.outerColor.a *= state.alpha;
6012 
6013   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6014 
6015   if (ctx.recblockdraw) return;
6016 
6017   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);
6018 
6019   // count triangles
6020   foreach (int i; 0..ctx.cache.npaths) {
6021     NVGpath* path = &ctx.cache.paths[i];
6022     ctx.fillTriCount += path.nfill-2;
6023     ctx.fillTriCount += path.nstroke-2;
6024     ctx.drawCallCount += 2;
6025   }
6026 }
6027 
6028 /// Fills the current path with current stroke style.
6029 /// Group: paths
6030 @scriptable
6031 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
6032   NVGstate* state = nvg__getState(ctx);
6033 
6034   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6035     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6036     ctx.currStrokeHitId = ctx.pathPickId;
6037   }
6038 
6039   nvg__prepareStroke(ctx);
6040 
6041   NVGpathCache* cache = ctx.cache;
6042 
6043   NVGPaint strokePaint = state.stroke;
6044   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6045   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6046   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6047 
6048   // apply global alpha
6049   strokePaint.innerColor.a *= state.alpha;
6050   strokePaint.middleColor.a *= state.alpha;
6051   strokePaint.outerColor.a *= state.alpha;
6052 
6053   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6054 
6055   if (ctx.recblockdraw) return;
6056 
6057   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6058 
6059   // count triangles
6060   foreach (int i; 0..ctx.cache.npaths) {
6061     NVGpath* path = &ctx.cache.paths[i];
6062     ctx.strokeTriCount += path.nstroke-2;
6063     ++ctx.drawCallCount;
6064   }
6065 }
6066 
6067 /// Sets current path as clipping region.
6068 /// Group: clipping
6069 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6070   NVGstate* state = nvg__getState(ctx);
6071 
6072   if (aclipmode == NVGClipMode.None) return;
6073   if (ctx.recblockdraw) return; //???
6074 
6075   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6076 
6077   /*
6078   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6079     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6080     ctx.currFillHitId = ctx.pathPickId;
6081   }
6082   */
6083 
6084   nvg__prepareFill(ctx);
6085 
6086   // apply global alpha
6087   NVGPaint fillPaint = state.fill;
6088   fillPaint.innerColor.a *= state.alpha;
6089   fillPaint.middleColor.a *= state.alpha;
6090   fillPaint.outerColor.a *= state.alpha;
6091 
6092   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6093 
6094   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);
6095 
6096   // count triangles
6097   foreach (int i; 0..ctx.cache.npaths) {
6098     NVGpath* path = &ctx.cache.paths[i];
6099     ctx.fillTriCount += path.nfill-2;
6100     ctx.fillTriCount += path.nstroke-2;
6101     ctx.drawCallCount += 2;
6102   }
6103 }
6104 
6105 /// Sets current path as clipping region.
6106 /// Group: clipping
6107 public alias clipFill = clip;
6108 
6109 /// Sets current path' stroke as clipping region.
6110 /// Group: clipping
6111 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6112   NVGstate* state = nvg__getState(ctx);
6113 
6114   if (aclipmode == NVGClipMode.None) return;
6115   if (ctx.recblockdraw) return; //???
6116 
6117   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6118 
6119   /*
6120   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6121     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6122     ctx.currStrokeHitId = ctx.pathPickId;
6123   }
6124   */
6125 
6126   nvg__prepareStroke(ctx);
6127 
6128   NVGpathCache* cache = ctx.cache;
6129 
6130   NVGPaint strokePaint = state.stroke;
6131   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6132   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6133   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6134 
6135   // apply global alpha
6136   strokePaint.innerColor.a *= state.alpha;
6137   strokePaint.middleColor.a *= state.alpha;
6138   strokePaint.outerColor.a *= state.alpha;
6139 
6140   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6141 
6142   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6143 
6144   // count triangles
6145   foreach (int i; 0..ctx.cache.npaths) {
6146     NVGpath* path = &ctx.cache.paths[i];
6147     ctx.strokeTriCount += path.nstroke-2;
6148     ++ctx.drawCallCount;
6149   }
6150 }
6151 
6152 
6153 // ////////////////////////////////////////////////////////////////////////// //
6154 // Picking API
6155 
6156 // most of the code is by Michael Wynne <mike@mikesspace.net>
6157 // https://github.com/memononen/nanovg/pull/230
6158 // https://github.com/MikeWW/nanovg
6159 
6160 /// Pick type query. Used in [hitTest] and [hitTestAll].
6161 /// Group: picking_api
6162 public enum NVGPickKind : ubyte {
6163   Fill = 0x01, ///
6164   Stroke = 0x02, ///
6165   All = 0x03, ///
6166 }
6167 
6168 /// Marks the fill of the current path as pickable with the specified id.
6169 /// Note that you can create and mark path without rasterizing it.
6170 /// Group: picking_api
6171 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6172   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6173   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6174   nvg__pickSceneInsert(ps, pp);
6175 }
6176 
6177 public alias currFillPickId = currFillHitId; /// Ditto.
6178 
6179 /// Marks the stroke of the current path as pickable with the specified id.
6180 /// Note that you can create and mark path without rasterizing it.
6181 /// Group: picking_api
6182 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6183   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6184   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6185   nvg__pickSceneInsert(ps, pp);
6186 }
6187 
6188 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6189 
6190 // Marks the saved path set (fill) as pickable with the specified id.
6191 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6192 // Group: picking_api
6193 /+
6194 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6195   if (svp is null) return;
6196   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6197   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6198     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6199     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6200     nvg__pickSceneInsert(ps, pp);
6201   }
6202 }
6203 +/
6204 
6205 // Marks the saved path set (stroke) as pickable with the specified id.
6206 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6207 // Group: picking_api
6208 /+
6209 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6210   if (svp is null) return;
6211   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6212   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6213     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6214     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6215     nvg__pickSceneInsert(ps, pp);
6216   }
6217 }
6218 +/
6219 
6220 private template IsGoodHitTestDG(DG) {
6221   enum IsGoodHitTestDG =
6222     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6223     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6224 }
6225 
6226 private template IsGoodHitTestInternalDG(DG) {
6227   enum IsGoodHitTestInternalDG =
6228     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6229     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6230 }
6231 
6232 /// Call delegate [dg] for each path under the specified position (in no particular order).
6233 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6234 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6235 /// Group: picking_api
6236 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) {
6237   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6238 
6239   NVGpickScene* ps = ctx.pickScene;
6240   int levelwidth = 1<<(ps.nlevels-1);
6241   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6242   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6243   int npicked = 0;
6244 
6245   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6246   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6247   // checking, so path walking order is not guaranteed.
6248   static if (bestOrder) {
6249     int lastBestOrder = int.min;
6250   }
6251 
6252   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6253   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6254     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6255       //{ 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); }
6256       static if (bestOrder) {
6257         // reject earlier paths
6258         if (pp.order <= lastBestOrder) continue; // not interesting
6259       }
6260       immutable uint kpx = kind&pp.flags&3;
6261       if (kpx == 0) continue; // not interesting
6262       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6263       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6264       int hit = 0;
6265       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6266       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6267       if (!hit) continue;
6268       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6269       static if (bestOrder) lastBestOrder = pp.order;
6270       static if (IsGoodHitTestDG!DG) {
6271         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6272           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6273         } else {
6274           dg(pp.id, cast(int)pp.order);
6275         }
6276       } else {
6277         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6278           if (dg(pp)) return pp.id;
6279         } else {
6280           dg(pp);
6281         }
6282       }
6283     }
6284     cellx >>= 1;
6285     celly >>= 1;
6286     levelwidth >>= 1;
6287   }
6288 
6289   return -1;
6290 }
6291 
6292 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6293 /// Returns the slice of [ids].
6294 /// Group: picking_api
6295 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6296   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6297 
6298   int npicked = 0;
6299   NVGpickScene* ps = ctx.pickScene;
6300 
6301   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6302     if (npicked == ps.cpicked) {
6303       int cpicked = ps.cpicked+ps.cpicked;
6304       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6305       if (picked is null) return true; // abort
6306       ps.cpicked = cpicked;
6307       ps.picked = picked;
6308     }
6309     ps.picked[npicked] = pp;
6310     ++npicked;
6311     return false; // go on
6312   });
6313 
6314   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6315 
6316   assert(npicked >= 0);
6317   if (npicked > ids.length) npicked = cast(int)ids.length;
6318   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6319 
6320   return ids[0..npicked];
6321 }
6322 
6323 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6324 /// Group: picking_api
6325 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6326   if (ctx.pickScene is null) return -1;
6327 
6328   int bestOrder = int.min;
6329   int bestID = -1;
6330 
6331   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6332     if (pp.order > bestOrder) {
6333       bestOrder = pp.order;
6334       bestID = pp.id;
6335     }
6336   });
6337 
6338   return bestID;
6339 }
6340 
6341 /// Returns `true` if the path with the given id contains x,y.
6342 /// Group: picking_api
6343 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6344   if (ctx.pickScene is null || id == NVGNoPick) return false;
6345 
6346   bool res = false;
6347 
6348   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6349     if (pp.id == id) {
6350       res = true;
6351       return true; // stop
6352     }
6353     return false; // continue
6354   });
6355 
6356   return res;
6357 }
6358 
6359 /// Returns `true` if the given point is within the fill of the currently defined path.
6360 /// This operation can be done before rasterizing the current path.
6361 /// Group: picking_api
6362 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6363   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6364   int oldnpoints = ps.npoints;
6365   int oldnsegments = ps.nsegments;
6366   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6367   if (pp is null) return false; // oops
6368   scope(exit) {
6369     nvg__freePickPath(ps, pp);
6370     ps.npoints = oldnpoints;
6371     ps.nsegments = oldnsegments;
6372   }
6373   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6374 }
6375 
6376 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6377 
6378 /// Returns `true` if the given point is within the stroke of the currently defined path.
6379 /// This operation can be done before rasterizing the current path.
6380 /// Group: picking_api
6381 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6382   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6383   int oldnpoints = ps.npoints;
6384   int oldnsegments = ps.nsegments;
6385   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6386   if (pp is null) return false; // oops
6387   scope(exit) {
6388     nvg__freePickPath(ps, pp);
6389     ps.npoints = oldnpoints;
6390     ps.nsegments = oldnsegments;
6391   }
6392   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6393 }
6394 
6395 
6396 nothrow @trusted @nogc {
6397 extern(C) {
6398   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6399   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6400 
6401   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6402     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6403   }
6404 }
6405 
6406 enum NVGPickEPS = 0.0001f;
6407 
6408 // Segment flags
6409 enum NVGSegmentFlags {
6410   Corner = 1,
6411   Bevel = 2,
6412   InnerBevel = 4,
6413   Cap = 8,
6414   Endcap = 16,
6415 }
6416 
6417 // Path flags
6418 enum NVGPathFlags : ushort {
6419   Fill = NVGPickKind.Fill,
6420   Stroke = NVGPickKind.Stroke,
6421   Scissor = 0x80,
6422 }
6423 
6424 struct NVGsegment {
6425   int firstPoint; // Index into NVGpickScene.points
6426   short type; // NVG_LINETO or NVG_BEZIERTO
6427   short flags; // Flags relate to the corner between the prev segment and this one.
6428   float[4] bounds;
6429   float[2] startDir; // Direction at t == 0
6430   float[2] endDir; // Direction at t == 1
6431   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6432 }
6433 
6434 struct NVGpickSubPath {
6435   short winding; // TODO: Merge to flag field
6436   bool closed; // TODO: Merge to flag field
6437 
6438   int firstSegment; // Index into NVGpickScene.segments
6439   int nsegments;
6440 
6441   float[4] bounds;
6442 
6443   NVGpickSubPath* next;
6444 }
6445 
6446 struct NVGpickPath {
6447   int id;
6448   short flags;
6449   short order;
6450   float strokeWidth;
6451   float miterLimit;
6452   short lineCap;
6453   short lineJoin;
6454   bool evenOddMode;
6455 
6456   float[4] bounds;
6457   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6458 
6459   NVGpickSubPath* subPaths;
6460   NVGpickPath* next;
6461   NVGpickPath* cellnext;
6462 }
6463 
6464 struct NVGpickScene {
6465   int npaths;
6466 
6467   NVGpickPath* paths; // Linked list of paths
6468   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6469   NVGpickPath* freePaths; // Linked list of free paths
6470 
6471   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6472 
6473   int width;
6474   int height;
6475 
6476   // Points for all path sub paths.
6477   float* points;
6478   int npoints;
6479   int cpoints;
6480 
6481   // Segments for all path sub paths
6482   NVGsegment* segments;
6483   int nsegments;
6484   int csegments;
6485 
6486   // Implicit quadtree
6487   float xdim; // Width / (1 << nlevels)
6488   float ydim; // Height / (1 << nlevels)
6489   int ncells; // Total number of cells in all levels
6490   int nlevels;
6491   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6492 
6493   // Temp storage for picking
6494   int cpicked;
6495   NVGpickPath** picked;
6496 }
6497 
6498 
6499 // bounds utilities
6500 void nvg__initBounds (ref float[4] bounds) {
6501   bounds.ptr[0] = bounds.ptr[1] = float.max;
6502   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6503 }
6504 
6505 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6506   npoints *= 2;
6507   for (int i = 0; i < npoints; i += 2) {
6508     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6509     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6510     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6511     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6512   }
6513 }
6514 
6515 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6516   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6517   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6518   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6519   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6520 }
6521 
6522 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) {
6523   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6524   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6525   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6526   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6527 
6528   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6529   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6530 }
6531 
6532 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) {
6533   pragma(inline, true);
6534   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6535 }
6536 
6537 // building paths & sub paths
6538 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6539   import core.stdc.string : memcpy;
6540   if (ps.npoints+n > ps.cpoints) {
6541     import core.stdc.stdlib : realloc;
6542     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6543     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6544     if (points is null) assert(0, "NanoVega: out of memory");
6545     ps.points = points;
6546     ps.cpoints = cpoints;
6547   }
6548   int i = ps.npoints;
6549   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6550   ps.npoints += n;
6551   return i;
6552 }
6553 
6554 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6555   NVGsegment* seg = null;
6556   if (ps.nsegments == ps.csegments) {
6557     int csegments = 1+ps.csegments+(ps.csegments<<1);
6558     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6559     if (segments is null) assert(0, "NanoVega: out of memory");
6560     ps.segments = segments;
6561     ps.csegments = csegments;
6562   }
6563 
6564   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6565 
6566   seg = &ps.segments[ps.nsegments];
6567   ++ps.nsegments;
6568   seg.firstPoint = firstPoint;
6569   seg.type = cast(short)type;
6570   seg.flags = flags;
6571   ++psp.nsegments;
6572 
6573   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6574   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6575 }
6576 
6577 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6578   const(float)* points = &ps.points[seg.firstPoint*2];
6579   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6580   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6581   switch (seg.type) {
6582     case Command.LineTo:
6583       d.ptr[0] = x1-x0;
6584       d.ptr[1] = y1-y0;
6585       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6586       break;
6587     case Command.BezierTo:
6588       immutable float x2 = points[2*2+0];
6589       immutable float y2 = points[2*2+1];
6590       immutable float x3 = points[3*2+0];
6591       immutable float y3 = points[3*2+1];
6592 
6593       immutable float omt = 1.0f-t;
6594       immutable float omt2 = omt*omt;
6595       immutable float t2 = t*t;
6596 
6597       d.ptr[0] =
6598         3.0f*omt2*(x1-x0)+
6599         6.0f*omt*t*(x2-x1)+
6600         3.0f*t2*(x3-x2);
6601 
6602       d.ptr[1] =
6603         3.0f*omt2*(y1-y0)+
6604         6.0f*omt*t*(y2-y1)+
6605         3.0f*t2*(y3-y2);
6606 
6607       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6608       break;
6609     default:
6610       break;
6611   }
6612 }
6613 
6614 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6615   if (psp.firstSegment == -1) return;
6616   NVGsegment* segments = &ps.segments[psp.firstSegment];
6617   for (int s = 0; s < psp.nsegments; ++s) {
6618     NVGsegment* seg = &segments[s];
6619     const(float)* points = &ps.points[seg.firstPoint*2];
6620     if (seg.type == Command.LineTo) {
6621       nvg__initBounds(seg.bounds);
6622       nvg__expandBounds(seg.bounds, points, 2);
6623     } else {
6624       nvg__bezierBounds(points, seg.bounds);
6625     }
6626   }
6627 }
6628 
6629 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6630   if (psp.firstSegment == -1) return;
6631   immutable bool closed = psp.closed;
6632   const(float)* points = ps.points;
6633   NVGsegment* seg = null;
6634   NVGsegment* segments = &ps.segments[psp.firstSegment];
6635   int nsegments = psp.nsegments;
6636   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6637 
6638   int ns = 0; // nsupports
6639   float[32] supportingPoints = void;
6640   int firstPoint, lastPoint;
6641 
6642   if (!closed) {
6643     segments[0].flags |= NVGSegmentFlags.Cap;
6644     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6645   }
6646 
6647   for (int s = 0; s < nsegments; ++s) {
6648     seg = &segments[s];
6649     nvg__initBounds(seg.bounds);
6650 
6651     firstPoint = seg.firstPoint*2;
6652     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6653 
6654     ns = 0;
6655 
6656     // First two supporting points are either side of the start point
6657     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6658     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6659 
6660     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6661     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6662 
6663     // Second two supporting points are either side of the end point
6664     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6665     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6666 
6667     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6668     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6669 
6670     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6671       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6672       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6673 
6674       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6675 
6676       if (M2 > 0.000001f) {
6677         float scale = 1.0f/M2;
6678         if (scale > 600.0f) scale = 600.0f;
6679         seg.miterDir.ptr[0] *= scale;
6680         seg.miterDir.ptr[1] *= scale;
6681       }
6682 
6683       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6684 
6685       // Add an additional support at the corner on the other line
6686       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6687       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6688 
6689       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6690         // Set a corner as beveled if the join type is bevel or mitered and
6691         // miterLimit is hit.
6692         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6693           seg.flags |= NVGSegmentFlags.Bevel;
6694         } else {
6695           // Corner is mitered - add miter point as a support
6696           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6697           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6698         }
6699       } else if (lineJoin == NVGLineCap.Round) {
6700         // ... and at the midpoint of the corner arc
6701         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6702         nvg__normalize(&vertexN[0], &vertexN[1]);
6703 
6704         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6705         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6706       }
6707     }
6708 
6709     if (seg.flags&NVGSegmentFlags.Cap) {
6710       switch (lineCap) {
6711         case NVGLineCap.Butt:
6712           // supports for butt already added
6713           break;
6714         case NVGLineCap.Square:
6715           // square cap supports are just the original two supports moved out along the direction
6716           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6717           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6718           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6719           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6720           break;
6721         case NVGLineCap.Round:
6722           // add one additional support for the round cap along the dir
6723           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6724           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6725           break;
6726         default:
6727           break;
6728       }
6729     }
6730 
6731     if (seg.flags&NVGSegmentFlags.Endcap) {
6732       // end supporting points, either side of line
6733       int end = 4;
6734       switch(lineCap) {
6735         case NVGLineCap.Butt:
6736           // supports for butt already added
6737           break;
6738         case NVGLineCap.Square:
6739           // square cap supports are just the original two supports moved out along the direction
6740           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6741           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6742           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6743           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6744           break;
6745         case NVGLineCap.Round:
6746           // add one additional support for the round cap along the dir
6747           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6748           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6749           break;
6750         default:
6751           break;
6752       }
6753     }
6754 
6755     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6756 
6757     prevseg = seg;
6758   }
6759 }
6760 
6761 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6762   NVGpickScene* ps = nvg__pickSceneGet(context);
6763   if (ps is null) return null;
6764 
6765   int i = 0;
6766 
6767   int ncommands = cast(int)acommands.length;
6768   const(float)* commands = acommands.ptr;
6769 
6770   NVGpickPath* pp = null;
6771   NVGpickSubPath* psp = null;
6772   float[2] start = void;
6773   int firstPoint;
6774 
6775   //bool hasHoles = false;
6776   NVGpickSubPath* prev = null;
6777 
6778   float[8] points = void;
6779   float[2] inflections = void;
6780   int ninflections = 0;
6781 
6782   NVGstate* state = nvg__getState(context);
6783   float[4] totalBounds = void;
6784   NVGsegment* segments = null;
6785   const(NVGsegment)* seg = null;
6786   NVGpickSubPath *curpsp;
6787 
6788   pp = nvg__allocPickPath(ps);
6789   if (pp is null) return null;
6790 
6791   pp.id = id;
6792 
6793   bool hasPoints = false;
6794 
6795   void closeIt () {
6796     if (psp is null || !hasPoints) return;
6797     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6798       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6799       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6800     }
6801     psp.closed = true;
6802   }
6803 
6804   while (i < ncommands) {
6805     int cmd = cast(int)commands[i++];
6806     switch (cmd) {
6807       case Command.MoveTo: // one coordinate pair
6808         const(float)* tfxy = commands+i;
6809         i += 2;
6810 
6811         // new starting point
6812         start.ptr[0..2] = tfxy[0..2];
6813 
6814         // start a new path for each sub path to handle sub paths that intersect other sub paths
6815         prev = psp;
6816         psp = nvg__allocPickSubPath(ps);
6817         if (psp is null) { psp = prev; break; }
6818         psp.firstSegment = -1;
6819         psp.winding = NVGSolidity.Solid;
6820         psp.next = prev;
6821 
6822         nvg__pickSceneAddPoints(ps, tfxy, 1);
6823         hasPoints = true;
6824         break;
6825       case Command.LineTo: // one coordinate pair
6826         const(float)* tfxy = commands+i;
6827         i += 2;
6828         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6829         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6830         hasPoints = true;
6831         break;
6832       case Command.BezierTo: // three coordinate pairs
6833         const(float)* tfxy = commands+i;
6834         i += 3*2;
6835 
6836         // Split the curve at it's dx==0 or dy==0 inflection points.
6837         // Thus:
6838         //    A horizontal line only ever interects the curves once.
6839         //  and
6840         //    Finding the closest point on any curve converges more reliably.
6841 
6842         // NOTE: We could just split on dy==0 here.
6843 
6844         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6845         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6846 
6847         ninflections = 0;
6848         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6849         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6850 
6851         if (ninflections) {
6852           float previnfl = 0;
6853           float[8] pointsA = void, pointsB = void;
6854 
6855           nvg__smallsort(inflections.ptr, ninflections);
6856 
6857           for (int infl = 0; infl < ninflections; ++infl) {
6858             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6859 
6860             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6861 
6862             previnfl = inflections.ptr[infl];
6863 
6864             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6865 
6866             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6867             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6868 
6869             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6870           }
6871 
6872           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6873           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6874         } else {
6875           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6876           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6877         }
6878         hasPoints = true;
6879         break;
6880       case Command.Close:
6881         closeIt();
6882         break;
6883       case Command.Winding:
6884         psp.winding = cast(short)cast(int)commands[i];
6885         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6886         i += 1;
6887         break;
6888       default:
6889         break;
6890     }
6891   }
6892 
6893   // force-close filled paths
6894   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6895 
6896   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6897   pp.subPaths = psp;
6898   pp.strokeWidth = state.strokeWidth*0.5f;
6899   pp.miterLimit = state.miterLimit;
6900   pp.lineCap = cast(short)state.lineCap;
6901   pp.lineJoin = cast(short)state.lineJoin;
6902   pp.evenOddMode = nvg__getState(context).evenOddMode;
6903 
6904   nvg__initBounds(totalBounds);
6905 
6906   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6907     if (forStroke) {
6908       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6909     } else {
6910       nvg__pickSubPathAddFillSupports(ps, curpsp);
6911     }
6912 
6913     if (curpsp.firstSegment == -1) continue;
6914     segments = &ps.segments[curpsp.firstSegment];
6915     nvg__initBounds(curpsp.bounds);
6916     for (int s = 0; s < curpsp.nsegments; ++s) {
6917       seg = &segments[s];
6918       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6919       nvg__unionBounds(curpsp.bounds, seg.bounds);
6920     }
6921 
6922     nvg__unionBounds(totalBounds, curpsp.bounds);
6923   }
6924 
6925   // Store the scissor rect if present.
6926   if (state.scissor.extent.ptr[0] != -1.0f) {
6927     // Use points storage to store the scissor data
6928     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6929     float* scissor = &ps.points[pp.scissor*2];
6930 
6931     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6932     scissor[0..6] = state.scissor.xform.mat[];
6933     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6934 
6935     pp.flags |= NVGPathFlags.Scissor;
6936   }
6937 
6938   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6939 
6940   return pp;
6941 }
6942 
6943 
6944 // Struct management
6945 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6946   NVGpickPath* pp = ps.freePaths;
6947   if (pp !is null) {
6948     ps.freePaths = pp.next;
6949   } else {
6950     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6951   }
6952   memset(pp, 0, NVGpickPath.sizeof);
6953   return pp;
6954 }
6955 
6956 // Put a pick path and any sub paths (back) to the free lists.
6957 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6958   // Add all sub paths to the sub path free list.
6959   // Finds the end of the path sub paths, links that to the current
6960   // sub path free list head and replaces the head ptr with the
6961   // head path sub path entry.
6962   NVGpickSubPath* psp = null;
6963   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6964 
6965   if (psp) {
6966     psp.next = ps.freeSubPaths;
6967     ps.freeSubPaths = pp.subPaths;
6968   }
6969   pp.subPaths = null;
6970 
6971   // Add the path to the path freelist
6972   pp.next = ps.freePaths;
6973   ps.freePaths = pp;
6974   if (pp.next is null) ps.lastPath = pp;
6975 }
6976 
6977 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6978   NVGpickSubPath* psp = ps.freeSubPaths;
6979   if (psp !is null) {
6980     ps.freeSubPaths = psp.next;
6981   } else {
6982     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6983     if (psp is null) return null;
6984   }
6985   memset(psp, 0, NVGpickSubPath.sizeof);
6986   return psp;
6987 }
6988 
6989 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6990   psp.next = ps.freeSubPaths;
6991   ps.freeSubPaths = psp;
6992 }
6993 
6994 NVGpickScene* nvg__allocPickScene () {
6995   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6996   if (ps is null) return null;
6997   memset(ps, 0, NVGpickScene.sizeof);
6998   ps.nlevels = 5;
6999   return ps;
7000 }
7001 
7002 void nvg__deletePickScene (NVGpickScene* ps) {
7003   NVGpickPath* pp;
7004   NVGpickSubPath* psp;
7005 
7006   // Add all paths (and thus sub paths) to the free list(s).
7007   while (ps.paths !is null) {
7008     pp = ps.paths.next;
7009     nvg__freePickPath(ps, ps.paths);
7010     ps.paths = pp;
7011   }
7012 
7013   // Delete all paths
7014   while (ps.freePaths !is null) {
7015     pp = ps.freePaths;
7016     ps.freePaths = pp.next;
7017     while (pp.subPaths !is null) {
7018       psp = pp.subPaths;
7019       pp.subPaths = psp.next;
7020       free(psp);
7021     }
7022     free(pp);
7023   }
7024 
7025   // Delete all sub paths
7026   while (ps.freeSubPaths !is null) {
7027     psp = ps.freeSubPaths.next;
7028     free(ps.freeSubPaths);
7029     ps.freeSubPaths = psp;
7030   }
7031 
7032   ps.npoints = 0;
7033   ps.nsegments = 0;
7034 
7035   if (ps.levels !is null) {
7036     free(ps.levels[0]);
7037     free(ps.levels);
7038   }
7039 
7040   if (ps.picked !is null) free(ps.picked);
7041   if (ps.points !is null) free(ps.points);
7042   if (ps.segments !is null) free(ps.segments);
7043 
7044   free(ps);
7045 }
7046 
7047 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7048   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7049   return ctx.pickScene;
7050 }
7051 
7052 
7053 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7054 // points is 4 points (8 floats)
7055 // lvl1 is 3 points (6 floats)
7056 // lvl2 is 2 points (4 floats)
7057 // lvl3 is 1 point (2 floats)
7058 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7059   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7060   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7061 
7062   // Level 1
7063   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7064   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7065 
7066   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7067   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7068 
7069   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7070   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7071 
7072   // Level 2
7073   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7074   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7075 
7076   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7077   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7078 
7079   // Level 3
7080   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7081   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7082 }
7083 
7084 // Calculates a point on a bezier at point t.
7085 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7086   immutable float omt = 1-t;
7087   immutable float omt3 = omt*omt*omt;
7088   immutable float omt2 = omt*omt;
7089   immutable float t3 = t*t*t;
7090   immutable float t2 = t*t;
7091 
7092   tpoint.ptr[0] =
7093     points[0]*omt3+
7094     points[2]*3.0f*omt2*t+
7095     points[4]*3.0f*omt*t2+
7096     points[6]*t3;
7097 
7098   tpoint.ptr[1] =
7099     points[1]*omt3+
7100     points[3]*3.0f*omt2*t+
7101     points[5]*3.0f*omt*t2+
7102     points[7]*t3;
7103 }
7104 
7105 // Splits a cubic bezier curve into two parts at point t.
7106 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7107   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7108   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7109 
7110   float[6] lvl1 = void;
7111   float[4] lvl2 = void;
7112   float[2] lvl3 = void;
7113 
7114   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7115 
7116   // First half
7117   pointsA[x0] = points[x0];
7118   pointsA[y0] = points[y0];
7119 
7120   pointsA[x1] = lvl1.ptr[x0];
7121   pointsA[y1] = lvl1.ptr[y0];
7122 
7123   pointsA[x2] = lvl2.ptr[x0];
7124   pointsA[y2] = lvl2.ptr[y0];
7125 
7126   pointsA[x3] = lvl3.ptr[x0];
7127   pointsA[y3] = lvl3.ptr[y0];
7128 
7129   // Second half
7130   pointsB[x0] = lvl3.ptr[x0];
7131   pointsB[y0] = lvl3.ptr[y0];
7132 
7133   pointsB[x1] = lvl2.ptr[x1];
7134   pointsB[y1] = lvl2.ptr[y1];
7135 
7136   pointsB[x2] = lvl1.ptr[x2];
7137   pointsB[y2] = lvl1.ptr[y2];
7138 
7139   pointsB[x3] = points[x3];
7140   pointsB[y3] = points[y3];
7141 }
7142 
7143 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7144 // Appends any found inflection points to the array inflections and increments *ninflections.
7145 // So finds the parameters where dx/dt or dy/dt is 0
7146 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7147   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7148   float[2] t = void;
7149   int nvalid = *ninflections;
7150 
7151   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7152   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7153   immutable float c = 3.0f*( v1-v0 );
7154 
7155   float d = b*b-4.0f*a*c;
7156   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7157     // Zero or one root
7158     t.ptr[0] = -b/2.0f*a;
7159     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7160       inflections[nvalid] = t.ptr[0];
7161       ++nvalid;
7162     }
7163   } else if (d > NVGPickEPS) {
7164     // zero, one or two roots
7165     d = nvg__sqrtf(d);
7166 
7167     t.ptr[0] = (-b+d)/(2.0f*a);
7168     t.ptr[1] = (-b-d)/(2.0f*a);
7169 
7170     for (int i = 0; i < 2; ++i) {
7171       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7172         inflections[nvalid] = t.ptr[i];
7173         ++nvalid;
7174       }
7175     }
7176   } else {
7177     // zero roots
7178   }
7179 
7180   *ninflections = nvalid;
7181 }
7182 
7183 // Sort a small number of floats in ascending order (0 < n < 6)
7184 void nvg__smallsort (float* values, int n) {
7185   bool bSwapped = true;
7186   for (int j = 0; j < n-1 && bSwapped; ++j) {
7187     bSwapped = false;
7188     for (int i = 0; i < n-1; ++i) {
7189       if (values[i] > values[i+1]) {
7190         auto tmp = values[i];
7191         values[i] = values[i+1];
7192         values[i+1] = tmp;
7193       }
7194     }
7195   }
7196 }
7197 
7198 // Calculates the bounding rect of a given cubic bezier curve.
7199 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7200   float[4] inflections = void;
7201   int ninflections = 0;
7202   float[2] tpoint = void;
7203 
7204   nvg__initBounds(bounds);
7205 
7206   // Include start and end points in bounds
7207   nvg__expandBounds(bounds, &points[0], 1);
7208   nvg__expandBounds(bounds, &points[6], 1);
7209 
7210   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7211 
7212   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7213   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7214 
7215   foreach (immutable int i; 0..ninflections) {
7216     nvg__bezierEval(points, inflections[i], tpoint);
7217     nvg__expandBounds(bounds, tpoint.ptr, 1);
7218   }
7219 }
7220 
7221 // Checks to see if a line originating from x,y along the +ve x axis
7222 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7223 // Returns `true` on intersection.
7224 // Horizontal lines are never hit.
7225 bool nvg__intersectLine (const(float)* points, float x, float y) {
7226   immutable float x1 = points[0];
7227   immutable float y1 = points[1];
7228   immutable float x2 = points[2];
7229   immutable float y2 = points[3];
7230   immutable float d = y2-y1;
7231   if (d > NVGPickEPS || d < -NVGPickEPS) {
7232     immutable float s = (x2-x1)/d;
7233     immutable float lineX = x1+(y-y1)*s;
7234     return (lineX > x);
7235   } else {
7236     return false;
7237   }
7238 }
7239 
7240 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7241 // It is assumed that the line originates from within the bounding box of
7242 // the bezier and that the curve has no dy=0 inflection points.
7243 // Returns the number of intersections found (which is either 1 or 0).
7244 int nvg__intersectBezier (const(float)* points, float x, float y) {
7245   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7246   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7247 
7248   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7249 
7250   // Initial t guess
7251   float t = void;
7252        if (y3 != y0) t = (y-y0)/(y3-y0);
7253   else if (x3 != x0) t = (x-x0)/(x3-x0);
7254   else t = 0.5f;
7255 
7256   // A few Newton iterations
7257   for (int iter = 0; iter < 6; ++iter) {
7258     immutable float omt = 1-t;
7259     immutable float omt2 = omt*omt;
7260     immutable float t2 = t*t;
7261     immutable float omt3 = omt2*omt;
7262     immutable float t3 = t2*t;
7263 
7264     immutable float ty = y0*omt3 +
7265       y1*3.0f*omt2*t +
7266       y2*3.0f*omt*t2 +
7267       y3*t3;
7268 
7269     // Newton iteration
7270     immutable float dty = 3.0f*omt2*(y1-y0) +
7271       6.0f*omt*t*(y2-y1) +
7272       3.0f*t2*(y3-y2);
7273 
7274     // dty will never == 0 since:
7275     //  Either omt, omt2 are zero OR t2 is zero
7276     //  y0 != y1 != y2 != y3 (checked above)
7277     t = t-(ty-y)/dty;
7278   }
7279 
7280   {
7281     immutable float omt = 1-t;
7282     immutable float omt2 = omt*omt;
7283     immutable float t2 = t*t;
7284     immutable float omt3 = omt2*omt;
7285     immutable float t3 = t2*t;
7286 
7287     immutable float tx =
7288       x0*omt3+
7289       x1*3.0f*omt2*t+
7290       x2*3.0f*omt*t2+
7291       x3*t3;
7292 
7293     return (tx > x ? 1 : 0);
7294   }
7295 }
7296 
7297 // Finds the closest point on a line to a given point
7298 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7299   immutable float x1 = points[0];
7300   immutable float y1 = points[1];
7301   immutable float x2 = points[2];
7302   immutable float y2 = points[3];
7303   immutable float pqx = x2-x1;
7304   immutable float pqz = y2-y1;
7305   immutable float dx = x-x1;
7306   immutable float dz = y-y1;
7307   immutable float d = pqx*pqx+pqz*pqz;
7308   float t = pqx*dx+pqz*dz;
7309   if (d > 0) t /= d;
7310   if (t < 0) t = 0; else if (t > 1) t = 1;
7311   closest[0] = x1+t*pqx;
7312   closest[1] = y1+t*pqz;
7313   *ot = t;
7314 }
7315 
7316 // Finds the closest point on a curve for a given point (x,y).
7317 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7318 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7319   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7320   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7321 
7322   // This assumes that the curve has no dy=0 inflection points.
7323 
7324   // Initial t guess
7325   float t = 0.5f;
7326 
7327   // A few Newton iterations
7328   for (int iter = 0; iter < 6; ++iter) {
7329     immutable float omt = 1-t;
7330     immutable float omt2 = omt*omt;
7331     immutable float t2 = t*t;
7332     immutable float omt3 = omt2*omt;
7333     immutable float t3 = t2*t;
7334 
7335     immutable float ty =
7336       y0*omt3+
7337       y1*3.0f*omt2*t+
7338       y2*3.0f*omt*t2+
7339       y3*t3;
7340 
7341     immutable float tx =
7342       x0*omt3+
7343       x1*3.0f*omt2*t+
7344       x2*3.0f*omt*t2+
7345       x3*t3;
7346 
7347     // Newton iteration
7348     immutable float dty =
7349       3.0f*omt2*(y1-y0)+
7350       6.0f*omt*t*(y2-y1)+
7351       3.0f*t2*(y3-y2);
7352 
7353     immutable float ddty =
7354       6.0f*omt*(y2-2.0f*y1+y0)+
7355       6.0f*t*(y3-2.0f*y2+y1);
7356 
7357     immutable float dtx =
7358       3.0f*omt2*(x1-x0)+
7359       6.0f*omt*t*(x2-x1)+
7360       3.0f*t2*(x3-x2);
7361 
7362     immutable float ddtx =
7363       6.0f*omt*(x2-2.0f*x1+x0)+
7364       6.0f*t*(x3-2.0f*x2+x1);
7365 
7366     immutable float errorx = tx-x;
7367     immutable float errory = ty-y;
7368 
7369     immutable float n = errorx*dtx+errory*dty;
7370     if (n == 0) break;
7371 
7372     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7373     if (d != 0) t = t-n/d; else break;
7374   }
7375 
7376   t = nvg__max(0, nvg__min(1.0, t));
7377   *ot = t;
7378   {
7379     immutable float omt = 1-t;
7380     immutable float omt2 = omt*omt;
7381     immutable float t2 = t*t;
7382     immutable float omt3 = omt2*omt;
7383     immutable float t3 = t2*t;
7384 
7385     immutable float ty =
7386       y0*omt3+
7387       y1*3.0f*omt2*t+
7388       y2*3.0f*omt*t2+
7389       y3*t3;
7390 
7391     immutable float tx =
7392       x0*omt3+
7393       x1*3.0f*omt2*t+
7394       x2*3.0f*omt*t2+
7395       x3*t3;
7396 
7397     closest[0] = tx;
7398     closest[1] = ty;
7399   }
7400 }
7401 
7402 // Returns:
7403 //  1  If (x,y) is contained by the stroke of the path
7404 //  0  If (x,y) is not contained by the path.
7405 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7406   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7407   if (psp.firstSegment == -1) return 0;
7408 
7409   float[2] closest = void;
7410   float[2] d = void;
7411   float t = void;
7412 
7413   // trace a line from x,y out along the positive x axis and count the number of intersections
7414   int nsegments = psp.nsegments;
7415   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7416   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7417   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7418 
7419   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7420     if (nvg__pointInBounds(x, y, seg.bounds)) {
7421       // Line potentially hits stroke.
7422       switch (seg.type) {
7423         case Command.LineTo:
7424           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7425           break;
7426         case Command.BezierTo:
7427           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7428           break;
7429         default:
7430           continue;
7431       }
7432 
7433       d.ptr[0] = x-closest.ptr[0];
7434       d.ptr[1] = y-closest.ptr[1];
7435 
7436       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7437           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7438           (lineJoin == NVGLineCap.Round))
7439       {
7440         // Closest point is in the middle of the line/curve, at a rounded join/cap
7441         // or at a smooth join
7442         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7443         if (distSqd < strokeWidthSqd) return 1;
7444       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7445                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7446         switch (lineCap) {
7447           case NVGLineCap.Butt:
7448             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7449             immutable float dirD = (t < NVGPickEPS ?
7450               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7451                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7452             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7453             break;
7454           case NVGLineCap.Square:
7455             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7456             break;
7457           case NVGLineCap.Round:
7458             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7459             if (distSqd < strokeWidthSqd) return 1;
7460             break;
7461           default:
7462             break;
7463         }
7464       } else if (seg.flags&NVGSegmentFlags.Corner) {
7465         // Closest point is at a corner
7466         const(NVGsegment)* seg0, seg1;
7467 
7468         if (t < NVGPickEPS) {
7469           seg0 = prevseg;
7470           seg1 = seg;
7471         } else {
7472           seg0 = seg;
7473           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7474         }
7475 
7476         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7477           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7478           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7479           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7480         } else {
7481           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7482           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7483           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7484         }
7485       }
7486     }
7487   }
7488 
7489   return 0;
7490 }
7491 
7492 // Returns:
7493 //   1  If (x,y) is contained by the path and the path is solid.
7494 //  -1  If (x,y) is contained by the path and the path is a hole.
7495 //   0  If (x,y) is not contained by the path.
7496 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7497   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7498   if (psp.firstSegment == -1) return 0;
7499 
7500   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7501   int nsegments = psp.nsegments;
7502   int nintersections = 0;
7503 
7504   // trace a line from x,y out along the positive x axis and count the number of intersections
7505   for (int s = 0; s < nsegments; ++s, ++seg) {
7506     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7507         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7508         seg.bounds.ptr[2] > x)
7509     {
7510       // Line hits the box.
7511       switch (seg.type) {
7512         case Command.LineTo:
7513           if (seg.bounds.ptr[0] > x) {
7514             // line originates outside the box
7515             ++nintersections;
7516           } else {
7517             // line originates inside the box
7518             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7519           }
7520           break;
7521         case Command.BezierTo:
7522           if (seg.bounds.ptr[0] > x) {
7523             // line originates outside the box
7524             ++nintersections;
7525           } else {
7526             // line originates inside the box
7527             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7528           }
7529           break;
7530         default:
7531           break;
7532       }
7533     }
7534   }
7535 
7536   if (evenOddMode) {
7537     return nintersections;
7538   } else {
7539     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7540   }
7541 }
7542 
7543 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7544   int pickCount = 0;
7545   const(NVGpickSubPath)* psp = pp.subPaths;
7546   while (psp !is null) {
7547     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7548     psp = psp.next;
7549   }
7550   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7551 }
7552 
7553 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7554   const(NVGpickSubPath)* psp = pp.subPaths;
7555   while (psp !is null) {
7556     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7557     psp = psp.next;
7558   }
7559   return false;
7560 }
7561 
7562 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7563   if (nvg__pointInBounds(x, y, pp.bounds)) {
7564     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7565     if (pp.flags&NVGPathFlags.Scissor) {
7566       const(float)* scissor = &ps.points[pp.scissor*2];
7567       // untransform scissor translation
7568       float stx = void, sty = void;
7569       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7570       immutable float rx = x-stx;
7571       immutable float ry = y-sty;
7572       //{ 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]); }
7573       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7574           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7575       {
7576         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7577         return false;
7578       }
7579     }
7580     return true;
7581   }
7582   return false;
7583 }
7584 
7585 int nvg__countBitsUsed (uint v) pure {
7586   pragma(inline, true);
7587   import core.bitop : bsr;
7588   return (v != 0 ? bsr(v)+1 : 0);
7589 }
7590 
7591 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7592   if (ps is null || pp is null) return;
7593 
7594   int[4] cellbounds;
7595   int base = ps.nlevels-1;
7596   int level;
7597   int levelwidth;
7598   int levelshift;
7599   int levelx;
7600   int levely;
7601   NVGpickPath** cell = null;
7602 
7603   // Bit tricks for inserting into an implicit quadtree.
7604 
7605   // Calc bounds of path in cells at the lowest level
7606   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7607   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7608   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7609   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7610 
7611   // Find which bits differ between the min/max x/y coords
7612   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7613   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7614 
7615   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7616   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7617   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7618   if (level < 0) level = 0;
7619   //{ 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]); }
7620   //level = 0;
7621 
7622   // Find the correct cell in the chosen level, clamping to the edges.
7623   levelwidth = 1<<level;
7624   levelshift = (ps.nlevels-level)-1;
7625   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7626   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7627 
7628   // Insert the path into the linked list at that cell.
7629   cell = &ps.levels[level][levely*levelwidth+levelx];
7630 
7631   pp.cellnext = *cell;
7632   *cell = pp;
7633 
7634   if (ps.paths is null) ps.lastPath = pp;
7635   pp.next = ps.paths;
7636   ps.paths = pp;
7637 
7638   // Store the order (depth) of the path for picking ops.
7639   pp.order = cast(short)ps.npaths;
7640   ++ps.npaths;
7641 }
7642 
7643 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7644   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7645 
7646   //NVG_PICK_DEBUG_NEWFRAME();
7647 
7648   // Return all paths & sub paths from last frame to the free list
7649   while (ps.paths !is null) {
7650     NVGpickPath* pp = ps.paths.next;
7651     nvg__freePickPath(ps, ps.paths);
7652     ps.paths = pp;
7653   }
7654 
7655   ps.paths = null;
7656   ps.npaths = 0;
7657 
7658   // Store the screen metrics for the quadtree
7659   ps.width = width;
7660   ps.height = height;
7661 
7662   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7663   ps.xdim = cast(float)width/lowestSubDiv;
7664   ps.ydim = cast(float)height/lowestSubDiv;
7665 
7666   // Allocate the quadtree if required.
7667   if (ps.levels is null) {
7668     int ncells = 1;
7669 
7670     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7671     for (int l = 0; l < ps.nlevels; ++l) {
7672       int leveldim = 1<<l;
7673       ncells += leveldim*leveldim;
7674     }
7675 
7676     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7677 
7678     int cell = 1;
7679     for (int l = 1; l < ps.nlevels; ++l) {
7680       ps.levels[l] = &ps.levels[0][cell];
7681       int leveldim = 1<<l;
7682       cell += leveldim*leveldim;
7683     }
7684 
7685     ps.ncells = ncells;
7686   }
7687   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7688 
7689   // Allocate temporary storage for nvgHitTestAll results if required.
7690   if (ps.picked is null) {
7691     ps.cpicked = 16;
7692     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7693   }
7694 
7695   ps.npoints = 0;
7696   ps.nsegments = 0;
7697 }
7698 } // nothrow @trusted @nogc
7699 
7700 
7701 /// Return outline of the current path. Returned outline is not flattened.
7702 /// Group: paths
7703 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7704   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7705 
7706   auto res = NVGPathOutline.createNew();
7707 
7708   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7709   int ncommands = cast(int)acommands.length;
7710   const(float)* commands = acommands.ptr;
7711 
7712   float cx = 0, cy = 0;
7713   float[2] start = void;
7714   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7715   float[8] bcp = void; // bezier curve points; used to calculate bounds
7716 
7717   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7718     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7719     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7720     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7721     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7722   }
7723 
7724   bool hasPoints = false;
7725 
7726   void closeIt () nothrow @trusted @nogc {
7727     if (!hasPoints) return;
7728     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7729       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7730       res.ds.putArgs(start[]);
7731       cx = start.ptr[0];
7732       cy = start.ptr[1];
7733       addToBounds(cx, cy);
7734     }
7735   }
7736 
7737   int i = 0;
7738   while (i < ncommands) {
7739     int cmd = cast(int)commands[i++];
7740     switch (cmd) {
7741       case Command.MoveTo: // one coordinate pair
7742         const(float)* tfxy = commands+i;
7743         i += 2;
7744         // add command
7745         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7746         res.ds.putArgs(tfxy[0..2]);
7747         // new starting point
7748         start.ptr[0..2] = tfxy[0..2];
7749         cx = tfxy[0];
7750         cy = tfxy[0];
7751         addToBounds(cx, cy);
7752         hasPoints = true;
7753         break;
7754       case Command.LineTo: // one coordinate pair
7755         const(float)* tfxy = commands+i;
7756         i += 2;
7757         // add command
7758         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7759         res.ds.putArgs(tfxy[0..2]);
7760         cx = tfxy[0];
7761         cy = tfxy[0];
7762         addToBounds(cx, cy);
7763         hasPoints = true;
7764         break;
7765       case Command.BezierTo: // three coordinate pairs
7766         const(float)* tfxy = commands+i;
7767         i += 3*2;
7768         // add command
7769         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7770         res.ds.putArgs(tfxy[0..6]);
7771         // bounds
7772         bcp.ptr[0] = cx;
7773         bcp.ptr[1] = cy;
7774         bcp.ptr[2..8] = tfxy[0..6];
7775         nvg__bezierBounds(bcp.ptr, totalBounds);
7776         cx = tfxy[4];
7777         cy = tfxy[5];
7778         hasPoints = true;
7779         break;
7780       case Command.Close:
7781         closeIt();
7782         hasPoints = false;
7783         break;
7784       case Command.Winding:
7785         //psp.winding = cast(short)cast(int)commands[i];
7786         i += 1;
7787         break;
7788       default:
7789         break;
7790     }
7791   }
7792 
7793   res.ds.bounds[] = totalBounds[];
7794   return res;
7795 }
7796 
7797 
7798 // ////////////////////////////////////////////////////////////////////////// //
7799 // Text
7800 
7801 /** Creates font by loading it from the disk from specified file name.
7802  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7803  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7804  *
7805  * On POSIX systems it is possible to use fontconfig font names too.
7806  * `:noaa` in font path is still allowed, but it must be the last option.
7807  *
7808  * Group: text_api
7809  */
7810 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7811   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7812 }
7813 
7814 /** Creates font by loading it from the specified memory chunk.
7815  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7816  * Won't free data on error.
7817  *
7818  * Group: text_api
7819  */
7820 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7821   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7822 }
7823 
7824 /// Add fonts from another context.
7825 /// This is more effective than reloading fonts, 'cause font data will be shared.
7826 /// Group: text_api
7827 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7828   if (ctx is null || source is null) return;
7829   ctx.fs.addFontsFrom(source.fs);
7830 }
7831 
7832 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7833 /// Group: text_api
7834 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7835   pragma(inline, true);
7836   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7837 }
7838 
7839 /// Sets the font size of current text style.
7840 /// Group: text_api
7841 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7842   pragma(inline, true);
7843   nvg__getState(ctx).fontSize = size;
7844 }
7845 
7846 /// Gets the font size of current text style.
7847 /// Group: text_api
7848 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7849   pragma(inline, true);
7850   return nvg__getState(ctx).fontSize;
7851 }
7852 
7853 /// Sets the blur of current text style.
7854 /// Group: text_api
7855 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7856   pragma(inline, true);
7857   nvg__getState(ctx).fontBlur = blur;
7858 }
7859 
7860 /// Gets the blur of current text style.
7861 /// Group: text_api
7862 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7863   pragma(inline, true);
7864   return nvg__getState(ctx).fontBlur;
7865 }
7866 
7867 /// Sets the letter spacing of current text style.
7868 /// Group: text_api
7869 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7870   pragma(inline, true);
7871   nvg__getState(ctx).letterSpacing = spacing;
7872 }
7873 
7874 /// Gets the letter spacing of current text style.
7875 /// Group: text_api
7876 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7877   pragma(inline, true);
7878   return nvg__getState(ctx).letterSpacing;
7879 }
7880 
7881 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7882 /// Group: text_api
7883 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7884   pragma(inline, true);
7885   nvg__getState(ctx).lineHeight = lineHeight;
7886 }
7887 
7888 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7889 /// Group: text_api
7890 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7891   pragma(inline, true);
7892   return nvg__getState(ctx).lineHeight;
7893 }
7894 
7895 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7896 /// Group: text_api
7897 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7898   pragma(inline, true);
7899   nvg__getState(ctx).textAlign = talign;
7900 }
7901 
7902 /// Ditto.
7903 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7904   pragma(inline, true);
7905   nvg__getState(ctx).textAlign.horizontal = h;
7906 }
7907 
7908 /// Ditto.
7909 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7910   pragma(inline, true);
7911   nvg__getState(ctx).textAlign.vertical = v;
7912 }
7913 
7914 /// Ditto.
7915 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7916   pragma(inline, true);
7917   nvg__getState(ctx).textAlign.reset(h, v);
7918 }
7919 
7920 /// Ditto.
7921 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7922   pragma(inline, true);
7923   nvg__getState(ctx).textAlign.reset(h, v);
7924 }
7925 
7926 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7927 /// Group: text_api
7928 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7929   pragma(inline, true);
7930   return nvg__getState(ctx).textAlign;
7931 }
7932 
7933 /// Sets the font face based on specified id of current text style.
7934 /// Group: text_api
7935 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7936   pragma(inline, true);
7937   nvg__getState(ctx).fontId = font;
7938 }
7939 
7940 /// Gets the font face based on specified id of current text style.
7941 /// Group: text_api
7942 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7943   pragma(inline, true);
7944   return nvg__getState(ctx).fontId;
7945 }
7946 
7947 /** Sets the font face based on specified name of current text style.
7948  *
7949  * The underlying implementation is using O(1) data structure to lookup
7950  * font names, so you probably should use this function instead of [fontFaceId]
7951  * to make your code more robust and less error-prone.
7952  *
7953  * Group: text_api
7954  */
7955 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7956   pragma(inline, true);
7957   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7958 }
7959 
7960 static if (is(typeof(&fons__nvg__toPath))) {
7961   public enum NanoVegaHasCharToPath = true; ///
7962 } else {
7963   public enum NanoVegaHasCharToPath = false; ///
7964 }
7965 
7966 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7967 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7968 /// Returns `false` if there is no such glyph, or current font is not scalable.
7969 /// Group: text_api
7970 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7971   NVGstate* state = nvg__getState(ctx);
7972   ctx.fs.fontId = state.fontId;
7973   return ctx.fs.toPath(ctx, dch, bounds);
7974 }
7975 
7976 static if (is(typeof(&fons__nvg__bounds))) {
7977   public enum NanoVegaHasCharPathBounds = true; ///
7978 } else {
7979   public enum NanoVegaHasCharPathBounds = false; ///
7980 }
7981 
7982 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7983 /// The glyph is not scaled in any way.
7984 /// Returns `false` if there is no such glyph, or current font is not scalable.
7985 /// Group: text_api
7986 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7987   NVGstate* state = nvg__getState(ctx);
7988   ctx.fs.fontId = state.fontId;
7989   return ctx.fs.getPathBounds(dch, bounds);
7990 }
7991 
7992 /** [charOutline] will return [NVGPathOutline].
7993 
7994  some usage samples:
7995 
7996  ---
7997     float[4] bounds = void;
7998 
7999     nvg.scale(0.5, 0.5);
8000     nvg.translate(500, 800);
8001     nvg.evenOddFill;
8002 
8003     nvg.newPath();
8004     nvg.charToPath('&', bounds[]);
8005     conwriteln(bounds[]);
8006     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
8007     nvg.strokeColor(NVGColor("#0f0"));
8008     nvg.strokeWidth = 3;
8009     nvg.fill();
8010     nvg.stroke();
8011     // glyph bounds
8012     nvg.newPath();
8013     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8014     nvg.strokeColor(NVGColor("#00f"));
8015     nvg.stroke();
8016 
8017     nvg.newPath();
8018     nvg.charToPath('g', bounds[]);
8019     conwriteln(bounds[]);
8020     nvg.fill();
8021     nvg.strokeColor(NVGColor("#0f0"));
8022     nvg.stroke();
8023     // glyph bounds
8024     nvg.newPath();
8025     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8026     nvg.strokeColor(NVGColor("#00f"));
8027     nvg.stroke();
8028 
8029     nvg.newPath();
8030     nvg.moveTo(0, 0);
8031     nvg.lineTo(600, 0);
8032     nvg.strokeColor(NVGColor("#0ff"));
8033     nvg.stroke();
8034 
8035     if (auto ol = nvg.charOutline('Q')) {
8036       scope(exit) ol.kill();
8037       nvg.newPath();
8038       conwriteln("==== length: ", ol.length, " ====");
8039       foreach (const ref cmd; ol.commands) {
8040         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8041         assert(cmd.valid);
8042         final switch (cmd.code) {
8043           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8044           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8045           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8046           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;
8047         }
8048       }
8049       nvg.strokeColor(NVGColor("#f00"));
8050       nvg.stroke();
8051     }
8052  ---
8053 
8054  Group: text_api
8055  */
8056 public struct NVGPathOutline {
8057 private nothrow @trusted @nogc:
8058   struct DataStore {
8059     uint rc; // refcount
8060     ubyte* data;
8061     uint used;
8062     uint size;
8063     uint ccount; // number of commands
8064     float[4] bounds = 0; /// outline bounds
8065   nothrow @trusted @nogc:
8066     void putBytes (const(void)[] b) {
8067       if (b.length == 0) return;
8068       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8069       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8070       if (used+cast(uint)b.length > size) {
8071         import core.stdc.stdlib : realloc;
8072         uint newsz = size;
8073         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8074         assert(used+cast(uint)b.length <= newsz);
8075         data = cast(ubyte*)realloc(data, newsz);
8076         if (data is null) assert(0, "NanoVega: out of memory");
8077         size = newsz;
8078       }
8079       import core.stdc.string : memcpy;
8080       memcpy(data+used, b.ptr, b.length);
8081       used += cast(uint)b.length;
8082     }
8083     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8084     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8085   }
8086 
8087   static void incRef (DataStore* ds) {
8088     pragma(inline, true);
8089     if (ds !is null) {
8090       ++ds.rc;
8091       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8092     }
8093   }
8094 
8095   static void decRef (DataStore* ds) {
8096     version(aliced) pragma(inline, true);
8097     if (ds !is null) {
8098       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8099       if (--ds.rc == 0) {
8100         import core.stdc.stdlib : free;
8101         import core.stdc.string : memset;
8102         if (ds.data !is null) free(ds.data);
8103         memset(ds, 0, DataStore.sizeof); // just in case
8104         free(ds);
8105         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8106       }
8107     }
8108   }
8109 
8110 private:
8111   static NVGPathOutline createNew () {
8112     import core.stdc.stdlib : malloc;
8113     import core.stdc.string : memset;
8114     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8115     if (ds is null) assert(0, "NanoVega: out of memory");
8116     memset(ds, 0, DataStore.sizeof);
8117     ds.rc = 1;
8118     NVGPathOutline res;
8119     res.dsaddr = cast(usize)ds;
8120     return res;
8121   }
8122 
8123 private:
8124   usize dsaddr; // fool GC
8125 
8126   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8127 
8128 public:
8129   /// commands
8130   static struct Command {
8131     ///
8132     enum Kind : ubyte {
8133       MoveTo, ///
8134       LineTo, ///
8135       QuadTo, ///
8136       BezierTo, ///
8137       End, /// no more commands (this command is not `valid`!)
8138 
8139     }
8140     Kind code; ///
8141     const(float)[] args; ///
8142     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8143 
8144     static uint arglen (Kind code) pure nothrow @safe @nogc {
8145       pragma(inline, true);
8146       return
8147         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8148         code == Kind.QuadTo ? 4 :
8149         code == Kind.BezierTo ? 6 :
8150         0;
8151     }
8152 
8153     /// perform NanoVega command with stored data.
8154     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8155       if (ctx is null) return;
8156       final switch (code) {
8157         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8158         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8159         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8160         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8161         case Kind.End: break;
8162       }
8163     }
8164 
8165     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8166     void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8167       if (ctx is null || !valid) return;
8168       float[6] pts = void;
8169       pts[0..args.length] = args[];
8170       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8171       final switch (code) {
8172         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8173         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8174         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8175         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8176         case Kind.End: break;
8177       }
8178     }
8179   }
8180 
8181 public:
8182   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8183   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8184     auto res = createNew();
8185     res.ds.putCommand(Command.Kind.MoveTo);
8186     res.ds.putArgs(x0, y0);
8187     res.ds.putCommand(Command.Kind.QuadTo);
8188     res.ds.putArgs(cx, cy, x, y);
8189     return res;
8190   }
8191 
8192   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8193   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) {
8194     auto res = createNew();
8195     res.ds.putCommand(Command.Kind.MoveTo);
8196     res.ds.putArgs(x1, y1);
8197     res.ds.putCommand(Command.Kind.BezierTo);
8198     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8199     return res;
8200   }
8201 
8202 public:
8203   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8204   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8205 
8206   void opAssign() (const scope auto ref NVGPathOutline a) {
8207     incRef(cast(DataStore*)a.dsaddr);
8208     decRef(cast(DataStore*)dsaddr);
8209     dsaddr = a.dsaddr;
8210   }
8211 
8212   /// Clear storage.
8213   void clear () {
8214     pragma(inline, true);
8215     decRef(ds);
8216     dsaddr = 0;
8217   }
8218 
8219   /// Is this outline empty?
8220   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8221 
8222   /// Returns number of commands in outline.
8223   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8224 
8225   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8226   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8227 
8228   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8229   NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8230 
8231   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8232   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8233     import core.stdc.string : memset;
8234 
8235     NVGPathOutline res;
8236     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8237 
8238     // check if we need to flatten the path
8239     if (tfm is null) {
8240       bool dowork = false;
8241       foreach (const ref cs; commands) {
8242         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8243           dowork = true;
8244           break;
8245         }
8246       }
8247       if (!dowork) { res = this; return res; } // nothing to do
8248     }
8249 
8250     NVGcontextinternal ctx;
8251     memset(&ctx, 0, ctx.sizeof);
8252     ctx.cache = nvg__allocPathCache();
8253     scope(exit) {
8254       import core.stdc.stdlib : free;
8255       nvg__deletePathCache(ctx.cache);
8256     }
8257 
8258     ctx.tessTol = 0.25f;
8259     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8260     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8261     ctx.distTol = 0.01f;
8262     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8263 
8264     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8265 
8266     // has some curves or transformations, convert path
8267     res = createNew();
8268     float[8] args = void;
8269 
8270     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8271 
8272     float lastX = float.max, lastY = float.max;
8273     bool lastWasMove = false;
8274 
8275     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8276       if (tfm !is null) tfm.point(x, y);
8277       bool isMove = (cmd == Command.Kind.MoveTo);
8278       if (isMove) {
8279         // moveto
8280         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8281       } else {
8282         // lineto
8283         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8284       }
8285       lastWasMove = isMove;
8286       lastX = x;
8287       lastY = y;
8288       res.ds.putCommand(cmd);
8289       res.ds.putArgs(x, y);
8290       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8291       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8292       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8293       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8294     }
8295 
8296     // sorry for this pasta
8297     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 {
8298       ctx.cache.npoints = 0;
8299       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8300         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8301       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8302         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8303       } else {
8304         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8305       }
8306       // add generated points
8307       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8308     }
8309 
8310     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8311       flattenBezier(
8312         x0, y0,
8313         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8314         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8315         x, y,
8316         0,
8317       );
8318     }
8319 
8320     float cx = 0, cy = 0;
8321     foreach (const ref cs; commands) {
8322       switch (cs.code) {
8323         case Command.Kind.LineTo:
8324         case Command.Kind.MoveTo:
8325           addPoint(cs.args[0], cs.args[1], cs.code);
8326           cx = cs.args[0];
8327           cy = cs.args[1];
8328           break;
8329         case Command.Kind.QuadTo:
8330           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8331           cx = cs.args[2];
8332           cy = cs.args[3];
8333           break;
8334         case Command.Kind.BezierTo:
8335           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8336           cx = cs.args[4];
8337           cy = cs.args[5];
8338           break;
8339         default:
8340           break;
8341       }
8342     }
8343 
8344     return res;
8345   }
8346 
8347   /// Returns forward range with all glyph commands.
8348   auto commands () const nothrow @trusted @nogc {
8349     static struct Range {
8350     private nothrow @trusted @nogc:
8351       usize dsaddr;
8352       uint cpos; // current position in data
8353       uint cleft; // number of commands left
8354       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8355     public:
8356       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8357       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8358       void opAssign() (const scope auto ref Range a) {
8359         incRef(cast(DataStore*)a.dsaddr);
8360         decRef(cast(DataStore*)dsaddr);
8361         dsaddr = a.dsaddr;
8362         cpos = a.cpos;
8363         cleft = a.cleft;
8364       }
8365       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8366       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8367       @property int length () const pure { pragma(inline, true); return cleft; }
8368       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8369       @property Command front () const {
8370         Command res = void;
8371         if (cleft > 0) {
8372           res.code = cast(Command.Kind)data[cpos];
8373           switch (res.code) {
8374             case Command.Kind.MoveTo:
8375             case Command.Kind.LineTo:
8376               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8377               break;
8378             case Command.Kind.QuadTo:
8379               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8380               break;
8381             case Command.Kind.BezierTo:
8382               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8383               break;
8384             default:
8385               res.code = Command.Kind.End;
8386               res.args = null;
8387               break;
8388           }
8389         } else {
8390           res.code = Command.Kind.End;
8391           res.args = null;
8392         }
8393         return res;
8394       }
8395       void popFront () {
8396         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8397         --cleft;
8398         switch (data[cpos]) {
8399           case Command.Kind.MoveTo:
8400           case Command.Kind.LineTo:
8401             cpos += 1+1*2*cast(uint)float.sizeof;
8402             break;
8403           case Command.Kind.QuadTo:
8404             cpos += 1+2*2*cast(uint)float.sizeof;
8405             break;
8406           case Command.Kind.BezierTo:
8407             cpos += 1+3*2*cast(uint)float.sizeof;
8408             break;
8409           default:
8410             cleft = 0;
8411             break;
8412         }
8413       }
8414     }
8415     if (dsaddr) {
8416       incRef(cast(DataStore*)dsaddr); // range anchors it
8417       return Range(dsaddr, 0, ds.ccount);
8418     } else {
8419       return Range.init;
8420     }
8421   }
8422 }
8423 
8424 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8425 
8426 /// Destroy glyph outiline and free allocated memory.
8427 /// Group: text_api
8428 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8429   pragma(inline, true);
8430   ol.clear();
8431 }
8432 
8433 static if (is(typeof(&fons__nvg__toOutline))) {
8434   public enum NanoVegaHasCharOutline = true; ///
8435 } else {
8436   public enum NanoVegaHasCharOutline = false; ///
8437 }
8438 
8439 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8440 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8441 /// Returns `null` if there is no such glyph, or current font is not scalable.
8442 /// Group: text_api
8443 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8444   import core.stdc.stdlib : malloc;
8445   import core.stdc.string : memcpy;
8446   NVGstate* state = nvg__getState(ctx);
8447   ctx.fs.fontId = state.fontId;
8448   auto oline = NVGPathOutline.createNew();
8449   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8450   return oline;
8451 }
8452 
8453 
8454 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8455   pragma(inline, true);
8456   return (cast(int)(a/d+0.5f))*d;
8457 }
8458 
8459 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8460   pragma(inline, true);
8461   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8462 }
8463 
8464 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8465   int[4] dirty = void;
8466   if (ctx.fs.validateTexture(dirty.ptr)) {
8467     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8468     // Update texture
8469     if (fontImage.valid) {
8470       int iw, ih;
8471       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8472       int x = dirty[0];
8473       int y = dirty[1];
8474       int w = dirty[2]-dirty[0];
8475       int h = dirty[3]-dirty[1];
8476       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8477     }
8478   }
8479 }
8480 
8481 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8482   int iw, ih;
8483   nvg__flushTextTexture(ctx);
8484   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8485   // if next fontImage already have a texture
8486   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8487     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8488   } else {
8489     // calculate the new font image size and create it
8490     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8491     if (iw > ih) ih *= 2; else iw *= 2;
8492     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8493     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8494     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8495       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8496       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8497     }
8498   }
8499   ++ctx.fontImageIdx;
8500   ctx.fs.resetAtlas(iw, ih);
8501   return true;
8502 }
8503 
8504 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8505   NVGstate* state = nvg__getState(ctx);
8506   NVGPaint paint = state.fill;
8507 
8508   // Render triangles.
8509   paint.image = ctx.fontImages[ctx.fontImageIdx];
8510 
8511   // Apply global alpha
8512   paint.innerColor.a *= state.alpha;
8513   paint.middleColor.a *= state.alpha;
8514   paint.outerColor.a *= state.alpha;
8515 
8516   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8517 
8518   ++ctx.drawCallCount;
8519   ctx.textTriCount += nverts/3;
8520 }
8521 
8522 /// Draws text string at specified location. Returns next x position.
8523 /// Group: text_api
8524 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8525   NVGstate* state = nvg__getState(ctx);
8526   FONSTextIter!T iter, prevIter;
8527   FONSQuad q;
8528   NVGVertex* verts;
8529   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8530   float invscale = 1.0f/scale;
8531   int cverts = 0;
8532   int nverts = 0;
8533 
8534   if (state.fontId == FONS_INVALID) return x;
8535   if (str.length == 0) return x;
8536 
8537   ctx.fs.size = state.fontSize*scale;
8538   ctx.fs.spacing = state.letterSpacing*scale;
8539   ctx.fs.blur = state.fontBlur*scale;
8540   ctx.fs.textAlign = state.textAlign;
8541   ctx.fs.fontId = state.fontId;
8542 
8543   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8544   verts = nvg__allocTempVerts(ctx, cverts);
8545   if (verts is null) return x;
8546 
8547   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8548   prevIter = iter;
8549   while (iter.next(q)) {
8550     float[4*2] c = void;
8551     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8552       if (nverts != 0) {
8553         // TODO: add back-end bit to do this just once per frame
8554         nvg__flushTextTexture(ctx);
8555         nvg__renderText(ctx, verts, nverts);
8556         nverts = 0;
8557       }
8558       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8559       iter = prevIter;
8560       iter.next(q); // try again
8561       if (iter.prevGlyphIndex < 0) {
8562         // still can not find glyph, try replacement
8563         iter = prevIter;
8564         if (!iter.getDummyChar(q)) break;
8565       }
8566     }
8567     prevIter = iter;
8568     // transform corners
8569     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8570     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8571     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8572     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8573     // create triangles
8574     if (nverts+6 <= cverts) {
8575       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8576       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8577       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8578       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8579       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8580       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8581     }
8582   }
8583 
8584   // TODO: add back-end bit to do this just once per frame
8585   if (nverts > 0) {
8586     nvg__flushTextTexture(ctx);
8587     nvg__renderText(ctx, verts, nverts);
8588   }
8589 
8590   return iter.nextx/scale;
8591 }
8592 
8593 /** Draws multi-line text string at specified location wrapped at the specified width.
8594  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8595  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8596  *
8597  * Group: text_api
8598  */
8599 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8600   NVGstate* state = nvg__getState(ctx);
8601   if (state.fontId == FONS_INVALID) return;
8602 
8603   NVGTextRow!T[2] rows;
8604   auto oldAlign = state.textAlign;
8605   scope(exit) state.textAlign = oldAlign;
8606   auto halign = state.textAlign.horizontal;
8607   float lineh = 0;
8608 
8609   ctx.textMetrics(null, null, &lineh);
8610   state.textAlign.horizontal = NVGTextAlign.H.Left;
8611   for (;;) {
8612     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8613     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8614     if (rres.length == 0) break;
8615     foreach (ref row; rres) {
8616       final switch (halign) {
8617         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8618         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8619         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8620       }
8621       y += lineh*state.lineHeight;
8622     }
8623     str = rres[$-1].rest;
8624   }
8625 }
8626 
8627 private template isGoodPositionDelegate(DG) {
8628   private DG dg;
8629   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8630              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8631     enum isGoodPositionDelegate = true;
8632   else
8633     enum isGoodPositionDelegate = false;
8634 }
8635 
8636 /** Calculates the glyph x positions of the specified text.
8637  * Measured values are returned in local coordinate space.
8638  *
8639  * Group: text_api
8640  */
8641 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8642 if (isAnyCharType!T)
8643 {
8644   if (str.length == 0 || positions.length == 0) return positions[0..0];
8645   usize posnum;
8646   auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) {
8647     positions.ptr[posnum++] = pos;
8648     return (posnum < positions.length);
8649   });
8650   return positions[0..len];
8651 }
8652 
8653 /// Ditto.
8654 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8655 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8656 {
8657   import std.traits : ReturnType;
8658   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8659 
8660   NVGstate* state = nvg__getState(ctx);
8661   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8662   float invscale = 1.0f/scale;
8663   FONSTextIter!T iter, prevIter;
8664   FONSQuad q;
8665   int npos = 0;
8666 
8667   if (str.length == 0) return 0;
8668 
8669   ctx.fs.size = state.fontSize*scale;
8670   ctx.fs.spacing = state.letterSpacing*scale;
8671   ctx.fs.blur = state.fontBlur*scale;
8672   ctx.fs.textAlign = state.textAlign;
8673   ctx.fs.fontId = state.fontId;
8674 
8675   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8676   prevIter = iter;
8677   while (iter.next(q)) {
8678     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8679       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8680       iter = prevIter;
8681       iter.next(q); // try again
8682       if (iter.prevGlyphIndex < 0) {
8683         // still can not find glyph, try replacement
8684         iter = prevIter;
8685         if (!iter.getDummyChar(q)) break;
8686       }
8687     }
8688     prevIter = iter;
8689     NVGGlyphPosition position = void; //WARNING!
8690     position.strpos = cast(usize)(iter.stringp-str.ptr);
8691     position.x = iter.x*invscale;
8692     position.minx = nvg__min(iter.x, q.x0)*invscale;
8693     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8694     ++npos;
8695     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8696   }
8697 
8698   return npos;
8699 }
8700 
8701 private template isGoodRowDelegate(CT, DG) {
8702   private DG dg;
8703   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8704              is(typeof({ NVGTextRow!CT row; dg(row); })))
8705     enum isGoodRowDelegate = true;
8706   else
8707     enum isGoodRowDelegate = false;
8708 }
8709 
8710 /** Breaks the specified text into lines.
8711  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8712  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8713  *
8714  * Group: text_api
8715  */
8716 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8717 if (isAnyCharType!T)
8718 {
8719   if (rows.length == 0) return rows;
8720   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8721   int nrow = 0;
8722   auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) {
8723     rows[nrow++] = row;
8724     return (nrow < rows.length);
8725   });
8726   return rows[0..count];
8727 }
8728 
8729 /** Breaks the specified text into lines.
8730  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8731  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8732  * Returns number of rows.
8733  *
8734  * Group: text_api
8735  */
8736 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8737 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8738 {
8739   import std.traits : ReturnType;
8740   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8741 
8742   enum NVGcodepointType : int {
8743     Space,
8744     NewLine,
8745     Char,
8746   }
8747 
8748   NVGstate* state = nvg__getState(ctx);
8749   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8750   float invscale = 1.0f/scale;
8751   FONSTextIter!T iter, prevIter;
8752   FONSQuad q;
8753   int nrows = 0;
8754   float rowStartX = 0;
8755   float rowWidth = 0;
8756   float rowMinX = 0;
8757   float rowMaxX = 0;
8758   int rowStart = 0;
8759   int rowEnd = 0;
8760   int wordStart = 0;
8761   float wordStartX = 0;
8762   float wordMinX = 0;
8763   int breakEnd = 0;
8764   float breakWidth = 0;
8765   float breakMaxX = 0;
8766   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8767   uint pcodepoint = 0;
8768 
8769   if (state.fontId == FONS_INVALID) return 0;
8770   if (str.length == 0 || dg is null) return 0;
8771 
8772   ctx.fs.size = state.fontSize*scale;
8773   ctx.fs.spacing = state.letterSpacing*scale;
8774   ctx.fs.blur = state.fontBlur*scale;
8775   ctx.fs.textAlign = state.textAlign;
8776   ctx.fs.fontId = state.fontId;
8777 
8778   breakRowWidth *= scale;
8779 
8780   enum Phase {
8781     Normal, // searching for breaking point
8782     SkipBlanks, // skip leading blanks
8783   }
8784   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8785 
8786   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8787   prevIter = iter;
8788   while (iter.next(q)) {
8789     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8790       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8791       iter = prevIter;
8792       iter.next(q); // try again
8793       if (iter.prevGlyphIndex < 0) {
8794         // still can not find glyph, try replacement
8795         iter = prevIter;
8796         if (!iter.getDummyChar(q)) break;
8797       }
8798     }
8799     prevIter = iter;
8800     switch (iter.codepoint) {
8801       case 9: // \t
8802       case 11: // \v
8803       case 12: // \f
8804       case 32: // space
8805       case 0x00a0: // NBSP
8806         type = NVGcodepointType.Space;
8807         break;
8808       case 10: // \n
8809         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8810         break;
8811       case 13: // \r
8812         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8813         break;
8814       case 0x0085: // NEL
8815       case 0x2028: // Line Separator
8816       case 0x2029: // Paragraph Separator
8817         type = NVGcodepointType.NewLine;
8818         break;
8819       default:
8820         type = NVGcodepointType.Char;
8821         break;
8822     }
8823     if (phase == Phase.SkipBlanks) {
8824       // fix row start
8825       rowStart = cast(int)(iter.stringp-str.ptr);
8826       rowEnd = rowStart;
8827       rowStartX = iter.x;
8828       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8829       rowMinX = q.x0-rowStartX;
8830       rowMaxX = q.x1-rowStartX;
8831       wordStart = rowStart;
8832       wordStartX = iter.x;
8833       wordMinX = q.x0-rowStartX;
8834       breakEnd = rowStart;
8835       breakWidth = 0.0;
8836       breakMaxX = 0.0;
8837       if (type == NVGcodepointType.Space) continue;
8838       phase = Phase.Normal;
8839     }
8840 
8841     if (type == NVGcodepointType.NewLine) {
8842       // always handle new lines
8843       NVGTextRow!T row;
8844       row.string = str;
8845       row.start = rowStart;
8846       row.end = rowEnd;
8847       row.width = rowWidth*invscale;
8848       row.minx = rowMinX*invscale;
8849       row.maxx = rowMaxX*invscale;
8850       ++nrows;
8851       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8852       phase = Phase.SkipBlanks;
8853     } else {
8854       float nextWidth = iter.nextx-rowStartX;
8855       // track last non-white space character
8856       if (type == NVGcodepointType.Char) {
8857         rowEnd = cast(int)(iter.nextp-str.ptr);
8858         rowWidth = iter.nextx-rowStartX;
8859         rowMaxX = q.x1-rowStartX;
8860       }
8861       // track last end of a word
8862       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8863         breakEnd = cast(int)(iter.stringp-str.ptr);
8864         breakWidth = rowWidth;
8865         breakMaxX = rowMaxX;
8866       }
8867       // track last beginning of a word
8868       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8869         wordStart = cast(int)(iter.stringp-str.ptr);
8870         wordStartX = iter.x;
8871         wordMinX = q.x0-rowStartX;
8872       }
8873       // break to new line when a character is beyond break width
8874       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8875         // the run length is too long, need to break to new line
8876         NVGTextRow!T row;
8877         row.string = str;
8878         if (breakEnd == rowStart) {
8879           // the current word is longer than the row length, just break it from here
8880           row.start = rowStart;
8881           row.end = cast(int)(iter.stringp-str.ptr);
8882           row.width = rowWidth*invscale;
8883           row.minx = rowMinX*invscale;
8884           row.maxx = rowMaxX*invscale;
8885           ++nrows;
8886           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8887           rowStartX = iter.x;
8888           rowStart = cast(int)(iter.stringp-str.ptr);
8889           rowEnd = cast(int)(iter.nextp-str.ptr);
8890           rowWidth = iter.nextx-rowStartX;
8891           rowMinX = q.x0-rowStartX;
8892           rowMaxX = q.x1-rowStartX;
8893           wordStart = rowStart;
8894           wordStartX = iter.x;
8895           wordMinX = q.x0-rowStartX;
8896         } else {
8897           // break the line from the end of the last word, and start new line from the beginning of the new
8898           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8899           row.start = rowStart;
8900           row.end = breakEnd;
8901           row.width = breakWidth*invscale;
8902           row.minx = rowMinX*invscale;
8903           row.maxx = breakMaxX*invscale;
8904           ++nrows;
8905           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8906           rowStartX = wordStartX;
8907           rowStart = wordStart;
8908           rowEnd = cast(int)(iter.nextp-str.ptr);
8909           rowWidth = iter.nextx-rowStartX;
8910           rowMinX = wordMinX;
8911           rowMaxX = q.x1-rowStartX;
8912           // no change to the word start
8913         }
8914         // set null break point
8915         breakEnd = rowStart;
8916         breakWidth = 0.0;
8917         breakMaxX = 0.0;
8918       }
8919     }
8920 
8921     pcodepoint = iter.codepoint;
8922     ptype = type;
8923   }
8924 
8925   // break the line from the end of the last word, and start new line from the beginning of the new
8926   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8927     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8928     NVGTextRow!T row;
8929     row.string = str;
8930     row.start = rowStart;
8931     row.end = cast(int)str.length;
8932     row.width = rowWidth*invscale;
8933     row.minx = rowMinX*invscale;
8934     row.maxx = rowMaxX*invscale;
8935     ++nrows;
8936     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8937   }
8938 
8939   return nrows;
8940 }
8941 
8942 /** Returns iterator which you can use to calculate text bounds and advancement.
8943  * This is usable when you need to do some text layouting with wrapping, to avoid
8944  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8945  * algorithm. Note that you can copy the returned struct to save iterator state.
8946  *
8947  * You can check if iterator is valid with [valid] property, put new chars with
8948  * [put] method, get current advance with [advance] property, and current
8949  * bounds with `getBounds(ref float[4] bounds)` method.
8950  *
8951  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8952  *
8953  * Group: text_api
8954  */
8955 public struct TextBoundsIterator {
8956 private:
8957   NVGContext ctx;
8958   FONSTextBoundsIterator fsiter; // fontstash iterator
8959   float scale, invscale, xscaled, yscaled;
8960   // font settings
8961   float fsSize, fsSpacing, fsBlur;
8962   int fsFontId;
8963   NVGTextAlign fsAlign;
8964 
8965 public:
8966   /// Setups iteration. Takes current font parameters from the given NanoVega context.
8967   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8968 
8969   /// Resets iteration. Takes current font parameters from the given NanoVega context.
8970   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8971     fsiter = fsiter.init;
8972     this = this.init;
8973     if (actx is null) return;
8974     NVGstate* state = nvg__getState(actx);
8975     if (state is null) return;
8976     if (state.fontId == FONS_INVALID) { ctx = null; return; }
8977 
8978     ctx = actx;
8979     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8980     invscale = 1.0f/scale;
8981 
8982     fsSize = state.fontSize*scale;
8983     fsSpacing = state.letterSpacing*scale;
8984     fsBlur = state.fontBlur*scale;
8985     fsAlign = state.textAlign;
8986     fsFontId = state.fontId;
8987     restoreFont();
8988 
8989     xscaled = ax*scale;
8990     yscaled = ay*scale;
8991     fsiter.reset(ctx.fs, xscaled, yscaled);
8992   }
8993 
8994   /// Restart iteration. Will not restore font.
8995   void restart () nothrow @trusted @nogc {
8996     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8997   }
8998 
8999   /// Restore font settings for the context.
9000   void restoreFont () nothrow @trusted @nogc {
9001     if (ctx !is null) {
9002       ctx.fs.size = fsSize;
9003       ctx.fs.spacing = fsSpacing;
9004       ctx.fs.blur = fsBlur;
9005       ctx.fs.textAlign = fsAlign;
9006       ctx.fs.fontId = fsFontId;
9007     }
9008   }
9009 
9010   /// Is this iterator valid?
9011   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
9012 
9013   /// Add chars.
9014   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
9015 
9016   /// Returns current advance
9017   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
9018 
9019   /// Returns current text bounds.
9020   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
9021     if (ctx !is null) {
9022       fsiter.getBounds(bounds);
9023       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
9024       bounds[0] *= invscale;
9025       bounds[1] *= invscale;
9026       bounds[2] *= invscale;
9027       bounds[3] *= invscale;
9028     } else {
9029       bounds[] = 0;
9030     }
9031   }
9032 
9033   /// Returns current horizontal text bounds.
9034   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
9035     if (ctx !is null) {
9036       fsiter.getHBounds(xmin, xmax);
9037       xmin *= invscale;
9038       xmax *= invscale;
9039     }
9040   }
9041 
9042   /// Returns current vertical text bounds.
9043   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9044     if (ctx !is null) {
9045       //fsiter.getVBounds(ymin, ymax);
9046       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9047       ymin *= invscale;
9048       ymax *= invscale;
9049     }
9050   }
9051 }
9052 
9053 /// Returns font line height (without line spacing), measured in local coordinate space.
9054 /// Group: text_api
9055 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9056   float res = void;
9057   ctx.textMetrics(null, null, &res);
9058   return res;
9059 }
9060 
9061 /// Returns font ascender (positive), measured in local coordinate space.
9062 /// Group: text_api
9063 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9064   float res = void;
9065   ctx.textMetrics(&res, null, null);
9066   return res;
9067 }
9068 
9069 /// Returns font descender (negative), measured in local coordinate space.
9070 /// Group: text_api
9071 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9072   float res = void;
9073   ctx.textMetrics(null, &res, null);
9074   return res;
9075 }
9076 
9077 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9078  * Measured values are returned in local coordinate space.
9079  *
9080  * Group: text_api
9081  */
9082 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9083   float[4] bnd = void;
9084   ctx.textBounds(0, 0, str, bnd[]);
9085   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9086     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9087     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9088   } else {
9089     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9090     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9091   }
9092 }
9093 
9094 /** Measures the specified text string. Returns horizontal size of the measured text.
9095  * Measured values are returned in local coordinate space.
9096  *
9097  * Group: text_api
9098  */
9099 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9100   float w = void;
9101   ctx.textExtents(str, &w, null);
9102   return w;
9103 }
9104 
9105 /** Measures the specified text string. Parameter bounds should be a float[4],
9106  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9107  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9108  * Measured values are returned in local coordinate space.
9109  *
9110  * Group: text_api
9111  */
9112 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9113 if (isAnyCharType!T)
9114 {
9115   NVGstate* state = nvg__getState(ctx);
9116 
9117   if (state.fontId == FONS_INVALID) {
9118     bounds[] = 0;
9119     return 0;
9120   }
9121 
9122   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9123   ctx.fs.size = state.fontSize*scale;
9124   ctx.fs.spacing = state.letterSpacing*scale;
9125   ctx.fs.blur = state.fontBlur*scale;
9126   ctx.fs.textAlign = state.textAlign;
9127   ctx.fs.fontId = state.fontId;
9128 
9129   float[4] b = void;
9130   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9131   immutable float invscale = 1.0f/scale;
9132   if (bounds.length) {
9133     // use line bounds for height
9134     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9135     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9136     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9137     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9138     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9139   }
9140   return width*invscale;
9141 }
9142 
9143 /// Ditto.
9144 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9145   NVGstate* state = nvg__getState(ctx);
9146   NVGTextRow!T[2] rows;
9147   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9148   float invscale = 1.0f/scale;
9149   float lineh = 0, rminy = 0, rmaxy = 0;
9150   float minx, miny, maxx, maxy;
9151 
9152   if (state.fontId == FONS_INVALID) {
9153     bounds[] = 0;
9154     return;
9155   }
9156 
9157   auto oldAlign = state.textAlign;
9158   scope(exit) state.textAlign = oldAlign;
9159   auto halign = state.textAlign.horizontal;
9160 
9161   ctx.textMetrics(null, null, &lineh);
9162   state.textAlign.horizontal = NVGTextAlign.H.Left;
9163 
9164   minx = maxx = x;
9165   miny = maxy = y;
9166 
9167   ctx.fs.size = state.fontSize*scale;
9168   ctx.fs.spacing = state.letterSpacing*scale;
9169   ctx.fs.blur = state.fontBlur*scale;
9170   ctx.fs.textAlign = state.textAlign;
9171   ctx.fs.fontId = state.fontId;
9172   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9173   rminy *= invscale;
9174   rmaxy *= invscale;
9175 
9176   for (;;) {
9177     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9178     if (rres.length == 0) break;
9179     foreach (ref row; rres) {
9180       float rminx, rmaxx, dx = 0;
9181       // horizontal bounds
9182       final switch (halign) {
9183         case NVGTextAlign.H.Left: dx = 0; break;
9184         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9185         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9186       }
9187       rminx = x+row.minx+dx;
9188       rmaxx = x+row.maxx+dx;
9189       minx = nvg__min(minx, rminx);
9190       maxx = nvg__max(maxx, rmaxx);
9191       // vertical bounds
9192       miny = nvg__min(miny, y+rminy);
9193       maxy = nvg__max(maxy, y+rmaxy);
9194       y += lineh*state.lineHeight;
9195     }
9196     str = rres[$-1].rest;
9197   }
9198 
9199   if (bounds.length) {
9200     if (bounds.length > 0) bounds.ptr[0] = minx;
9201     if (bounds.length > 1) bounds.ptr[1] = miny;
9202     if (bounds.length > 2) bounds.ptr[2] = maxx;
9203     if (bounds.length > 3) bounds.ptr[3] = maxy;
9204   }
9205 }
9206 
9207 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9208 /// Group: text_api
9209 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9210   NVGstate* state = nvg__getState(ctx);
9211 
9212   if (state.fontId == FONS_INVALID) {
9213     if (ascender !is null) *ascender *= 0;
9214     if (descender !is null) *descender *= 0;
9215     if (lineh !is null) *lineh *= 0;
9216     return;
9217   }
9218 
9219   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9220   immutable float invscale = 1.0f/scale;
9221 
9222   ctx.fs.size = state.fontSize*scale;
9223   ctx.fs.spacing = state.letterSpacing*scale;
9224   ctx.fs.blur = state.fontBlur*scale;
9225   ctx.fs.textAlign = state.textAlign;
9226   ctx.fs.fontId = state.fontId;
9227 
9228   ctx.fs.getVertMetrics(ascender, descender, lineh);
9229   if (ascender !is null) *ascender *= invscale;
9230   if (descender !is null) *descender *= invscale;
9231   if (lineh !is null) *lineh *= invscale;
9232 }
9233 
9234 
9235 // ////////////////////////////////////////////////////////////////////////// //
9236 // fontstash
9237 // ////////////////////////////////////////////////////////////////////////// //
9238 import core.stdc.stdlib : malloc, realloc, free;
9239 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9240 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9241 
9242 public:
9243 // welcome to version hell!
9244 version(nanovg_force_stb_ttf) {
9245 } else {
9246   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9247 }
9248 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9249 //version(nanovg_ignore_mono);
9250 
9251 version(nanovg_force_stb_ttf) {
9252   private enum NanoVegaForceFreeType = false;
9253 } else {
9254   version (nanovg_builtin_freetype_bindings) {
9255     version(Posix) {
9256       private enum NanoVegaForceFreeType = true;
9257     } else {
9258       private enum NanoVegaForceFreeType = false;
9259     }
9260   } else {
9261     version(Posix) {
9262       private enum NanoVegaForceFreeType = true;
9263     } else {
9264       private enum NanoVegaForceFreeType = false;
9265     }
9266   }
9267 }
9268 
9269 version(nanovg_use_freetype_ii) {
9270   enum NanoVegaIsUsingSTBTTF = false;
9271   //pragma(msg, "iv.freetype: forced");
9272 } else {
9273   static if (NanoVegaForceFreeType) {
9274     enum NanoVegaIsUsingSTBTTF = false;
9275   } else {
9276     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9277       import iv.stb.ttf;
9278       enum NanoVegaIsUsingSTBTTF = true;
9279       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9280     } else static if (__traits(compiles, { import arsd.ttf; })) {
9281       import arsd.ttf;
9282       enum NanoVegaIsUsingSTBTTF = true;
9283       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9284     } else static if (__traits(compiles, { import stb_truetype; })) {
9285       import stb_truetype;
9286       enum NanoVegaIsUsingSTBTTF = true;
9287       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9288     } else static if (__traits(compiles, { import iv.freetype; })) {
9289       version (nanovg_builtin_freetype_bindings) {
9290         enum NanoVegaIsUsingSTBTTF = false;
9291         version = nanovg_builtin_freetype_bindings;
9292       } else {
9293         import iv.freetype;
9294         enum NanoVegaIsUsingSTBTTF = false;
9295       }
9296       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9297     } else {
9298       static assert(0, "no stb_ttf/iv.freetype found!");
9299     }
9300   }
9301 }
9302 
9303 
9304 // ////////////////////////////////////////////////////////////////////////// //
9305 //version = nanovg_ft_mono;
9306 
9307 /// Invald font id.
9308 /// Group: font_stash
9309 public enum FONS_INVALID = -1;
9310 
9311 public enum FONSBitmapFlag : uint {
9312   Required = 0,
9313   Optional = 1,
9314 }
9315 
9316 public enum FONSError : int {
9317   NoError = 0,
9318   AtlasFull = 1, // Font atlas is full.
9319   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.
9320   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9321   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9322 }
9323 
9324 /// Initial parameters for new FontStash.
9325 /// Group: font_stash
9326 public struct FONSParams {
9327   enum Flag : uint {
9328     ZeroTopLeft    = 0U, // default
9329     ZeroBottomLeft = 1U,
9330   }
9331   int width, height;
9332   Flag flags = Flag.ZeroTopLeft;
9333   void* userPtr;
9334   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9335   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9336   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9337   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9338   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9339 }
9340 
9341 //TODO: document this
9342 public struct FONSQuad {
9343   float x0=0, y0=0, s0=0, t0=0;
9344   float x1=0, y1=0, s1=0, t1=0;
9345 }
9346 
9347 //TODO: document this
9348 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9349   alias CharType = CT;
9350   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9351   uint codepoint;
9352   short isize, iblur;
9353   FONSContext stash;
9354   FONSfont* font;
9355   int prevGlyphIndex;
9356   const(CT)* s; // string
9357   const(CT)* n; // next
9358   const(CT)* e; // end
9359   FONSBitmapFlag bitmapOption;
9360   static if (is(CT == char)) {
9361     uint utf8state;
9362   }
9363 
9364   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9365   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9366 
9367   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9368   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9369   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9370 
9371   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9372     import core.stdc.string : memset;
9373 
9374     memset(&this, 0, this.sizeof);
9375     if (astash is null) return false;
9376 
9377     FONSstate* state = astash.getState;
9378 
9379     if (state.font < 0 || state.font >= astash.nfonts) return false;
9380     font = astash.fonts[state.font];
9381     if (font is null || font.fdata is null) return false;
9382 
9383     isize = cast(short)(state.size*10.0f);
9384     iblur = cast(short)state.blur;
9385     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9386 
9387     // align horizontally
9388     if (state.talign.left) {
9389       // empty
9390     } else if (state.talign.right) {
9391       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9392       ax -= width;
9393     } else if (state.talign.center) {
9394       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9395       ax -= width*0.5f;
9396     }
9397 
9398     // align vertically
9399     ay += astash.getVertAlign(font, state.talign, isize);
9400 
9401     x = nextx = ax;
9402     y = nexty = ay;
9403     spacing = state.spacing;
9404 
9405     if (astr.ptr is null) {
9406            static if (is(CharType == char)) astr = "";
9407       else static if (is(CharType == wchar)) astr = ""w;
9408       else static if (is(CharType == dchar)) astr = ""d;
9409       else static assert(0, "wtf?!");
9410     }
9411     s = astr.ptr;
9412     n = astr.ptr;
9413     e = astr.ptr+astr.length;
9414 
9415     codepoint = 0;
9416     prevGlyphIndex = -1;
9417     bitmapOption = abitmapOption;
9418     stash = astash;
9419 
9420     return true;
9421   }
9422 
9423   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9424     if (stash is null || font is null) return false;
9425     // get glyph and quad
9426     x = nextx;
9427     y = nexty;
9428     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9429     if (glyph !is null) {
9430       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9431       prevGlyphIndex = glyph.index;
9432       return true;
9433     } else {
9434       prevGlyphIndex = -1;
9435       return false;
9436     }
9437   }
9438 
9439   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9440     if (stash is null || font is null) return false;
9441     FONSglyph* glyph = null;
9442     static if (is(CharType == char)) {
9443       const(char)* str = this.n;
9444       this.s = this.n;
9445       if (str is this.e) return false;
9446       const(char)* e = this.e;
9447       for (; str !is e; ++str) {
9448         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9449         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9450         if (utf8state) continue;
9451         ++str; // 'cause we'll break anyway
9452         // get glyph and quad
9453         x = nextx;
9454         y = nexty;
9455         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9456         if (glyph !is null) {
9457           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9458           prevGlyphIndex = glyph.index;
9459         } else {
9460           prevGlyphIndex = -1;
9461         }
9462         break;
9463       }
9464       this.n = str;
9465     } else {
9466       const(CharType)* str = this.n;
9467       this.s = this.n;
9468       if (str is this.e) return false;
9469       codepoint = cast(uint)(*str++);
9470       if (codepoint > dchar.max) codepoint = 0xFFFD;
9471       // get glyph and quad
9472       x = nextx;
9473       y = nexty;
9474       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9475       if (glyph !is null) {
9476         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9477         prevGlyphIndex = glyph.index;
9478       } else {
9479         prevGlyphIndex = -1;
9480       }
9481       this.n = str;
9482     }
9483     return true;
9484   }
9485 }
9486 
9487 
9488 // ////////////////////////////////////////////////////////////////////////// //
9489 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9490 
9491 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9492 version(nanovg_builtin_freetype_bindings) {
9493 pragma(lib, "freetype");
9494 private extern(C) nothrow @trusted @nogc {
9495 private import core.stdc.config : c_long, c_ulong;
9496 alias FT_Pos = c_long;
9497 // config/ftconfig.h
9498 alias FT_Int16 = short;
9499 alias FT_UInt16 = ushort;
9500 alias FT_Int32 = int;
9501 alias FT_UInt32 = uint;
9502 alias FT_Fast = int;
9503 alias FT_UFast = uint;
9504 alias FT_Int64 = long;
9505 alias FT_Uint64 = ulong;
9506 // fttypes.h
9507 alias FT_Bool = ubyte;
9508 alias FT_FWord = short;
9509 alias FT_UFWord = ushort;
9510 alias FT_Char = char;
9511 alias FT_Byte = ubyte;
9512 alias FT_Bytes = FT_Byte*;
9513 alias FT_Tag = FT_UInt32;
9514 alias FT_String = char;
9515 alias FT_Short = short;
9516 alias FT_UShort = ushort;
9517 alias FT_Int = int;
9518 alias FT_UInt = uint;
9519 alias FT_Long = c_long;
9520 alias FT_ULong = c_ulong;
9521 alias FT_F2Dot14 = short;
9522 alias FT_F26Dot6 = c_long;
9523 alias FT_Fixed = c_long;
9524 alias FT_Error = int;
9525 alias FT_Pointer = void*;
9526 alias FT_Offset = usize;
9527 alias FT_PtrDist = ptrdiff_t;
9528 
9529 struct FT_UnitVector {
9530   FT_F2Dot14 x;
9531   FT_F2Dot14 y;
9532 }
9533 
9534 struct FT_Matrix {
9535   FT_Fixed xx, xy;
9536   FT_Fixed yx, yy;
9537 }
9538 
9539 struct FT_Data {
9540   const(FT_Byte)* pointer;
9541   FT_Int length;
9542 }
9543 alias FT_Face = FT_FaceRec*;
9544 struct FT_FaceRec {
9545   FT_Long num_faces;
9546   FT_Long face_index;
9547   FT_Long face_flags;
9548   FT_Long style_flags;
9549   FT_Long num_glyphs;
9550   FT_String* family_name;
9551   FT_String* style_name;
9552   FT_Int num_fixed_sizes;
9553   FT_Bitmap_Size* available_sizes;
9554   FT_Int num_charmaps;
9555   FT_CharMap* charmaps;
9556   FT_Generic generic;
9557   FT_BBox bbox;
9558   FT_UShort units_per_EM;
9559   FT_Short ascender;
9560   FT_Short descender;
9561   FT_Short height;
9562   FT_Short max_advance_width;
9563   FT_Short max_advance_height;
9564   FT_Short underline_position;
9565   FT_Short underline_thickness;
9566   FT_GlyphSlot glyph;
9567   FT_Size size;
9568   FT_CharMap charmap;
9569   FT_Driver driver;
9570   FT_Memory memory;
9571   FT_Stream stream;
9572   FT_ListRec sizes_list;
9573   FT_Generic autohint;
9574   void* extensions;
9575   FT_Face_Internal internal;
9576 }
9577 struct FT_Bitmap_Size {
9578   FT_Short height;
9579   FT_Short width;
9580   FT_Pos size;
9581   FT_Pos x_ppem;
9582   FT_Pos y_ppem;
9583 }
9584 alias FT_CharMap = FT_CharMapRec*;
9585 struct FT_CharMapRec {
9586   FT_Face face;
9587   FT_Encoding encoding;
9588   FT_UShort platform_id;
9589   FT_UShort encoding_id;
9590 }
9591 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9592 struct FT_Generic {
9593   void* data;
9594   FT_Generic_Finalizer finalizer;
9595 }
9596 struct FT_Vector {
9597   FT_Pos x;
9598   FT_Pos y;
9599 }
9600 struct FT_BBox {
9601   FT_Pos xMin, yMin;
9602   FT_Pos xMax, yMax;
9603 }
9604 alias FT_Pixel_Mode = int;
9605 enum {
9606   FT_PIXEL_MODE_NONE = 0,
9607   FT_PIXEL_MODE_MONO,
9608   FT_PIXEL_MODE_GRAY,
9609   FT_PIXEL_MODE_GRAY2,
9610   FT_PIXEL_MODE_GRAY4,
9611   FT_PIXEL_MODE_LCD,
9612   FT_PIXEL_MODE_LCD_V,
9613   FT_PIXEL_MODE_MAX
9614 }
9615 struct FT_Bitmap {
9616   uint rows;
9617   uint width;
9618   int pitch;
9619   ubyte* buffer;
9620   ushort num_grays;
9621   ubyte pixel_mode;
9622   ubyte palette_mode;
9623   void* palette;
9624 }
9625 struct FT_Outline {
9626   short n_contours;
9627   short n_points;
9628   FT_Vector* points;
9629   byte* tags;
9630   short* contours;
9631   int flags;
9632 }
9633 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9634 struct FT_GlyphSlotRec {
9635   FT_Library library;
9636   FT_Face face;
9637   FT_GlyphSlot next;
9638   FT_UInt reserved;
9639   FT_Generic generic;
9640   FT_Glyph_Metrics metrics;
9641   FT_Fixed linearHoriAdvance;
9642   FT_Fixed linearVertAdvance;
9643   FT_Vector advance;
9644   FT_Glyph_Format format;
9645   FT_Bitmap bitmap;
9646   FT_Int bitmap_left;
9647   FT_Int bitmap_top;
9648   FT_Outline outline;
9649   FT_UInt num_subglyphs;
9650   FT_SubGlyph subglyphs;
9651   void* control_data;
9652   c_long control_len;
9653   FT_Pos lsb_delta;
9654   FT_Pos rsb_delta;
9655   void* other;
9656   FT_Slot_Internal internal;
9657 }
9658 alias FT_Size = FT_SizeRec*;
9659 struct FT_SizeRec {
9660   FT_Face face;
9661   FT_Generic generic;
9662   FT_Size_Metrics metrics;
9663   FT_Size_Internal internal;
9664 }
9665 alias FT_Encoding = FT_Tag;
9666 alias FT_Face_Internal = void*;
9667 alias FT_Driver = void*;
9668 alias FT_Memory = void*;
9669 alias FT_Stream = void*;
9670 alias FT_Library = void*;
9671 alias FT_SubGlyph = void*;
9672 alias FT_Slot_Internal = void*;
9673 alias FT_Size_Internal = void*;
9674 alias FT_ListNode = FT_ListNodeRec*;
9675 alias FT_List = FT_ListRec*;
9676 struct FT_ListNodeRec {
9677   FT_ListNode prev;
9678   FT_ListNode next;
9679   void* data;
9680 }
9681 struct FT_ListRec {
9682   FT_ListNode head;
9683   FT_ListNode tail;
9684 }
9685 struct FT_Glyph_Metrics {
9686   FT_Pos width;
9687   FT_Pos height;
9688   FT_Pos horiBearingX;
9689   FT_Pos horiBearingY;
9690   FT_Pos horiAdvance;
9691   FT_Pos vertBearingX;
9692   FT_Pos vertBearingY;
9693   FT_Pos vertAdvance;
9694 }
9695 alias FT_Glyph_Format = FT_Tag;
9696 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9697   pragma(inline, true);
9698   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9699 }
9700 enum : FT_Tag {
9701   FT_GLYPH_FORMAT_NONE = 0,
9702   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9703   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9704   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9705   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9706 }
9707 struct FT_Size_Metrics {
9708   FT_UShort x_ppem;
9709   FT_UShort y_ppem;
9710 
9711   FT_Fixed x_scale;
9712   FT_Fixed y_scale;
9713 
9714   FT_Pos ascender;
9715   FT_Pos descender;
9716   FT_Pos height;
9717   FT_Pos max_advance;
9718 }
9719 enum FT_LOAD_DEFAULT = 0x0U;
9720 enum FT_LOAD_NO_SCALE = 1U<<0;
9721 enum FT_LOAD_NO_HINTING = 1U<<1;
9722 enum FT_LOAD_RENDER = 1U<<2;
9723 enum FT_LOAD_NO_BITMAP = 1U<<3;
9724 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9725 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9726 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9727 enum FT_LOAD_PEDANTIC = 1U<<7;
9728 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9729 enum FT_LOAD_NO_RECURSE = 1U<<10;
9730 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9731 enum FT_LOAD_MONOCHROME = 1U<<12;
9732 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9733 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9734 enum FT_LOAD_COLOR = 1U<<20;
9735 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9736 enum FT_FACE_FLAG_KERNING = 1U<<6;
9737 alias FT_Kerning_Mode = int;
9738 enum /*FT_Kerning_Mode*/ {
9739   FT_KERNING_DEFAULT = 0,
9740   FT_KERNING_UNFITTED,
9741   FT_KERNING_UNSCALED
9742 }
9743 extern(C) nothrow @nogc {
9744   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9745   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9746   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9747   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9748 }
9749 struct FT_Outline_Funcs {
9750   FT_Outline_MoveToFunc move_to;
9751   FT_Outline_LineToFunc line_to;
9752   FT_Outline_ConicToFunc conic_to;
9753   FT_Outline_CubicToFunc cubic_to;
9754   int shift;
9755   FT_Pos delta;
9756 }
9757 
9758 FT_Error FT_Init_FreeType (FT_Library*);
9759 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9760 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9761 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9762 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9763 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9764 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9765 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9766 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9767 }
9768 } else version(bindbc) {
9769   import bindbc.freetype;
9770   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9771   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9772   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9773 } else {
9774   import iv.freetype;
9775 }
9776 
9777 struct FONSttFontImpl {
9778   FT_Face font;
9779   bool mono; // no aa?
9780 }
9781 
9782 __gshared FT_Library ftLibrary;
9783 
9784 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9785   FT_Error ftError;
9786   //FONS_NOTUSED(context);
9787   ftError = FT_Init_FreeType(&ftLibrary);
9788   return (ftError == 0);
9789 }
9790 
9791 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9792   font.mono = v;
9793 }
9794 
9795 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9796   return font.mono;
9797 }
9798 
9799 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9800   FT_Error ftError;
9801   //font.font.userdata = stash;
9802   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9803   return ftError == 0;
9804 }
9805 
9806 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9807   *ascent = font.font.ascender;
9808   *descent = font.font.descender;
9809   *lineGap = font.font.height-(*ascent - *descent);
9810 }
9811 
9812 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9813   return size/(font.font.ascender-font.font.descender);
9814 }
9815 
9816 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9817   return FT_Get_Char_Index(font.font, codepoint);
9818 }
9819 
9820 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 {
9821   FT_Error ftError;
9822   FT_GlyphSlot ftGlyph;
9823   //version(nanovg_ignore_mono) enum exflags = 0;
9824   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9825   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9826   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)));
9827   if (ftError) return 0;
9828   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9829   if (ftError) return 0;
9830   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9831   if (ftError) return 0;
9832   ftGlyph = font.font.glyph;
9833   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9834   *x0 = ftGlyph.bitmap_left;
9835   *x1 = *x0+ftGlyph.bitmap.width;
9836   *y0 = -ftGlyph.bitmap_top;
9837   *y1 = *y0+ftGlyph.bitmap.rows;
9838   return 1;
9839 }
9840 
9841 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9842   FT_GlyphSlot ftGlyph = font.font.glyph;
9843   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9844   //version(nanovg_ignore_mono) enum RenderAA = true;
9845   //else version(nanovg_ft_mono) enum RenderAA = false;
9846   //else enum RenderAA = true;
9847   if (font.mono) {
9848     auto src = ftGlyph.bitmap.buffer;
9849     auto dst = output;
9850     auto spt = ftGlyph.bitmap.pitch;
9851     if (spt < 0) spt = -spt;
9852     foreach (int y; 0..ftGlyph.bitmap.rows) {
9853       ubyte count = 0, b = 0;
9854       auto s = src;
9855       auto d = dst;
9856       foreach (int x; 0..ftGlyph.bitmap.width) {
9857         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9858         *d++ = (b&0x80 ? 255 : 0);
9859       }
9860       src += spt;
9861       dst += outStride;
9862     }
9863   } else {
9864     auto src = ftGlyph.bitmap.buffer;
9865     auto dst = output;
9866     auto spt = ftGlyph.bitmap.pitch;
9867     if (spt < 0) spt = -spt;
9868     foreach (int y; 0..ftGlyph.bitmap.rows) {
9869       import core.stdc.string : memcpy;
9870       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9871       memcpy(dst, src, ftGlyph.bitmap.width);
9872       src += spt;
9873       dst += outStride;
9874     }
9875   }
9876 }
9877 
9878 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9879   FT_Vector ftKerning;
9880   version(none) {
9881     // fitted kerning
9882     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9883     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9884     return cast(int)ftKerning.x; // round up and convert to integer
9885   } else {
9886     // unfitted kerning
9887     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9888     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9889     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;
9890     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9891     version(none) {
9892       if (ftKerning.x) {
9893         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9894         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9895       }
9896     }
9897     version(none) {
9898       FT_Vector kk;
9899       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9900       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9901       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9902       //assert(ftKerning.x == kadvfrac);
9903       if (ftKerning.x || kadvfrac) {
9904         { 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); }
9905       }
9906       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9907       return kadvfrac/64.0f;
9908     }
9909     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9910     return ftKerning.x/64.0f;
9911   }
9912 }
9913 
9914 extern(C) nothrow @trusted @nogc {
9915   static struct OutlinerData {
9916     @disable this (this);
9917     void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
9918     NVGContext vg;
9919     NVGPathOutline.DataStore* ol;
9920     FT_BBox outlineBBox;
9921   nothrow @trusted @nogc:
9922     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9923     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9924   }
9925 
9926   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9927     auto odata = cast(OutlinerData*)user;
9928     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9929     if (odata.ol !is null) {
9930       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9931       odata.ol.putArgs(odata.transx(to.x));
9932       odata.ol.putArgs(odata.transy(to.y));
9933     }
9934     return 0;
9935   }
9936 
9937   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9938     auto odata = cast(OutlinerData*)user;
9939     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9940     if (odata.ol !is null) {
9941       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9942       odata.ol.putArgs(odata.transx(to.x));
9943       odata.ol.putArgs(odata.transy(to.y));
9944     }
9945     return 0;
9946   }
9947 
9948   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9949     auto odata = cast(OutlinerData*)user;
9950     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9951     if (odata.ol !is null) {
9952       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9953       odata.ol.putArgs(odata.transx(c1.x));
9954       odata.ol.putArgs(odata.transy(c1.y));
9955       odata.ol.putArgs(odata.transx(to.x));
9956       odata.ol.putArgs(odata.transy(to.y));
9957     }
9958     return 0;
9959   }
9960 
9961   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9962     auto odata = cast(OutlinerData*)user;
9963     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));
9964     if (odata.ol !is null) {
9965       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9966       odata.ol.putArgs(odata.transx(c1.x));
9967       odata.ol.putArgs(odata.transy(c1.y));
9968       odata.ol.putArgs(odata.transx(c2.x));
9969       odata.ol.putArgs(odata.transy(c2.y));
9970       odata.ol.putArgs(odata.transx(to.x));
9971       odata.ol.putArgs(odata.transy(to.y));
9972     }
9973     return 0;
9974   }
9975 }
9976 
9977 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9978   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9979 
9980   FT_Outline_Funcs funcs;
9981   funcs.move_to = &fons__nvg__moveto_cb;
9982   funcs.line_to = &fons__nvg__lineto_cb;
9983   funcs.conic_to = &fons__nvg__quadto_cb;
9984   funcs.cubic_to = &fons__nvg__cubicto_cb;
9985 
9986   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9987   if (err) { bounds[] = 0; return false; }
9988   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9989 
9990   FT_Outline outline = font.font.glyph.outline;
9991 
9992   OutlinerData odata;
9993   odata.vg = vg;
9994   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9995 
9996   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9997   if (err) { bounds[] = 0; return false; }
9998   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9999   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
10000   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
10001   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
10002   return true;
10003 }
10004 
10005 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10006   FT_Outline_Funcs funcs;
10007   funcs.move_to = &fons__nvg__moveto_cb;
10008   funcs.line_to = &fons__nvg__lineto_cb;
10009   funcs.conic_to = &fons__nvg__quadto_cb;
10010   funcs.cubic_to = &fons__nvg__cubicto_cb;
10011 
10012   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10013   if (err) return false;
10014   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
10015 
10016   FT_Outline outline = font.font.glyph.outline;
10017 
10018   OutlinerData odata;
10019   odata.ol = ol;
10020   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10021 
10022   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10023   if (err) return false;
10024   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
10025   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
10026   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
10027   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
10028   return true;
10029 }
10030 
10031 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10032   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10033 
10034   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10035   if (err) return false;
10036   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10037 
10038   FT_Outline outline = font.font.glyph.outline;
10039   FT_BBox outlineBBox;
10040   FT_Outline_Get_CBox(&outline, &outlineBBox);
10041   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10042   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10043   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10044   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10045   return true;
10046 }
10047 
10048 
10049 } else {
10050 // ////////////////////////////////////////////////////////////////////////// //
10051 // sorry
10052 import std.traits : isFunctionPointer, isDelegate;
10053 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10054   import std.traits;
10055   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10056   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10057 }
10058 
10059 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10060   try {
10061     return assumeNoThrowNoGC(t)();
10062   } catch (Exception e) {
10063     assert(0, "OOPS!");
10064   }
10065 }
10066 
10067 struct FONSttFontImpl {
10068   stbtt_fontinfo font;
10069   bool mono; // no aa?
10070 }
10071 
10072 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10073   return 1;
10074 }
10075 
10076 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10077   font.mono = v;
10078 }
10079 
10080 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10081   return font.mono;
10082 }
10083 
10084 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10085   int stbError;
10086   font.font.userdata = context;
10087   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10088   return stbError;
10089 }
10090 
10091 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10092   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10093 }
10094 
10095 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10096   float res = void;
10097   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10098   return res;
10099 }
10100 
10101 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10102   int res;
10103   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10104   return res;
10105 }
10106 
10107 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 {
10108   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10109   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10110   return 1;
10111 }
10112 
10113 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10114   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10115 }
10116 
10117 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10118   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10119   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10120   float res = void;
10121   forceNoThrowNoGC({
10122     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10123     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10124   });
10125   /*
10126   if (res != 0) {
10127     { 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)); }
10128   }
10129   */
10130   //k8: dunno if this is right; i guess it isn't but...
10131   return res;
10132 }
10133 
10134 // old arsd.ttf sux! ;-)
10135 static if (is(typeof(STBTT_vcubic))) {
10136 
10137 static struct OutlinerData {
10138   @disable this (this);
10139   void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); }
10140   NVGPathOutline.DataStore* ol;
10141 nothrow @trusted @nogc:
10142   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10143   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10144 }
10145 
10146 
10147 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10148   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10149 
10150   bool okflag = false;
10151 
10152   forceNoThrowNoGC({
10153     int x0, y0, x1, y1;
10154     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10155       bounds[] = 0;
10156       return;
10157     }
10158 
10159     if (bounds.length > 0) bounds.ptr[0] = x0;
10160     if (bounds.length > 1) bounds.ptr[1] = -y1;
10161     if (bounds.length > 2) bounds.ptr[2] = x1;
10162     if (bounds.length > 3) bounds.ptr[3] = -y0;
10163 
10164     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10165 
10166     stbtt_vertex* verts = null;
10167     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10168     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10169     if (vcount < 1) return;
10170 
10171     foreach (const ref vt; verts[0..vcount]) {
10172       switch (vt.type) {
10173         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10174         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10175         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10176         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10177         default:
10178       }
10179     }
10180 
10181     okflag = true;
10182   });
10183 
10184   return okflag;
10185 }
10186 
10187 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10188   bool okflag = false;
10189 
10190   forceNoThrowNoGC({
10191     int x0, y0, x1, y1;
10192 
10193     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10194       ol.bounds[] = 0;
10195       return;
10196     }
10197 
10198     ol.bounds.ptr[0] = x0;
10199     ol.bounds.ptr[1] = -y1;
10200     ol.bounds.ptr[2] = x1;
10201     ol.bounds.ptr[3] = -y0;
10202 
10203     stbtt_vertex* verts = null;
10204     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10205     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10206     if (vcount < 1) return;
10207 
10208     OutlinerData odata;
10209     odata.ol = ol;
10210 
10211     foreach (const ref vt; verts[0..vcount]) {
10212       switch (vt.type) {
10213         case STBTT_vmove:
10214           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10215           odata.ol.putArgs(odata.transx(vt.x));
10216           odata.ol.putArgs(odata.transy(vt.y));
10217           break;
10218         case STBTT_vline:
10219           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10220           odata.ol.putArgs(odata.transx(vt.x));
10221           odata.ol.putArgs(odata.transy(vt.y));
10222           break;
10223         case STBTT_vcurve:
10224           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10225           odata.ol.putArgs(odata.transx(vt.x));
10226           odata.ol.putArgs(odata.transy(vt.y));
10227           odata.ol.putArgs(odata.transx(vt.cx));
10228           odata.ol.putArgs(odata.transy(vt.cy));
10229           break;
10230         case STBTT_vcubic:
10231           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10232           odata.ol.putArgs(odata.transx(vt.x));
10233           odata.ol.putArgs(odata.transy(vt.y));
10234           odata.ol.putArgs(odata.transx(vt.cx));
10235           odata.ol.putArgs(odata.transy(vt.cy));
10236           odata.ol.putArgs(odata.transx(vt.cx1));
10237           odata.ol.putArgs(odata.transy(vt.cy1));
10238           break;
10239         default:
10240       }
10241     }
10242 
10243     okflag = true;
10244   });
10245 
10246   return okflag;
10247 }
10248 
10249 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10250   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10251 
10252   bool okflag = false;
10253 
10254   forceNoThrowNoGC({
10255     int x0, y0, x1, y1;
10256     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10257       if (bounds.length > 0) bounds.ptr[0] = x0;
10258       if (bounds.length > 1) bounds.ptr[1] = -y1;
10259       if (bounds.length > 2) bounds.ptr[2] = x1;
10260       if (bounds.length > 3) bounds.ptr[3] = -y0;
10261       okflag = true;
10262     } else {
10263       bounds[] = 0;
10264     }
10265   });
10266 
10267   return okflag;
10268 }
10269 
10270 } // check for old stb_ttf
10271 
10272 
10273 } // version
10274 
10275 
10276 // ////////////////////////////////////////////////////////////////////////// //
10277 private:
10278 enum FONS_SCRATCH_BUF_SIZE = 64000;
10279 enum FONS_HASH_LUT_SIZE = 256;
10280 enum FONS_INIT_FONTS = 4;
10281 enum FONS_INIT_GLYPHS = 256;
10282 enum FONS_INIT_ATLAS_NODES = 256;
10283 enum FONS_VERTEX_COUNT = 1024;
10284 enum FONS_MAX_STATES = 20;
10285 enum FONS_MAX_FALLBACKS = 20;
10286 
10287 
10288 struct FONSglyph {
10289   uint codepoint;
10290   int index;
10291   int next;
10292   short size, blur;
10293   short x0, y0, x1, y1;
10294   short xadv, xoff, yoff;
10295 }
10296 
10297 // refcounted
10298 struct FONSfontData {
10299   ubyte* data;
10300   int dataSize;
10301   bool freeData;
10302   int rc;
10303 
10304   @disable this (this); // no copies
10305   void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); }
10306 }
10307 
10308 // won't set rc to 1
10309 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10310   import core.stdc.stdlib : malloc;
10311   assert(adata !is null);
10312   assert(asize > 0);
10313   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10314   if (res is null) assert(0, "FONS: out of memory");
10315   res.data = adata;
10316   res.dataSize = asize;
10317   res.freeData = afree;
10318   res.rc = 0;
10319   return res;
10320 }
10321 
10322 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10323   pragma(inline, true);
10324   if (fd !is null) ++fd.rc;
10325 }
10326 
10327 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10328   if (fd !is null) {
10329     if (--fd.rc == 0) {
10330       import core.stdc.stdlib : free;
10331       if (fd.freeData && fd.data !is null) {
10332         free(fd.data);
10333         fd.data = null;
10334       }
10335       free(fd);
10336       fd = null;
10337     }
10338   }
10339 }
10340 
10341 // as creating and destroying fonts is a rare operation, malloc some data
10342 struct FONSfont {
10343   FONSttFontImpl font;
10344   char* name; // malloced, strz, always lowercase
10345   uint namelen;
10346   uint namehash;
10347   char* path; // malloced, strz
10348   FONSfontData* fdata;
10349   float ascender;
10350   float descender;
10351   float lineh;
10352   FONSglyph* glyphs;
10353   int cglyphs;
10354   int nglyphs;
10355   int[FONS_HASH_LUT_SIZE] lut;
10356   int[FONS_MAX_FALLBACKS] fallbacks;
10357   int nfallbacks;
10358 
10359   @disable this (this);
10360   void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); }
10361 
10362   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10363     uint hash = 5381;
10364     foreach (ubyte b; cast(const(ubyte)[])s) {
10365       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10366       hash = ((hash<<5)+hash)+b;
10367     }
10368     return hash;
10369   }
10370 
10371   // except glyphs
10372   void freeMemory () nothrow @trusted @nogc {
10373     import core.stdc.stdlib : free;
10374     if (name !is null) { free(name); name = null; }
10375     namelen = namehash = 0;
10376     if (path !is null) { free(path); path = null; }
10377     fdata.decref();
10378   }
10379 
10380   // this also calcs name hash
10381   void setName (const(char)[] aname) nothrow @trusted @nogc {
10382     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10383     import core.stdc.stdlib : realloc;
10384     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10385     namelen = cast(uint)aname.length;
10386     name = cast(char*)realloc(name, namelen+1);
10387     if (name is null) assert(0, "FONS: out of memory");
10388     if (aname.length) name[0..aname.length] = aname[];
10389     name[namelen] = 0;
10390     // lowercase it
10391     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10392     namehash = djbhash(name[0..namelen]);
10393     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10394   }
10395 
10396   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10397     import core.stdc.stdlib : realloc;
10398     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10399     path = cast(char*)realloc(path, apath.length+1);
10400     if (path is null) assert(0, "FONS: out of memory");
10401     if (apath.length) path[0..apath.length] = apath[];
10402     path[apath.length] = 0;
10403   }
10404 
10405   // this won't check hash
10406   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10407     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10408     if (namelen != aname.length) return false;
10409     const(char)* ns = name;
10410     // name part
10411     foreach (char ch; aname) {
10412       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10413       if (ch != *ns++) return false;
10414     }
10415     // done (length was checked earlier)
10416     return true;
10417   }
10418 
10419   void clear () nothrow @trusted @nogc {
10420     import core.stdc.stdlib : free;
10421     import core.stdc.string : memset;
10422     if (glyphs !is null) free(glyphs);
10423     freeMemory();
10424     memset(&this, 0, this.sizeof);
10425   }
10426 
10427   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10428     if (nglyphs+1 > cglyphs) {
10429       import core.stdc.stdlib : realloc;
10430       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10431       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10432       if (glyphs is null) assert(0, "FontStash: out of memory");
10433     }
10434     ++nglyphs;
10435     return &glyphs[nglyphs-1];
10436   }
10437 }
10438 
10439 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10440   if (font !is null) {
10441     import core.stdc.stdlib : free;
10442     font.clear();
10443     free(font);
10444     font = null;
10445   }
10446 }
10447 
10448 
10449 // ////////////////////////////////////////////////////////////////////////// //
10450 struct FONSstate {
10451   int font;
10452   NVGTextAlign talign;
10453   float size = 0;
10454   float blur = 0;
10455   float spacing = 0;
10456 }
10457 
10458 
10459 // ////////////////////////////////////////////////////////////////////////// //
10460 // atlas based on Skyline Bin Packer by Jukka Jylänki
10461 alias FONSAtlas = FONSatlasInternal*;
10462 
10463 struct FONSatlasInternal {
10464   static struct Node {
10465     short x, y, width;
10466   }
10467 
10468   int width, height;
10469   Node* nodes;
10470   int nnodes;
10471   int cnodes;
10472 
10473   @disable this (this);
10474   void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10475 
10476 nothrow @trusted @nogc:
10477   static FONSAtlas create (int w, int h, int nnodes) {
10478     import core.stdc.stdlib : malloc;
10479     import core.stdc.string : memset;
10480 
10481     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10482     if (atlas is null) assert(0, "FontStash: out of memory");
10483     memset(atlas, 0, FONSatlasInternal.sizeof);
10484 
10485     atlas.width = w;
10486     atlas.height = h;
10487 
10488     // allocate space for skyline nodes
10489     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10490     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10491     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10492     atlas.nnodes = 0;
10493     atlas.cnodes = nnodes;
10494 
10495     // init root node
10496     atlas.nodes[0].x = 0;
10497     atlas.nodes[0].y = 0;
10498     atlas.nodes[0].width = cast(short)w;
10499     ++atlas.nnodes;
10500 
10501     return atlas;
10502   }
10503 
10504   void clear () {
10505     import core.stdc.stdlib : free;
10506     import core.stdc.string : memset;
10507 
10508     if (nodes !is null) free(nodes);
10509     memset(&this, 0, this.sizeof);
10510   }
10511 
10512   void insertNode (int idx, int x, int y, int w) {
10513     if (nnodes+1 > cnodes) {
10514       import core.stdc.stdlib : realloc;
10515       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10516       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10517       if (nodes is null) assert(0, "FontStash: out of memory");
10518     }
10519     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10520     nodes[idx].x = cast(short)x;
10521     nodes[idx].y = cast(short)y;
10522     nodes[idx].width = cast(short)w;
10523     ++nnodes;
10524   }
10525 
10526   void removeNode (int idx) {
10527     if (nnodes == 0) return;
10528     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10529     --nnodes;
10530   }
10531 
10532   // insert node for empty space
10533   void expand (int w, int h) {
10534     if (w > width) insertNode(nnodes, width, 0, w-width);
10535     width = w;
10536     height = h;
10537   }
10538 
10539   void reset (int w, int h) {
10540     width = w;
10541     height = h;
10542     nnodes = 0;
10543     // init root node
10544     nodes[0].x = 0;
10545     nodes[0].y = 0;
10546     nodes[0].width = cast(short)w;
10547     ++nnodes;
10548   }
10549 
10550   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10551     insertNode(idx, x, y+h, w);
10552 
10553     // delete skyline segments that fall under the shadow of the new segment
10554     for (int i = idx+1; i < nnodes; ++i) {
10555       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10556         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10557         nodes[i].x += cast(short)shrink;
10558         nodes[i].width -= cast(short)shrink;
10559         if (nodes[i].width <= 0) {
10560           removeNode(i);
10561           --i;
10562         } else {
10563           break;
10564         }
10565       } else {
10566         break;
10567       }
10568     }
10569 
10570     // Merge same height skyline segments that are next to each other
10571     for (int i = 0; i < nnodes-1; ++i) {
10572       if (nodes[i].y == nodes[i+1].y) {
10573         nodes[i].width += nodes[i+1].width;
10574         removeNode(i+1);
10575         --i;
10576       }
10577     }
10578   }
10579 
10580   // checks if there is enough space at the location of skyline span 'i',
10581   // and return the max height of all skyline spans under that at that location,
10582   // (think tetris block being dropped at that position); or -1 if no space found
10583   int rectFits (int i, int w, int h) {
10584     int x = nodes[i].x;
10585     int y = nodes[i].y;
10586     if (x+w > width) return -1;
10587     int spaceLeft = w;
10588     while (spaceLeft > 0) {
10589       if (i == nnodes) return -1;
10590       y = nvg__max(y, nodes[i].y);
10591       if (y+h > height) return -1;
10592       spaceLeft -= nodes[i].width;
10593       ++i;
10594     }
10595     return y;
10596   }
10597 
10598   bool addRect (int rw, int rh, int* rx, int* ry) {
10599     int besth = height, bestw = width, besti = -1;
10600     int bestx = -1, besty = -1;
10601 
10602     // Bottom left fit heuristic.
10603     for (int i = 0; i < nnodes; ++i) {
10604       int y = rectFits(i, rw, rh);
10605       if (y != -1) {
10606         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10607           besti = i;
10608           bestw = nodes[i].width;
10609           besth = y+rh;
10610           bestx = nodes[i].x;
10611           besty = y;
10612         }
10613       }
10614     }
10615 
10616     if (besti == -1) return false;
10617 
10618     // perform the actual packing
10619     addSkylineLevel(besti, bestx, besty, rw, rh);
10620 
10621     *rx = bestx;
10622     *ry = besty;
10623 
10624     return true;
10625   }
10626 }
10627 
10628 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10629   if (atlas !is null) {
10630     import core.stdc.stdlib : free;
10631     atlas.clear();
10632     free(atlas);
10633     atlas = null;
10634   }
10635 }
10636 
10637 
10638 // ////////////////////////////////////////////////////////////////////////// //
10639 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10640 /// Group: font_stash
10641 public struct FONScontextInternal {
10642 private:
10643   FONSParams params;
10644   float itw, ith;
10645   ubyte* texData;
10646   int[4] dirtyRect;
10647   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10648   int cfonts; // allocated
10649   int nfonts; // used (so we can track hash table stats)
10650   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10651   int hused, hsize;// used items and total items in [hashidx]
10652   FONSAtlas atlas;
10653   ubyte* scratch;
10654   int nscratch;
10655   FONSstate[FONS_MAX_STATES] states;
10656   int nstates;
10657 
10658   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10659 
10660   @disable this (this);
10661   void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10662 
10663 private:
10664   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10665     if (s0.length != s1.length) return false;
10666     const(char)* sp0 = s0.ptr;
10667     const(char)* sp1 = s1.ptr;
10668     foreach (immutable _; 0..s0.length) {
10669       char c0 = *sp0++;
10670       char c1 = *sp1++;
10671       if (c0 != c1) {
10672         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10673         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10674         if (c0 != c1) return false;
10675       }
10676     }
10677     return true;
10678   }
10679 
10680   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return {
10681     pragma(inline, true);
10682     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10683   }
10684 
10685   // simple linear probing; returns [FONS_INVALID] if not found
10686   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10687     if (nfonts == 0) return FONS_INVALID;
10688     auto nhash = FONSfont.djbhash(name);
10689     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10690     auto res = nhash%hsize;
10691     // hash will never be 100% full, so this loop is safe
10692     for (;;) {
10693       int idx = hashidx[res];
10694       if (idx == -1) break;
10695       auto font = fonts[idx];
10696       if (font is null) assert(0, "FONS internal error");
10697       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10698       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10699       res = (res+1)%hsize;
10700     }
10701     return FONS_INVALID;
10702   }
10703 
10704   // should be called $(B before) freeing `fonts[fidx]`
10705   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10706     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10707     if (fonts[fidx] is null) assert(0, "FONS internal error");
10708     if (hused != nfonts) assert(0, "FONS internal error");
10709     auto nhash = fonts[fidx].namehash;
10710     auto res = nhash%hsize;
10711     // hash will never be 100% full, so this loop is safe
10712     for (;;) {
10713       int idx = hashidx[res];
10714       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10715       if (idx == fidx) {
10716         // i found her! copy rest here
10717         int nidx = (res+1)%hsize;
10718         for (;;) {
10719           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10720           res = nidx;
10721           nidx = (nidx+1)%hsize;
10722         }
10723         return;
10724       }
10725       res = (res+1)%hsize;
10726     }
10727   }
10728 
10729   // add font with the given index to hash
10730   // prerequisite: font should not exists in hash
10731   void addIndexToHash (int idx) nothrow @trusted @nogc {
10732     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10733     if (fonts[idx] is null) assert(0, "FONS internal error");
10734     import core.stdc.stdlib : realloc;
10735     auto nhash = fonts[idx].namehash;
10736     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10737     // allocate new hash table if there was none
10738     if (hsize == 0) {
10739       enum InitSize = 256;
10740       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10741       if (newlist is null) assert(0, "FONS: out of memory");
10742       newlist[0..InitSize] = -1;
10743       hsize = InitSize;
10744       hused = 0;
10745       hashidx = newlist;
10746     }
10747     int res = cast(int)(nhash%hsize);
10748     // need to rehash? we want our hash table 50% full at max
10749     if (hashidx[res] != -1 && hused >= hsize/2) {
10750       uint nsz = hsize*2;
10751       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10752       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10753       if (newlist is null) assert(0, "FONS: out of memory");
10754       newlist[0..nsz] = -1;
10755       hused = 0;
10756       // rehash
10757       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10758         if (ff is null) continue;
10759         // find slot for this font (guaranteed to have one)
10760         uint newslot = ff.namehash%nsz;
10761         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10762         newlist[newslot] = cast(int)fidx;
10763         ++hused;
10764       }
10765       hsize = nsz;
10766       hashidx = newlist;
10767       // we added everything, including [idx], so nothing more to do here
10768     } else {
10769       // find slot (guaranteed to have one)
10770       while (hashidx[res] != -1) res = (res+1)%hsize;
10771       // i found her!
10772       hashidx[res] = idx;
10773       ++hused;
10774     }
10775   }
10776 
10777   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10778     int gx, gy;
10779     ubyte* dst;
10780 
10781     if (!atlas.addRect(w, h, &gx, &gy)) return;
10782 
10783     // Rasterize
10784     dst = &texData[gx+gy*params.width];
10785     foreach (int y; 0..h) {
10786       foreach (int x; 0..w) {
10787         dst[x] = 0xff;
10788       }
10789       dst += params.width;
10790     }
10791 
10792     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10793     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10794     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10795     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10796   }
10797 
10798   // returns fid, not hash slot
10799   int allocFontAt (int atidx) nothrow @trusted @nogc {
10800     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10801 
10802     if (atidx < 0) {
10803       if (nfonts >= cfonts) {
10804         import core.stdc.stdlib : realloc;
10805         import core.stdc.string : memset;
10806         assert(nfonts == cfonts);
10807         int newsz = cfonts+64;
10808         if (newsz > 65535) assert(0, "FONS: too many fonts");
10809         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10810         if (newlist is null) assert(0, "FONS: out of memory");
10811         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10812         fonts = newlist;
10813         cfonts = newsz;
10814       }
10815       assert(nfonts < cfonts);
10816     }
10817 
10818     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10819     if (font is null) assert(0, "FONS: out of memory");
10820     memset(font, 0, FONSfont.sizeof);
10821 
10822     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10823     if (font.glyphs is null) assert(0, "FONS: out of memory");
10824     font.cglyphs = FONS_INIT_GLYPHS;
10825     font.nglyphs = 0;
10826 
10827     if (atidx < 0) {
10828       fonts[nfonts] = font;
10829       return nfonts++;
10830     } else {
10831       fonts[atidx] = font;
10832       return atidx;
10833     }
10834   }
10835 
10836   // 0: ooops
10837   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10838     if (renderfont !is null) *renderfont = font;
10839     if (font is null || font.fdata is null) return 0;
10840     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10841     // try to find the glyph in fallback fonts
10842     if (g == 0) {
10843       foreach (immutable i; 0..font.nfallbacks) {
10844         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10845         if (fallbackFont !is null) {
10846           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10847           if (fallbackIndex != 0) {
10848             if (renderfont !is null) *renderfont = fallbackFont;
10849             return g;
10850           }
10851         }
10852       }
10853       // no char, try to find replacement one
10854       if (dch != 0xFFFD) {
10855         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10856         if (g == 0) {
10857           foreach (immutable i; 0..font.nfallbacks) {
10858             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10859             if (fallbackFont !is null) {
10860               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10861               if (fallbackIndex != 0) {
10862                 if (renderfont !is null) *renderfont = fallbackFont;
10863                 return g;
10864               }
10865             }
10866           }
10867         }
10868       }
10869     }
10870     return g;
10871   }
10872 
10873   void clear () nothrow @trusted @nogc {
10874     import core.stdc.stdlib : free;
10875 
10876     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10877     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10878 
10879     if (atlas !is null) atlas.kill();
10880     if (fonts !is null) free(fonts);
10881     if (texData !is null) free(texData);
10882     if (scratch !is null) free(scratch);
10883     if (hashidx !is null) free(hashidx);
10884   }
10885 
10886   // add font from another fontstash
10887   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10888     if (font is null || font.fdata is null) return FONS_INVALID;
10889     font.fdata.incref();
10890     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10891     if (res == FONS_INVALID) font.fdata.decref(); // oops
10892     return res;
10893   }
10894 
10895   // fdata refcount must be already increased; it won't be changed
10896   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10897     int i, ascent, descent, fh, lineGap;
10898 
10899     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10900     if (name.length > 32767) return FONS_INVALID;
10901     if (fdata is null) return FONS_INVALID;
10902 
10903     // find a font with the given name
10904     int newidx;
10905     FONSfont* oldfont = null;
10906     int oldidx = findNameInHash(name);
10907     if (oldidx != FONS_INVALID) {
10908       // replacement font
10909       oldfont = fonts[oldidx];
10910       newidx = oldidx;
10911     } else {
10912       // new font, allocate new bucket
10913       newidx = -1;
10914     }
10915 
10916     newidx = allocFontAt(newidx);
10917     FONSfont* font = fonts[newidx];
10918     font.setName(name);
10919     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10920     font.fdata = fdata; // set the font data (don't change reference count)
10921     fons__tt_setMono(&this, &font.font, !defAA);
10922 
10923     // init font
10924     nscratch = 0;
10925     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10926       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10927       font.fdata = null;
10928       font.kill();
10929       if (oldidx != FONS_INVALID) {
10930         assert(oldidx == newidx);
10931         fonts[oldidx] = oldfont;
10932       } else {
10933         assert(newidx == nfonts-1);
10934         fonts[newidx] = null;
10935         --nfonts;
10936       }
10937       return FONS_INVALID;
10938     } else {
10939       // free old font data, if any
10940       if (oldfont !is null) oldfont.kill();
10941     }
10942 
10943     // add font to name hash
10944     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10945 
10946     // store normalized line height
10947     // the real line height is got by multiplying the lineh by font size
10948     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10949     fh = ascent-descent;
10950     font.ascender = cast(float)ascent/cast(float)fh;
10951     font.descender = cast(float)descent/cast(float)fh;
10952     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10953 
10954     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10955     return newidx;
10956   }
10957 
10958   // isize: size*10
10959   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10960     if (params.isZeroTopLeft) {
10961       final switch (talign.vertical) {
10962         case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10963         case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10964         case NVGTextAlign.V.Baseline: return 0.0f;
10965         case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10966       }
10967     } else {
10968       final switch (talign.vertical) {
10969         case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10970         case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10971         case NVGTextAlign.V.Baseline: return 0.0f;
10972         case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10973       }
10974     }
10975     assert(0);
10976   }
10977 
10978 public:
10979   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10980    *
10981    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10982    * FontStash only to measure text), you can simply pass `FONSParams.init`).
10983    */
10984   static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc {
10985     import core.stdc.string : memcpy;
10986 
10987     FONSContext stash = null;
10988 
10989     // allocate memory for the font stash
10990     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10991     if (stash is null) goto error;
10992     memset(stash, 0, FONScontextInternal.sizeof);
10993 
10994     memcpy(&stash.params, &params, params.sizeof);
10995     if (stash.params.width < 1) stash.params.width = 32;
10996     if (stash.params.height < 1) stash.params.height = 32;
10997 
10998     // allocate scratch buffer
10999     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
11000     if (stash.scratch is null) goto error;
11001 
11002     // initialize implementation library
11003     if (!fons__tt_init(stash)) goto error;
11004 
11005     if (stash.params.renderCreate !is null) {
11006       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
11007     }
11008 
11009     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
11010     if (stash.atlas is null) goto error;
11011 
11012     // don't allocate space for fonts: hash manager will do that for us later
11013     //stash.cfonts = 0;
11014     //stash.nfonts = 0;
11015 
11016     // create texture for the cache
11017     stash.itw = 1.0f/stash.params.width;
11018     stash.ith = 1.0f/stash.params.height;
11019     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
11020     if (stash.texData is null) goto error;
11021     memset(stash.texData, 0, stash.params.width*stash.params.height);
11022 
11023     stash.dirtyRect.ptr[0] = stash.params.width;
11024     stash.dirtyRect.ptr[1] = stash.params.height;
11025     stash.dirtyRect.ptr[2] = 0;
11026     stash.dirtyRect.ptr[3] = 0;
11027 
11028     // add white rect at 0, 0 for debug drawing
11029     stash.addWhiteRect(2, 2);
11030 
11031     stash.pushState();
11032     stash.clearState();
11033 
11034     return stash;
11035 
11036   error:
11037     stash.kill();
11038     return null;
11039   }
11040 
11041 public:
11042   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11043   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11044     FONSfont* baseFont = fonts[base];
11045     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11046       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11047       return true;
11048     }
11049     return false;
11050   }
11051 
11052   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11053   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11054 
11055   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11056   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11057 
11058   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11059   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11060 
11061   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11062   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11063 
11064   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11065   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11066 
11067   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11068 
11069   /// Check if FontStash has a font with the given name loaded.
11070   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11071 
11072   /// Get AA for the current font, or for the specified font.
11073   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11074     FONSstate* state = getState;
11075     if (font < 0) font = state.font;
11076     if (font < 0 || font >= nfonts) return false;
11077     FONSfont* f = fonts[font];
11078     return (f !is null ? !f.font.mono : false);
11079   }
11080 
11081   /// Push current state. Returns `false` if state stack overflowed.
11082   bool pushState () nothrow @trusted @nogc {
11083     if (nstates >= FONS_MAX_STATES) {
11084       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11085       return false;
11086     }
11087     if (nstates > 0) {
11088       import core.stdc.string : memcpy;
11089       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11090     }
11091     ++nstates;
11092     return true;
11093   }
11094 
11095   /// Pop current state. Returns `false` if state stack underflowed.
11096   bool popState () nothrow @trusted @nogc {
11097     if (nstates <= 1) {
11098       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11099       return false;
11100     }
11101     --nstates;
11102     return true;
11103   }
11104 
11105   /// Clear current state (i.e. set it to some sane defaults).
11106   void clearState () nothrow @trusted @nogc {
11107     FONSstate* state = getState;
11108     state.size = 12.0f;
11109     state.font = 0;
11110     state.blur = 0;
11111     state.spacing = 0;
11112     state.talign.reset;
11113   }
11114 
11115   private enum NoAlias = ":noaa";
11116 
11117   /** Add font to FontStash.
11118    *
11119    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11120    * with same name and path several times, FontStash will load it only once. Also, you can
11121    * load new disk font for any existing logical font.
11122    *
11123    * Params:
11124    *   name = logical font name, that will be used to select this font later.
11125    *   path = path to disk file with your font.
11126    *   defAA = should FontStash use antialiased font rasterizer?
11127    *
11128    * Returns:
11129    *   font id or [FONS_INVALID].
11130    */
11131   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11132     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11133     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11134 
11135     // if font path ends with ":noaa", turn off antialiasing
11136     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11137       path = path[0..$-NoAlias.length];
11138       if (path.length == 0) return FONS_INVALID;
11139       defAA = false;
11140     }
11141 
11142     // if font name ends with ":noaa", turn off antialiasing
11143     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11144       name = name[0..$-NoAlias.length];
11145       defAA = false;
11146     }
11147 
11148     // find a font with the given name
11149     int fidx = findNameInHash(name);
11150     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11151 
11152     int loadFontFile (const(char)[] path) {
11153       // check if existing font (if any) has the same path
11154       if (fidx >= 0) {
11155         import core.stdc.string : strlen;
11156         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11157         version(Posix) {
11158           //{ 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); }
11159           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11160             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11161             // i found her!
11162             return fidx;
11163           }
11164         } else {
11165           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11166             // i found her!
11167             return fidx;
11168           }
11169         }
11170       }
11171       version(Windows) {
11172         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11173         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11174       }
11175       // either no such font, or different path
11176       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11177       int xres = FONS_INVALID;
11178       try {
11179         import core.stdc.stdlib : free, malloc;
11180         static if (NanoVegaHasIVVFS) {
11181           auto fl = VFile(path);
11182           auto dataSize = fl.size;
11183           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11184           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11185           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11186           scope(failure) free(data); // oops
11187           fl.rawReadExact(data[0..cast(uint)dataSize]);
11188           fl.close();
11189         } else {
11190           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11191           import std.internal.cstring : tempCString;
11192           auto fl = fopen(path.tempCString, "rb");
11193           if (fl is null) return FONS_INVALID;
11194           scope(exit) fclose(fl);
11195           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11196           auto dataSize = ftell(fl);
11197           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11198           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11199           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11200           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11201           scope(failure) free(data); // oops
11202           ubyte* dptr = data;
11203           auto left = cast(uint)dataSize;
11204           while (left > 0) {
11205             auto rd = fread(dptr, 1, left, fl);
11206             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11207             dptr += rd;
11208             left -= rd;
11209           }
11210         }
11211         scope(failure) free(data); // oops
11212         // create font data
11213         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11214         fdata.incref();
11215         xres = addFontWithData(name, fdata, defAA);
11216         if (xres == FONS_INVALID) {
11217           fdata.decref(); // this will free [data] and [fdata]
11218         } else {
11219           // remember path
11220           fonts[xres].setPath(path);
11221         }
11222       } catch (Exception e) {
11223         // oops; sorry
11224       }
11225       return xres;
11226     }
11227 
11228     // first try direct path
11229     auto res = loadFontFile(path);
11230     // if loading failed, try fontconfig (if fontconfig is available)
11231     static if (NanoVegaHasFontConfig) {
11232       if (res == FONS_INVALID && fontconfigAvailable) {
11233         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11234         bool ok = true;
11235         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11236         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11237         if (ok) {
11238           import std.internal.cstring : tempCString;
11239           FcPattern* pat = FcNameParse(path.tempCString);
11240           if (pat !is null) {
11241             scope(exit) FcPatternDestroy(pat);
11242             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11243               FcDefaultSubstitute(pat);
11244               // find the font
11245               FcResult result;
11246               FcPattern* font = FcFontMatch(null, pat, &result);
11247               if (font !is null) {
11248                 scope(exit) FcPatternDestroy(font);
11249                 char* file = null;
11250                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11251                   if (file !is null && file[0]) {
11252                     import core.stdc.string : strlen;
11253                     res = loadFontFile(file[0..strlen(file)]);
11254                   }
11255                 }
11256               }
11257             }
11258           }
11259         }
11260       }
11261     }
11262     return res;
11263   }
11264 
11265   /** Add font to FontStash, using data from memory.
11266    *
11267    * And already loaded font to FontStash. You can replace existing logical fonts.
11268    * But note that you can't remove logical font by passing "empty" data.
11269    *
11270    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11271    *
11272    * Params:
11273    *   name = logical font name, that will be used to select this font later.
11274    *   data = font data.
11275    *   dataSize = font data size.
11276    *   freeData = should FontStash take ownership of the font data?
11277    *   defAA = should FontStash use antialiased font rasterizer?
11278    *
11279    * Returns:
11280    *   font id or [FONS_INVALID].
11281    */
11282   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11283     if (data is null || dataSize < 16) return FONS_INVALID;
11284     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11285     fdata.incref();
11286     auto res = addFontWithData(name, fdata, defAA);
11287     if (res == FONS_INVALID) {
11288       // we promised to not free data on error
11289       fdata.freeData = false;
11290       fdata.decref(); // this will free [fdata]
11291     }
11292     return res;
11293   }
11294 
11295   /** Add fonts from another FontStash.
11296    *
11297    * This is more effective (and faster) than reloading fonts, because internally font data
11298    * is reference counted.
11299    */
11300   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11301     if (source is null) return;
11302     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11303       if (font !is null) {
11304         auto newidx = addCookedFont(font);
11305         FONSfont* newfont = fonts[newidx];
11306         assert(newfont !is null);
11307         assert(newfont.path is null);
11308         // copy path
11309         if (font.path !is null && font.path[0]) {
11310           import core.stdc.stdlib : malloc;
11311           import core.stdc.string : strcpy, strlen;
11312           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11313           if (newfont.path is null) assert(0, "FONS: out of memory");
11314           strcpy(newfont.path, font.path);
11315         }
11316       }
11317     }
11318   }
11319 
11320   /// Returns logical font name corresponding to the given font id, or `null`.
11321   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11322   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11323     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11324     return fonts[idx].name[0..fonts[idx].namelen];
11325   }
11326 
11327   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11328   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11329     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11330     // remove ":noaa" suffix
11331     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11332       name = name[0..$-NoAlias.length];
11333     }
11334     if (name.length == 0) return FONS_INVALID;
11335     return findNameInHash(name);
11336   }
11337 
11338   /** Measures the specified text string. Parameter bounds should be a float[4],
11339    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11340    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11341    */
11342   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11343     FONSstate* state = getState;
11344     uint codepoint;
11345     uint utf8state = 0;
11346     FONSQuad q;
11347     FONSglyph* glyph = null;
11348     int prevGlyphIndex = -1;
11349     short isize = cast(short)(state.size*10.0f);
11350     short iblur = cast(short)state.blur;
11351     FONSfont* font;
11352 
11353     if (state.font < 0 || state.font >= nfonts) return 0;
11354     font = fonts[state.font];
11355     if (font is null || font.fdata is null) return 0;
11356 
11357     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11358 
11359     // Align vertically.
11360     y += getVertAlign(font, state.talign, isize);
11361 
11362     float minx = x, maxx = x;
11363     float miny = y, maxy = y;
11364     float startx = x;
11365 
11366     foreach (T ch; str) {
11367       static if (T.sizeof == 1) {
11368         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11369         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11370         if (utf8state) continue;
11371       } else {
11372         static if (T.sizeof == 4) {
11373           if (ch > dchar.max) ch = 0xFFFD;
11374         }
11375         codepoint = cast(uint)ch;
11376       }
11377       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11378       if (glyph !is null) {
11379         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11380         if (q.x0 < minx) minx = q.x0;
11381         if (q.x1 > maxx) maxx = q.x1;
11382         if (params.isZeroTopLeft) {
11383           if (q.y0 < miny) miny = q.y0;
11384           if (q.y1 > maxy) maxy = q.y1;
11385         } else {
11386           if (q.y1 < miny) miny = q.y1;
11387           if (q.y0 > maxy) maxy = q.y0;
11388         }
11389         prevGlyphIndex = glyph.index;
11390       } else {
11391         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11392         prevGlyphIndex = -1;
11393       }
11394     }
11395 
11396     float advance = x-startx;
11397     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11398 
11399     // Align horizontally
11400     if (state.talign.left) {
11401       // empty
11402     } else if (state.talign.right) {
11403       minx -= advance;
11404       maxx -= advance;
11405     } else if (state.talign.center) {
11406       minx -= advance*0.5f;
11407       maxx -= advance*0.5f;
11408     }
11409 
11410     if (bounds.length) {
11411       if (bounds.length > 0) bounds.ptr[0] = minx;
11412       if (bounds.length > 1) bounds.ptr[1] = miny;
11413       if (bounds.length > 2) bounds.ptr[2] = maxx;
11414       if (bounds.length > 3) bounds.ptr[3] = maxy;
11415     }
11416 
11417     return advance;
11418   }
11419 
11420   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11421   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11422     FONSstate* state = getState;
11423     if (state.font < 0 || state.font >= nfonts) {
11424       if (ascender !is null) *ascender = 0;
11425       if (descender !is null) *descender = 0;
11426       if (lineh !is null) *lineh = 0;
11427     } else {
11428       FONSfont* font = fonts[state.font];
11429       if (font is null || font.fdata is null) {
11430         if (ascender !is null) *ascender = 0;
11431         if (descender !is null) *descender = 0;
11432         if (lineh !is null) *lineh = 0;
11433       } else {
11434         short isize = cast(short)(state.size*10.0f);
11435         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11436         if (descender !is null) *descender = font.descender*isize/10.0f;
11437         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11438       }
11439     }
11440   }
11441 
11442   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11443   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11444     FONSfont* font;
11445     FONSstate* state = getState;
11446     short isize;
11447 
11448     if (minyp !is null) *minyp = 0;
11449     if (maxyp !is null) *maxyp = 0;
11450 
11451     if (state.font < 0 || state.font >= nfonts) return;
11452     font = fonts[state.font];
11453     isize = cast(short)(state.size*10.0f);
11454     if (font is null || font.fdata is null) return;
11455 
11456     y += getVertAlign(font, state.talign, isize);
11457 
11458     if (params.isZeroTopLeft) {
11459       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11460       immutable float maxy = miny+font.lineh*isize/10.0f;
11461       if (minyp !is null) *minyp = miny;
11462       if (maxyp !is null) *maxyp = maxy;
11463     } else {
11464       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11465       immutable float miny = maxy-font.lineh*isize/10.0f;
11466       if (minyp !is null) *minyp = miny;
11467       if (maxyp !is null) *maxyp = maxy;
11468     }
11469   }
11470 
11471   /// Returns font line height.
11472   float fontHeight () nothrow @trusted @nogc {
11473     float res = void;
11474     getVertMetrics(null, null, &res);
11475     return res;
11476   }
11477 
11478   /// Returns font ascender (positive).
11479   float fontAscender () nothrow @trusted @nogc {
11480     float res = void;
11481     getVertMetrics(&res, null, null);
11482     return res;
11483   }
11484 
11485   /// Returns font descender (negative).
11486   float fontDescender () nothrow @trusted @nogc {
11487     float res = void;
11488     getVertMetrics(null, &res, null);
11489     return res;
11490   }
11491 
11492   //TODO: document this
11493   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11494     if (width !is null) *width = params.width;
11495     if (height !is null) *height = params.height;
11496     return texData;
11497   }
11498 
11499   //TODO: document this
11500   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11501     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11502       dirty[0] = dirtyRect.ptr[0];
11503       dirty[1] = dirtyRect.ptr[1];
11504       dirty[2] = dirtyRect.ptr[2];
11505       dirty[3] = dirtyRect.ptr[3];
11506       // reset dirty rect
11507       dirtyRect.ptr[0] = params.width;
11508       dirtyRect.ptr[1] = params.height;
11509       dirtyRect.ptr[2] = 0;
11510       dirtyRect.ptr[3] = 0;
11511       return true;
11512     }
11513     return false;
11514   }
11515 
11516   //TODO: document this
11517   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11518     handleError = callback;
11519   }
11520 
11521   //TODO: document this
11522   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11523     if (width !is null) *width = params.width;
11524     if (height !is null) *height = params.height;
11525   }
11526 
11527   //TODO: document this
11528   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11529     import core.stdc.stdlib : free;
11530     import core.stdc.string : memcpy, memset;
11531 
11532     int maxy = 0;
11533     ubyte* data = null;
11534 
11535     width = nvg__max(width, params.width);
11536     height = nvg__max(height, params.height);
11537 
11538     if (width == params.width && height == params.height) return true;
11539 
11540     // Flush pending glyphs.
11541     flush();
11542 
11543     // Create new texture
11544     if (params.renderResize !is null) {
11545       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11546     }
11547     // Copy old texture data over.
11548     data = cast(ubyte*)malloc(width*height);
11549     if (data is null) return 0;
11550     foreach (immutable int i; 0..params.height) {
11551       ubyte* dst = &data[i*width];
11552       ubyte* src = &texData[i*params.width];
11553       memcpy(dst, src, params.width);
11554       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11555     }
11556     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11557 
11558     free(texData);
11559     texData = data;
11560 
11561     // Increase atlas size
11562     atlas.expand(width, height);
11563 
11564     // Add existing data as dirty.
11565     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11566     dirtyRect.ptr[0] = 0;
11567     dirtyRect.ptr[1] = 0;
11568     dirtyRect.ptr[2] = params.width;
11569     dirtyRect.ptr[3] = maxy;
11570 
11571     params.width = width;
11572     params.height = height;
11573     itw = 1.0f/params.width;
11574     ith = 1.0f/params.height;
11575 
11576     return true;
11577   }
11578 
11579   //TODO: document this
11580   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11581     import core.stdc.stdlib : realloc;
11582     import core.stdc.string : memcpy, memset;
11583 
11584     // flush pending glyphs
11585     flush();
11586 
11587     // create new texture
11588     if (params.renderResize !is null) {
11589       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11590     }
11591 
11592     // reset atlas
11593     atlas.reset(width, height);
11594 
11595     // clear texture data
11596     texData = cast(ubyte*)realloc(texData, width*height);
11597     if (texData is null) assert(0, "FONS: out of memory");
11598     memset(texData, 0, width*height);
11599 
11600     // reset dirty rect
11601     dirtyRect.ptr[0] = width;
11602     dirtyRect.ptr[1] = height;
11603     dirtyRect.ptr[2] = 0;
11604     dirtyRect.ptr[3] = 0;
11605 
11606     // Reset cached glyphs
11607     foreach (FONSfont* font; fonts[0..nfonts]) {
11608       if (font !is null) {
11609         font.nglyphs = 0;
11610         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11611       }
11612     }
11613 
11614     params.width = width;
11615     params.height = height;
11616     itw = 1.0f/params.width;
11617     ith = 1.0f/params.height;
11618 
11619     // Add white rect at 0, 0 for debug drawing.
11620     addWhiteRect(2, 2);
11621 
11622     return true;
11623   }
11624 
11625   //TODO: document this
11626   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11627     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11628     static if (is(typeof(&fons__nvg__bounds))) {
11629       FONSstate* state = getState;
11630       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11631       FONSfont* font;
11632       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11633       if (g == 0) { bounds[] = 0; return false; }
11634       assert(font !is null);
11635       return fons__nvg__bounds(&font.font, g, bounds);
11636     } else {
11637       bounds[] = 0;
11638       return false;
11639     }
11640   }
11641 
11642   //TODO: document this
11643   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11644     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11645     static if (is(typeof(&fons__nvg__toPath))) {
11646       if (vg is null) { bounds[] = 0; return false; }
11647       FONSstate* state = getState;
11648       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11649       FONSfont* font;
11650       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11651       if (g == 0) { bounds[] = 0; return false; }
11652       assert(font !is null);
11653       return fons__nvg__toPath(vg, &font.font, g, bounds);
11654     } else {
11655       bounds[] = 0;
11656       return false;
11657     }
11658   }
11659 
11660   //TODO: document this
11661   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11662     if (ol is null) return false;
11663     static if (is(typeof(&fons__nvg__toOutline))) {
11664       FONSstate* state = getState;
11665       if (state.font < 0 || state.font >= nfonts) return false;
11666       FONSfont* font;
11667       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11668       if (g == 0) return false;
11669       assert(font !is null);
11670       return fons__nvg__toOutline(&font.font, g, ol);
11671     } else {
11672       return false;
11673     }
11674   }
11675 
11676   //TODO: document this
11677   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11678     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11679       pragma(inline, true);
11680       a += ~(a<<15);
11681       a ^=  (a>>10);
11682       a +=  (a<<3);
11683       a ^=  (a>>6);
11684       a += ~(a<<11);
11685       a ^=  (a>>16);
11686       return a;
11687     }
11688 
11689     // based on Exponential blur, Jani Huhtanen, 2006
11690     enum APREC = 16;
11691     enum ZPREC = 7;
11692 
11693     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11694       foreach (immutable int y; 0..h) {
11695         int z = 0; // force zero border
11696         foreach (int x; 1..w) {
11697           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11698           dst[x] = cast(ubyte)(z>>ZPREC);
11699         }
11700         dst[w-1] = 0; // force zero border
11701         z = 0;
11702         for (int x = w-2; x >= 0; --x) {
11703           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11704           dst[x] = cast(ubyte)(z>>ZPREC);
11705         }
11706         dst[0] = 0; // force zero border
11707         dst += dstStride;
11708       }
11709     }
11710 
11711     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11712       foreach (immutable int x; 0..w) {
11713         int z = 0; // force zero border
11714         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11715           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11716           dst[y] = cast(ubyte)(z>>ZPREC);
11717         }
11718         dst[(h-1)*dstStride] = 0; // force zero border
11719         z = 0;
11720         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11721           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11722           dst[y] = cast(ubyte)(z>>ZPREC);
11723         }
11724         dst[0] = 0; // force zero border
11725         ++dst;
11726       }
11727     }
11728 
11729     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11730       import std.math : expf = exp;
11731       if (blur < 1) return;
11732       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11733       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11734       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11735       fons__blurRows(dst, w, h, dstStride, alpha);
11736       fons__blurCols(dst, w, h, dstStride, alpha);
11737       fons__blurRows(dst, w, h, dstStride, alpha);
11738       fons__blurCols(dst, w, h, dstStride, alpha);
11739       //fons__blurrows(dst, w, h, dstStride, alpha);
11740       //fons__blurcols(dst, w, h, dstStride, alpha);
11741     }
11742 
11743     int advance, lsb, x0, y0, x1, y1, gx, gy;
11744     FONSglyph* glyph = null;
11745     float size = isize/10.0f;
11746     FONSfont* renderFont = font;
11747 
11748     if (isize < 2) return null;
11749     if (iblur > 20) iblur = 20;
11750     int pad = iblur+2;
11751 
11752     // Reset allocator.
11753     nscratch = 0;
11754 
11755     // Find code point and size.
11756     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11757     int i = font.lut.ptr[h];
11758     while (i != -1) {
11759       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11760       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11761         glyph = &font.glyphs[i];
11762         // Negative coordinate indicates there is no bitmap data created.
11763         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11764         // At this point, glyph exists but the bitmap data is not yet created.
11765         break;
11766       }
11767       i = font.glyphs[i].next;
11768     }
11769 
11770     // Create a new glyph or rasterize bitmap data for a cached glyph.
11771     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11772     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11773     // It is possible that we did not find a fallback glyph.
11774     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11775 
11776     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11777     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11778     int gw = x1-x0+pad*2;
11779     int gh = y1-y0+pad*2;
11780 
11781     // Determines the spot to draw glyph in the atlas.
11782     if (bitmapOption == FONSBitmapFlag.Required) {
11783       // Find free spot for the rect in the atlas.
11784       bool added = atlas.addRect(gw, gh, &gx, &gy);
11785       if (!added && handleError !is null) {
11786         // Atlas is full, let the user to resize the atlas (or not), and try again.
11787         handleError(FONSError.AtlasFull, 0);
11788         added = atlas.addRect(gw, gh, &gx, &gy);
11789       }
11790       if (!added) return null;
11791     } else {
11792       // Negative coordinate indicates there is no bitmap data created.
11793       gx = -1;
11794       gy = -1;
11795     }
11796 
11797     // Init glyph.
11798     if (glyph is null) {
11799       glyph = font.allocGlyph();
11800       glyph.codepoint = codepoint;
11801       glyph.size = isize;
11802       glyph.blur = iblur;
11803       glyph.next = 0;
11804 
11805       // Insert char to hash lookup.
11806       glyph.next = font.lut.ptr[h];
11807       font.lut.ptr[h] = font.nglyphs-1;
11808     }
11809     glyph.index = g;
11810     glyph.x0 = cast(short)gx;
11811     glyph.y0 = cast(short)gy;
11812     glyph.x1 = cast(short)(glyph.x0+gw);
11813     glyph.y1 = cast(short)(glyph.y0+gh);
11814     glyph.xadv = cast(short)(scale*advance*10.0f);
11815     glyph.xoff = cast(short)(x0-pad);
11816     glyph.yoff = cast(short)(y0-pad);
11817 
11818     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11819 
11820     // Rasterize
11821     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11822     fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11823 
11824     // Make sure there is one pixel empty border.
11825     dst = &texData[glyph.x0+glyph.y0*params.width];
11826     foreach (immutable int y; 0..gh) {
11827       dst[y*params.width] = 0;
11828       dst[gw-1+y*params.width] = 0;
11829     }
11830     foreach (immutable int x; 0..gw) {
11831       dst[x] = 0;
11832       dst[x+(gh-1)*params.width] = 0;
11833     }
11834 
11835     // Debug code to color the glyph background
11836     version(none) {
11837       foreach (immutable yy; 0..gh) {
11838         foreach (immutable xx; 0..gw) {
11839           int a = cast(int)dst[xx+yy*params.width]+42;
11840           if (a > 255) a = 255;
11841           dst[xx+yy*params.width] = cast(ubyte)a;
11842         }
11843       }
11844     }
11845 
11846     // Blur
11847     if (iblur > 0) {
11848       nscratch = 0;
11849       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11850       fons__blur(bdst, gw, gh, params.width, iblur);
11851     }
11852 
11853     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11854     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11855     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11856     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11857 
11858     return glyph;
11859   }
11860 
11861   //TODO: document this
11862   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11863     if (prevGlyphIndex >= 0) {
11864       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11865       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11866       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11867     }
11868 
11869     // Each glyph has 2px border to allow good interpolation,
11870     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11871     // Inset the texture region by one pixel for correct interpolation.
11872     immutable float xoff = cast(short)(glyph.xoff+1);
11873     immutable float yoff = cast(short)(glyph.yoff+1);
11874     immutable float x0 = cast(float)(glyph.x0+1);
11875     immutable float y0 = cast(float)(glyph.y0+1);
11876     immutable float x1 = cast(float)(glyph.x1-1);
11877     immutable float y1 = cast(float)(glyph.y1-1);
11878 
11879     if (params.isZeroTopLeft) {
11880       immutable float rx = cast(float)cast(int)(*x+xoff);
11881       immutable float ry = cast(float)cast(int)(*y+yoff);
11882 
11883       q.x0 = rx;
11884       q.y0 = ry;
11885       q.x1 = rx+x1-x0;
11886       q.y1 = ry+y1-y0;
11887 
11888       q.s0 = x0*itw;
11889       q.t0 = y0*ith;
11890       q.s1 = x1*itw;
11891       q.t1 = y1*ith;
11892     } else {
11893       immutable float rx = cast(float)cast(int)(*x+xoff);
11894       immutable float ry = cast(float)cast(int)(*y-yoff);
11895 
11896       q.x0 = rx;
11897       q.y0 = ry;
11898       q.x1 = rx+x1-x0;
11899       q.y1 = ry-y1+y0;
11900 
11901       q.s0 = x0*itw;
11902       q.t0 = y0*ith;
11903       q.s1 = x1*itw;
11904       q.t1 = y1*ith;
11905     }
11906 
11907     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11908   }
11909 
11910   void flush () nothrow @trusted @nogc {
11911     // flush texture
11912     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11913       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11914       // reset dirty rect
11915       dirtyRect.ptr[0] = params.width;
11916       dirtyRect.ptr[1] = params.height;
11917       dirtyRect.ptr[2] = 0;
11918       dirtyRect.ptr[3] = 0;
11919     }
11920   }
11921 }
11922 
11923 /// Free all resources used by the `stash`, and `stash` itself.
11924 /// Group: font_stash
11925 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11926   import core.stdc.stdlib : free;
11927   if (stash is null) return;
11928   stash.clear();
11929   free(stash);
11930   stash = null;
11931 }
11932 
11933 
11934 // ////////////////////////////////////////////////////////////////////////// //
11935 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11936   ubyte* ptr;
11937   FONSContext stash = cast(FONSContext)up;
11938   // 16-byte align the returned pointer
11939   size = (size+0xf)&~0xf;
11940   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11941     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11942     return null;
11943   }
11944   ptr = stash.scratch+stash.nscratch;
11945   stash.nscratch += cast(int)size;
11946   return ptr;
11947 }
11948 
11949 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11950   // empty
11951 }
11952 
11953 
11954 // ////////////////////////////////////////////////////////////////////////// //
11955 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11956 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11957 
11958 enum FONS_UTF8_ACCEPT = 0;
11959 enum FONS_UTF8_REJECT = 12;
11960 
11961 static immutable ubyte[364] utf8d = [
11962   // The first part of the table maps bytes to character classes that
11963   // to reduce the size of the transition table and create bitmasks.
11964   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,
11965   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,
11966   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,
11967   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,
11968   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,
11969   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,
11970   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,
11971   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,
11972 
11973   // The second part is a transition table that maps a combination
11974   // of a state of the automaton and a character class to a state.
11975   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11976   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11977   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11978   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11979   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11980 ];
11981 
11982 private enum DecUtfMixin(string state, string codep, string byte_) =
11983 `{
11984   uint type_ = utf8d.ptr[`~byte_~`];
11985   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11986   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11987     `~state~` = FONS_UTF8_ACCEPT;
11988     `~codep~` = 0xFFFD;
11989   }
11990  }`;
11991 
11992 /*
11993 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11994   pragma(inline, true);
11995   uint type = utf8d.ptr[byte_];
11996   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11997   *state = utf8d.ptr[256 + *state+type];
11998   return *state;
11999 }
12000 */
12001 
12002 
12003 // ////////////////////////////////////////////////////////////////////////// //
12004 /// This iterator can be used to do text measurement.
12005 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
12006 /// Group: font_stash
12007 public struct FONSTextBoundsIterator {
12008 private:
12009   FONSContext stash;
12010   FONSstate state;
12011   uint codepoint = 0xFFFD;
12012   uint utf8state = 0;
12013   int prevGlyphIndex = -1;
12014   short isize, iblur;
12015   float scale = 0;
12016   FONSfont* font;
12017   float startx = 0, x = 0, y = 0;
12018   float minx = 0, miny = 0, maxx = 0, maxy = 0;
12019 
12020 private:
12021   void clear () nothrow @trusted @nogc {
12022     import core.stdc.string : memset;
12023     memset(&this, 0, this.sizeof);
12024     this.prevGlyphIndex = -1;
12025     this.codepoint = 0xFFFD;
12026   }
12027 
12028 public:
12029   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12030   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
12031 
12032   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12033   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
12034     clear();
12035 
12036     if (astash is null || astash.nstates == 0) return;
12037 
12038     stash = astash;
12039     state = *stash.getState;
12040 
12041     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12042     font = stash.fonts[state.font];
12043     if (font is null || font.fdata is null) { clear(); return; }
12044 
12045     x = ax;
12046     y = ay;
12047     isize = cast(short)(state.size*10.0f);
12048     iblur = cast(short)state.blur;
12049     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12050 
12051     // align vertically
12052     y += astash.getVertAlign(font, state.talign, isize);
12053 
12054     minx = maxx = x;
12055     miny = maxy = y;
12056     startx = x;
12057   }
12058 
12059   /// Can this iterator be used?
12060   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12061 
12062   /// Put some text into iterator, calculate new values.
12063   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12064     enum DoCodePointMixin = q{
12065       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12066       if (glyph !is null) {
12067         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12068         if (q.x0 < minx) minx = q.x0;
12069         if (q.x1 > maxx) maxx = q.x1;
12070         if (stash.params.isZeroTopLeft) {
12071           if (q.y0 < miny) miny = q.y0;
12072           if (q.y1 > maxy) maxy = q.y1;
12073         } else {
12074           if (q.y1 < miny) miny = q.y1;
12075           if (q.y0 > maxy) maxy = q.y0;
12076         }
12077         prevGlyphIndex = glyph.index;
12078       } else {
12079         prevGlyphIndex = -1;
12080       }
12081     };
12082 
12083     if (stash is null || str.length == 0) return; // alas
12084 
12085     FONSQuad q;
12086     FONSglyph* glyph;
12087 
12088     static if (is(T == char)) {
12089       foreach (char ch; str) {
12090         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12091         if (utf8state) continue; // full char is not collected yet
12092         mixin(DoCodePointMixin);
12093       }
12094     } else {
12095       if (utf8state) {
12096         utf8state = 0;
12097         codepoint = 0xFFFD;
12098         mixin(DoCodePointMixin);
12099       }
12100       foreach (T dch; str) {
12101         static if (is(T == dchar)) {
12102           if (dch > dchar.max) dch = 0xFFFD;
12103         }
12104         codepoint = cast(uint)dch;
12105         mixin(DoCodePointMixin);
12106       }
12107     }
12108   }
12109 
12110   /// Returns current advance.
12111   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12112 
12113   /// Returns current text bounds.
12114   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12115     if (stash is null) { bounds[] = 0; return; }
12116     float lminx = minx, lmaxx = maxx;
12117     // align horizontally
12118     if (state.talign.left) {
12119       // empty
12120     } else if (state.talign.right) {
12121       float ca = advance;
12122       lminx -= ca;
12123       lmaxx -= ca;
12124     } else if (state.talign.center) {
12125       float ca = advance*0.5f;
12126       lminx -= ca;
12127       lmaxx -= ca;
12128     }
12129     bounds[0] = lminx;
12130     bounds[1] = miny;
12131     bounds[2] = lmaxx;
12132     bounds[3] = maxy;
12133   }
12134 
12135   /// Returns current horizontal text bounds.
12136   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12137     if (stash !is null) {
12138       float lminx = minx, lmaxx = maxx;
12139       // align horizontally
12140       if (state.talign.left) {
12141         // empty
12142       } else if (state.talign.right) {
12143         float ca = advance;
12144         lminx -= ca;
12145         lmaxx -= ca;
12146       } else if (state.talign.center) {
12147         float ca = advance*0.5f;
12148         lminx -= ca;
12149         lmaxx -= ca;
12150       }
12151       xmin = lminx;
12152       xmax = lmaxx;
12153     } else {
12154       xmin = xmax = 0;
12155     }
12156   }
12157 
12158   /// Returns current vertical text bounds.
12159   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12160     pragma(inline, true);
12161     if (stash !is null) {
12162       ymin = miny;
12163       ymax = maxy;
12164     } else {
12165       ymin = ymax = 0;
12166     }
12167   }
12168 
12169   /// Returns font line height.
12170   float lineHeight () nothrow @trusted @nogc {
12171     pragma(inline, true);
12172     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12173   }
12174 
12175   /// Returns font ascender (positive).
12176   float ascender () nothrow @trusted @nogc {
12177     pragma(inline, true);
12178     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12179   }
12180 
12181   /// Returns font descender (negative).
12182   float descender () nothrow @trusted @nogc {
12183     pragma(inline, true);
12184     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12185   }
12186 }
12187 
12188 
12189 // ////////////////////////////////////////////////////////////////////////// //
12190 // backgl
12191 // ////////////////////////////////////////////////////////////////////////// //
12192 import core.stdc.stdlib : malloc, realloc, free;
12193 import core.stdc.string : memcpy, memset;
12194 
12195 static if (__VERSION__ < 2076) {
12196   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12197     import std.traits;
12198     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12199     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12200   }
12201 }
12202 
12203 
12204 //import arsd.simpledisplay;
12205 version(nanovg_bindbc_opengl_bindings) {
12206   import bindbc.opengl;
12207 } else version(nanovg_builtin_opengl_bindings) {
12208   import arsd.simpledisplay;
12209 
12210 	/++
12211 		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.
12212 
12213 		History:
12214 			Added January 22, 2021 (version 9.2 release)
12215 	+/
12216 	public class NVGWindow : SimpleWindow {
12217 		NVGContext nvg;
12218 
12219 		/++
12220 
12221 		+/
12222 		this(int width, int height, string title) {
12223 			setOpenGLContextVersion(3, 0);
12224 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12225 
12226 			this.onClosing = delegate() {
12227 				nvg.kill();
12228 			};
12229 
12230 			this.visibleForTheFirstTime = delegate() {
12231 				this.setAsCurrentOpenGlContext();
12232 				nvg = nvgCreateContext();
12233 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12234 			};
12235 
12236 			this.redrawOpenGlScene = delegate() {
12237 				if(redrawNVGScene is null)
12238 					return;
12239 				glViewport(0, 0, this.width, this.height);
12240 				if(clearOnEachFrame) {
12241 					glClearColor(0, 0, 0, 0);
12242 					glClear(glNVGClearFlags);
12243 				}
12244 
12245 				nvg.beginFrame(this.width, this.height);
12246 				scope(exit) nvg.endFrame();
12247 
12248 				redrawNVGScene(nvg);
12249 			};
12250 
12251 			this.setEventHandlers(
12252 				&redrawOpenGlSceneNow,
12253 				(KeyEvent ke) {
12254 					if(ke.key == Key.Escape || ke.key == Key.Q)
12255 						this.close();
12256 				}
12257 			);
12258 		}
12259 
12260 		/++
12261 
12262 		+/
12263 		bool clearOnEachFrame = true;
12264 
12265 		/++
12266 
12267 		+/
12268 		void delegate(NVGContext nvg) redrawNVGScene;
12269 
12270 		/++
12271 
12272 		+/
12273 		void redrawNVGSceneNow() {
12274 			redrawOpenGlSceneNow();
12275 		}
12276 	}
12277 
12278 } else {
12279   import iv.glbinds;
12280 }
12281 
12282 private:
12283 // sdpy is missing that yet
12284 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12285 
12286 
12287 
12288 version(bindbc){
12289   private extern(System) nothrow @nogc:
12290   // this definition doesn't exist in regular OpenGL (?)
12291   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12292   private void nanovgInitOpenGL () {
12293     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12294     GLSupport support = loadOpenGL();
12295     if (support == GLSupport.noLibrary)
12296       assert(0, "OpenGL initialization failed: shared library failed to load");
12297     else if (support == GLSupport.badLibrary)
12298       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12299     else if (support == GLSupport.noContext)
12300       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12301   }
12302 } else { // OpenGL API missing from simpledisplay
12303     private void nanovgInitOpenGL () @nogc nothrow {
12304       __gshared bool initialized = false;
12305       if (initialized) return;
12306 
12307       try
12308         gl3.loadDynamicLibrary();
12309       catch(Exception)
12310       	assert(0, "GL 3 failed to load");
12311 
12312       initialized = true;
12313   }
12314 }
12315 
12316 
12317 
12318 /// Context creation flags.
12319 /// Group: context_management
12320 public enum NVGContextFlag : int {
12321   /// Nothing special, i.e. empty flag.
12322   None = 0,
12323   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12324   Antialias = 1U<<0,
12325   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12326     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12327   StencilStrokes = 1U<<1,
12328   /// Flag indicating that additional debug checks are done.
12329   Debug = 1U<<2,
12330   /// Filter (antialias) fonts
12331   FontAA = 1U<<7,
12332   /// Don't filter (antialias) fonts
12333   FontNoAA = 1U<<8,
12334   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12335   Default = 1U<<31,
12336 }
12337 
12338 public enum NANOVG_GL_USE_STATE_FILTER = true;
12339 
12340 /// Returns flags for glClear().
12341 /// Group: context_management
12342 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12343   pragma(inline, true);
12344   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12345 }
12346 
12347 
12348 // ////////////////////////////////////////////////////////////////////////// //
12349 private:
12350 
12351 version = nanovega_shared_stencil;
12352 //version = nanovega_debug_clipping;
12353 
12354 enum GLNVGuniformLoc {
12355   ViewSize,
12356   Tex,
12357   Frag,
12358   TMat,
12359   TTr,
12360   ClipTex,
12361 }
12362 
12363 alias GLNVGshaderType = int;
12364 enum /*GLNVGshaderType*/ {
12365   NSVG_SHADER_FILLCOLOR,
12366   NSVG_SHADER_FILLGRAD,
12367   NSVG_SHADER_FILLIMG,
12368   NSVG_SHADER_SIMPLE, // also used for clipfill
12369   NSVG_SHADER_IMG,
12370 }
12371 
12372 struct GLNVGshader {
12373   GLuint prog;
12374   GLuint frag;
12375   GLuint vert;
12376   GLint[GLNVGuniformLoc.max+1] loc;
12377 }
12378 
12379 struct GLNVGtexture {
12380   int id;
12381   GLuint tex;
12382   int width, height;
12383   NVGtexture type;
12384   int flags;
12385   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12386   int nextfree;
12387 }
12388 
12389 struct GLNVGblend {
12390   bool simple;
12391   GLenum srcRGB;
12392   GLenum dstRGB;
12393   GLenum srcAlpha;
12394   GLenum dstAlpha;
12395 }
12396 
12397 alias GLNVGcallType = int;
12398 enum /*GLNVGcallType*/ {
12399   GLNVG_NONE = 0,
12400   GLNVG_FILL,
12401   GLNVG_CONVEXFILL,
12402   GLNVG_STROKE,
12403   GLNVG_TRIANGLES,
12404   GLNVG_AFFINE, // change affine transformation matrix
12405   GLNVG_PUSHCLIP,
12406   GLNVG_POPCLIP,
12407   GLNVG_RESETCLIP,
12408   GLNVG_CLIP_DDUMP_ON,
12409   GLNVG_CLIP_DDUMP_OFF,
12410 }
12411 
12412 struct GLNVGcall {
12413   int type;
12414   int evenOdd; // for fill
12415   int image;
12416   int pathOffset;
12417   int pathCount;
12418   int triangleOffset;
12419   int triangleCount;
12420   int uniformOffset;
12421   NVGMatrix affine;
12422   GLNVGblend blendFunc;
12423   NVGClipMode clipmode;
12424 }
12425 
12426 struct GLNVGpath {
12427   int fillOffset;
12428   int fillCount;
12429   int strokeOffset;
12430   int strokeCount;
12431 }
12432 
12433 align(1) struct GLNVGfragUniforms {
12434 align(1):
12435   enum UNIFORM_ARRAY_SIZE = 13;
12436   // note: after modifying layout or size of uniform array,
12437   // don't forget to also update the fragment shader source!
12438   align(1) union {
12439   align(1):
12440     align(1) struct {
12441     align(1):
12442       float[12] scissorMat; // matrices are actually 3 vec4s
12443       float[12] paintMat;
12444       NVGColor innerCol;
12445       NVGColor middleCol;
12446       NVGColor outerCol;
12447       float[2] scissorExt;
12448       float[2] scissorScale;
12449       float[2] extent;
12450       float radius;
12451       float feather;
12452       float strokeMult;
12453       float strokeThr;
12454       float texType;
12455       float type;
12456       float doclip;
12457       float midp; // for gradients
12458       float unused2, unused3;
12459     }
12460     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12461   }
12462 }
12463 
12464 enum GLMaskState {
12465   DontMask = -1,
12466   Uninitialized = 0,
12467   Initialized = 1,
12468   JustCleared = 2,
12469 }
12470 
12471 import core.sync.mutex;
12472 __gshared Mutex GLNVGTextureLocker;
12473 shared static this() {
12474 	GLNVGTextureLocker = new Mutex();
12475 }
12476 
12477 struct GLNVGcontext {
12478   private import core.thread : ThreadID;
12479 
12480   GLNVGshader shader;
12481   GLNVGtexture* textures;
12482   float[2] view;
12483   int freetexid; // -1: none
12484   int ntextures;
12485   int ctextures;
12486   GLuint vertBuf;
12487   int fragSize;
12488   int flags;
12489   // FBOs for masks
12490   GLuint[NVG_MAX_STATES] fbo;
12491   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12492   int fboWidth, fboHeight;
12493   GLMaskState[NVG_MAX_STATES] maskStack;
12494   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12495   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12496   int lastClipUniOfs;
12497   bool doClipUnion; // specal mode
12498   GLNVGshader shaderFillFBO;
12499   GLNVGshader shaderCopyFBO;
12500 
12501   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12502   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12503   ThreadID mainTID;
12504   uint mainFBO;
12505 
12506   // Per frame buffers
12507   GLNVGcall* calls;
12508   int ccalls;
12509   int ncalls;
12510   GLNVGpath* paths;
12511   int cpaths;
12512   int npaths;
12513   NVGVertex* verts;
12514   int cverts;
12515   int nverts;
12516   ubyte* uniforms;
12517   int cuniforms;
12518   int nuniforms;
12519   NVGMatrix lastAffine;
12520 
12521   // cached state
12522   static if (NANOVG_GL_USE_STATE_FILTER) {
12523     GLuint boundTexture;
12524     GLuint stencilMask;
12525     GLenum stencilFunc;
12526     GLint stencilFuncRef;
12527     GLuint stencilFuncMask;
12528     GLNVGblend blendFunc;
12529   }
12530 }
12531 
12532 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12533 
12534 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12535   static if (NANOVG_GL_USE_STATE_FILTER) {
12536     if (gl.boundTexture != tex) {
12537       gl.boundTexture = tex;
12538       glBindTexture(GL_TEXTURE_2D, tex);
12539     }
12540   } else {
12541     glBindTexture(GL_TEXTURE_2D, tex);
12542   }
12543 }
12544 
12545 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12546   static if (NANOVG_GL_USE_STATE_FILTER) {
12547     if (gl.stencilMask != mask) {
12548       gl.stencilMask = mask;
12549       glStencilMask(mask);
12550     }
12551   } else {
12552     glStencilMask(mask);
12553   }
12554 }
12555 
12556 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12557   static if (NANOVG_GL_USE_STATE_FILTER) {
12558     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12559       gl.stencilFunc = func;
12560       gl.stencilFuncRef = ref_;
12561       gl.stencilFuncMask = mask;
12562       glStencilFunc(func, ref_, mask);
12563     }
12564   } else {
12565     glStencilFunc(func, ref_, mask);
12566   }
12567 }
12568 
12569 // texture id is never zero
12570 // sets refcount to one
12571 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12572   GLNVGtexture* tex = null;
12573 
12574   int tid = gl.freetexid;
12575   if (tid == -1) {
12576     if (gl.ntextures >= gl.ctextures) {
12577       assert(gl.ntextures == gl.ctextures);
12578       //pragma(msg, GLNVGtexture.sizeof*32);
12579       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12580       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12581       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12582       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12583       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12584       gl.textures = textures;
12585       gl.ctextures = ctextures;
12586     }
12587     assert(gl.ntextures+1 <= gl.ctextures);
12588     tid = gl.ntextures++;
12589     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12590   } else {
12591     gl.freetexid = gl.textures[tid].nextfree;
12592   }
12593   assert(tid <= gl.ntextures);
12594 
12595   assert(gl.textures[tid].id == 0);
12596   tex = &gl.textures[tid];
12597   memset(tex, 0, (*tex).sizeof);
12598   tex.id = tid+1;
12599   tex.rc = 1;
12600   tex.nextfree = -1;
12601 
12602   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12603 
12604   return tex;
12605 }
12606 
12607 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12608   if (id <= 0 || id > gl.ntextures) return null;
12609   if (gl.textures[id-1].id == 0) return null; // free one
12610   assert(gl.textures[id-1].id == id);
12611   return &gl.textures[id-1];
12612 }
12613 
12614 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12615   if (id <= 0 || id > gl.ntextures) return false;
12616   auto tx = &gl.textures[id-1];
12617   if (tx.id == 0) { id = 0; return false; } // free one
12618   assert(tx.id == id);
12619   assert(tx.tex != 0);
12620   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12621   import core.atomic : atomicOp;
12622   if (atomicOp!"-="(tx.rc, 1) == 0) {
12623     import core.thread : ThreadID;
12624     ThreadID mytid;
12625     static if (__VERSION__ < 2076) {
12626       DGNoThrowNoGC(() {
12627         import core.thread; mytid = Thread.getThis.id;
12628       })();
12629     } else {
12630       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12631     }
12632     if (gl.mainTID == mytid && gl.inFrame) {
12633       // can delete it right now
12634       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12635       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12636       memset(tx, 0, (*tx).sizeof);
12637       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12638       tx.nextfree = gl.freetexid;
12639       gl.freetexid = id-1;
12640     } else {
12641       // alas, we aren't doing frame business, so we should postpone deletion
12642       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12643       version(aliced) {
12644 	{
12645         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12646           tx.id = 0; // mark it as dead
12647           gl.mustCleanTextures = true; // set "need cleanup" flag
12648 	}
12649       } else {
12650         try {
12651             GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12652             tx.id = 0; // mark it as dead
12653             gl.mustCleanTextures = true; // set "need cleanup" flag
12654         } catch (Exception e) {}
12655       }
12656     }
12657   }
12658   id = 0;
12659   return true;
12660 }
12661 
12662 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12663   import core.stdc.stdio : fprintf, stderr;
12664   GLchar[512+1] str = 0;
12665   GLsizei len = 0;
12666   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12667   if (len > 512) len = 512;
12668   str[len] = '\0';
12669   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12670 }
12671 
12672 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12673   import core.stdc.stdio : fprintf, stderr;
12674   GLchar[512+1] str = 0;
12675   GLsizei len = 0;
12676   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12677   if (len > 512) len = 512;
12678   str[len] = '\0';
12679   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12680 }
12681 
12682 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12683   static if (!force) {
12684     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12685   }
12686   glGetError();
12687 }
12688 
12689 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12690   GLenum err;
12691   static if (!force) {
12692     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12693   }
12694   err = glGetError();
12695   if (err != GL_NO_ERROR) {
12696     import core.stdc.stdio : fprintf, stderr;
12697     fprintf(stderr, "Error %08x after %s\n", err, str);
12698     return;
12699   }
12700 }
12701 
12702 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12703   GLint status;
12704   GLuint prog, vert, frag;
12705   const(char)*[3] str;
12706 
12707   memset(shader, 0, (*shader).sizeof);
12708 
12709   prog = glCreateProgram();
12710   vert = glCreateShader(GL_VERTEX_SHADER);
12711   frag = glCreateShader(GL_FRAGMENT_SHADER);
12712   str[0] = header;
12713   str[1] = (opts !is null ? opts : "");
12714   str[2] = vshader;
12715   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12716 
12717   glCompileShader(vert);
12718   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12719   if (status != GL_TRUE) {
12720     glnvg__dumpShaderError(vert, name, "vert");
12721     return false;
12722   }
12723 
12724   str[0] = header;
12725   str[1] = (opts !is null ? opts : "");
12726   str[2] = fshader;
12727   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12728 
12729   glCompileShader(frag);
12730   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12731   if (status != GL_TRUE) {
12732     glnvg__dumpShaderError(frag, name, "frag");
12733     return false;
12734   }
12735 
12736   glAttachShader(prog, vert);
12737   glAttachShader(prog, frag);
12738 
12739   glBindAttribLocation(prog, 0, "vertex");
12740   glBindAttribLocation(prog, 1, "tcoord");
12741 
12742   glLinkProgram(prog);
12743   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12744   if (status != GL_TRUE) {
12745     glnvg__dumpProgramError(prog, name);
12746     return false;
12747   }
12748 
12749   shader.prog = prog;
12750   shader.vert = vert;
12751   shader.frag = frag;
12752 
12753   return true;
12754 }
12755 
12756 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12757   if (shader.prog != 0) glDeleteProgram(shader.prog);
12758   if (shader.vert != 0) glDeleteShader(shader.vert);
12759   if (shader.frag != 0) glDeleteShader(shader.frag);
12760 }
12761 
12762 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12763   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12764   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12765   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12766   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12767   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12768   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12769 }
12770 
12771 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12772   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12773     if (fbo != 0) {
12774       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12775       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12776       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12777       glDeleteFramebuffers(1, &fbo);
12778       fbo = 0;
12779     }
12780   }
12781   gl.fboWidth = gl.fboHeight = 0;
12782 }
12783 
12784 // returns `true` is new FBO was created
12785 // will not unbind buffer, if it was created
12786 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12787   assert(fidx >= 0 && fidx < gl.fbo.length);
12788   assert(gl.fboWidth > 0);
12789   assert(gl.fboHeight > 0);
12790 
12791   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12792 
12793   glnvg__resetError(gl);
12794 
12795   // allocate FBO object
12796   GLuint fbo = 0;
12797   glGenFramebuffers(1, &fbo);
12798   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12799   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12800   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12801   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12802 
12803   // attach 2D texture to this FBO
12804   GLuint tidColor = 0;
12805   glGenTextures(1, &tidColor);
12806   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12807   glBindTexture(GL_TEXTURE_2D, tidColor);
12808   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12809   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12810   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12811   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12812   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12813   //FIXME: linear or nearest?
12814   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12815   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12816   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12817   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12818   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12819   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12820   // empty texture
12821   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12822   // create texture with only one color channel
12823   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12824   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12825   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12826   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12827   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12828 
12829   // attach stencil texture to this FBO
12830   GLuint tidStencil = 0;
12831   version(nanovega_shared_stencil) {
12832     if (gl.fboTex.ptr[0].ptr[0] == 0) {
12833       glGenTextures(1, &tidStencil);
12834       if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12835       gl.fboTex.ptr[0].ptr[0] = tidStencil;
12836     } else {
12837       tidStencil = gl.fboTex.ptr[0].ptr[0];
12838     }
12839     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12840   } else {
12841     glGenTextures(1, &tidStencil);
12842     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12843     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12844   }
12845   glBindTexture(GL_TEXTURE_2D, tidStencil);
12846   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12847   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12848   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12849   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12850 
12851   {
12852     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12853     if (status != GL_FRAMEBUFFER_COMPLETE) {
12854       version(all) {
12855         import core.stdc.stdio;
12856         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12857         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12858         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12859         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12860       }
12861       assert(0, "NanoVega: framebuffer creation failed");
12862     }
12863   }
12864 
12865   // clear 'em all
12866   if (doclear) {
12867     glClearColor(0, 0, 0, 0);
12868     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12869   }
12870 
12871   // save texture ids
12872   gl.fbo.ptr[fidx] = fbo;
12873   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12874   version(nanovega_shared_stencil) {} else {
12875     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12876   }
12877 
12878   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12879 
12880   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12881 
12882   return true;
12883 }
12884 
12885 // will not unbind buffer
12886 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12887   assert(fidx >= 0 && fidx < gl.fbo.length);
12888   assert(gl.fboWidth > 0);
12889   assert(gl.fboHeight > 0);
12890   assert(gl.fbo.ptr[fidx] != 0);
12891   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12892   glClearColor(0, 0, 0, 0);
12893   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12894   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12895 }
12896 
12897 // will not unbind buffer
12898 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12899   import core.stdc.string : memset;
12900   assert(didx >= 0 && didx < gl.fbo.length);
12901   assert(sidx >= 0 && sidx < gl.fbo.length);
12902   assert(gl.fboWidth > 0);
12903   assert(gl.fboHeight > 0);
12904   assert(gl.fbo.ptr[didx] != 0);
12905   assert(gl.fbo.ptr[sidx] != 0);
12906   if (didx == sidx) return;
12907 
12908   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12909 
12910   glUseProgram(gl.shaderCopyFBO.prog);
12911 
12912   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12913   glDisable(GL_CULL_FACE);
12914   glDisable(GL_BLEND);
12915   glDisable(GL_SCISSOR_TEST);
12916   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12917   // copy texture by drawing full quad
12918   enum x = 0;
12919   enum y = 0;
12920   immutable int w = gl.fboWidth;
12921   immutable int h = gl.fboHeight;
12922   immutable(NVGVertex[4]) vertices =
12923    [NVGVertex(x, y), // top-left
12924     NVGVertex(w, y), // top-right
12925     NVGVertex(w, h), // bottom-right
12926     NVGVertex(x, h)]; // bottom-left
12927 
12928   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12929   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12930 
12931   // restore state (but don't unbind FBO)
12932   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12933   glEnable(GL_CULL_FACE);
12934   glEnable(GL_BLEND);
12935   glUseProgram(gl.shader.prog);
12936 }
12937 
12938 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12939   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12940   /*
12941   if (gl.lastClipFBO >= 0) {
12942     glActiveTexture(GL_TEXTURE1);
12943     glBindTexture(GL_TEXTURE_2D, 0);
12944     glActiveTexture(GL_TEXTURE0);
12945   }
12946   */
12947   gl.lastClipFBO = -666;
12948 }
12949 
12950 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12951   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12952   if (gl.lastClipFBO != -666) {
12953     // cached
12954     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12955     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12956     return;
12957   }
12958 
12959   // no cache
12960   int fboidx = -1;
12961   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12962     final switch (mst) {
12963       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12964       case GLMaskState.Uninitialized: break;
12965       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12966       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12967     }
12968   }
12969 
12970   if (fboidx < 0) {
12971     // don't mask
12972     gl.lastClipFBO = -1;
12973     frag.doclip = 0;
12974   } else {
12975     // do masking
12976     assert(gl.fbo.ptr[fboidx] != 0);
12977     gl.lastClipFBO = fboidx;
12978     frag.doclip = 1;
12979   }
12980 
12981   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12982 
12983   if (gl.lastClipFBO >= 0) {
12984     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12985     glActiveTexture(GL_TEXTURE1);
12986     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12987     glActiveTexture(GL_TEXTURE0);
12988   }
12989 }
12990 
12991 // returns index in `gl.fbo`, or -1 for "don't mask"
12992 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12993   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12994   // we need initialized FBO, even for "don't mask" case
12995   // for this, look back in stack, and either copy initialized FBO,
12996   // or stop at first uninitialized one, and clear it
12997   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12998     // shortcut
12999     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); }
13000     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
13001     return gl.msp-1;
13002   }
13003   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
13004     final switch (gl.maskStack.ptr[sp]) {
13005       case GLMaskState.DontMask:
13006         // clear it
13007         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
13008         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13009         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13010         return gl.msp-1;
13011       case GLMaskState.Uninitialized: break; // do nothing
13012       case GLMaskState.Initialized:
13013         // i found her! copy to TOS
13014         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13015         glnvg__allocFBO(gl, gl.msp-1, false);
13016         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13017         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13018         return gl.msp-1;
13019       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13020     }
13021   }
13022   // nothing was initialized, lol
13023   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13024   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13025   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13026   return gl.msp-1;
13027 }
13028 
13029 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13030   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13031   GLNVGcall* call = glnvg__allocCall(gl);
13032   if (call is null) return;
13033   call.type = GLNVG_PUSHCLIP;
13034 }
13035 
13036 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13037   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13038   GLNVGcall* call = glnvg__allocCall(gl);
13039   if (call is null) return;
13040   call.type = GLNVG_POPCLIP;
13041 }
13042 
13043 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13044   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13045   GLNVGcall* call = glnvg__allocCall(gl);
13046   if (call is null) return;
13047   call.type = GLNVG_RESETCLIP;
13048 }
13049 
13050 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13051   version(nanovega_debug_clipping) {
13052     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13053     GLNVGcall* call = glnvg__allocCall(gl);
13054     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13055   }
13056 }
13057 
13058 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13059   import core.stdc.stdio : snprintf;
13060 
13061   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13062   enum align_ = 4;
13063 
13064   char[64] shaderHeader = void;
13065   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13066   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13067 
13068   enum fillVertShader = q{
13069     uniform vec2 viewSize;
13070     attribute vec2 vertex;
13071     attribute vec2 tcoord;
13072     varying vec2 ftcoord;
13073     varying vec2 fpos;
13074     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13075     uniform vec2 ttr; /* tx and ty of affine matrix */
13076     void main (void) {
13077       /* affine transformation */
13078       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13079       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13080       ftcoord = tcoord;
13081       fpos = vec2(nx, ny);
13082       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13083     }
13084   };
13085 
13086   enum fillFragShader = `
13087     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13088     uniform sampler2D tex;
13089     uniform sampler2D clipTex;
13090     uniform vec2 viewSize;
13091     varying vec2 ftcoord;
13092     varying vec2 fpos;
13093     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13094     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13095     #define innerCol frag[6]
13096     #define middleCol frag[7]
13097     #define outerCol frag[7+1]
13098     #define scissorExt frag[8+1].xy
13099     #define scissorScale frag[8+1].zw
13100     #define extent frag[9+1].xy
13101     #define radius frag[9+1].z
13102     #define feather frag[9+1].w
13103     #define strokeMult frag[10+1].x
13104     #define strokeThr frag[10+1].y
13105     #define texType int(frag[10+1].z)
13106     #define type int(frag[10+1].w)
13107     #define doclip int(frag[11+1].x)
13108     #define midp frag[11+1].y
13109 
13110     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13111       vec2 ext2 = ext-vec2(rad, rad);
13112       vec2 d = abs(pt)-ext2;
13113       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13114     }
13115 
13116     // Scissoring
13117     float scissorMask (in vec2 p) {
13118       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13119       sc = vec2(0.5, 0.5)-sc*scissorScale;
13120       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13121     }
13122 
13123     #ifdef EDGE_AA
13124     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13125     float strokeMask () {
13126       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13127     }
13128     #endif
13129 
13130     void main (void) {
13131       // clipping
13132       if (doclip != 0) {
13133         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13134         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13135         if (clr.r == 0.0) discard;
13136       }
13137       float scissor = scissorMask(fpos);
13138       if (scissor <= 0.0) discard; //k8: is it really faster?
13139       #ifdef EDGE_AA
13140       float strokeAlpha = strokeMask();
13141       if (strokeAlpha < strokeThr) discard;
13142       #else
13143       float strokeAlpha = 1.0;
13144       #endif
13145       // rendering
13146       vec4 color;
13147       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13148         color = innerCol;
13149         // Combine alpha
13150         color *= strokeAlpha*scissor;
13151       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13152         // Gradient
13153         // Calculate gradient color using box gradient
13154         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13155         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13156         if (midp <= 0.0) {
13157           color = mix(innerCol, outerCol, d);
13158         } else {
13159           float gdst = min(midp, 1.0);
13160           if (d < gdst) {
13161             color = mix(innerCol, middleCol, d/gdst);
13162           } else {
13163             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13164           }
13165         }
13166         // Combine alpha
13167         color *= strokeAlpha*scissor;
13168       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13169         // Image
13170         // Calculate color from texture
13171         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13172         color = texture2D(tex, pt);
13173         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13174         if (texType == 2) color = vec4(color.x);
13175         // Apply color tint and alpha
13176         color *= innerCol;
13177         // Combine alpha
13178         color *= strokeAlpha*scissor;
13179       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13180         // Stencil fill
13181         color = vec4(1, 1, 1, 1);
13182       } else if (type == 4) { /* NSVG_SHADER_IMG */
13183         // Textured tris
13184         color = texture2D(tex, ftcoord);
13185         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13186         if (texType == 2) color = vec4(color.x);
13187         color *= scissor;
13188         color *= innerCol; // Apply color tint
13189       }
13190       gl_FragColor = color;
13191     }
13192   `;
13193 
13194   enum clipVertShaderFill = q{
13195     uniform vec2 viewSize;
13196     attribute vec2 vertex;
13197     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13198     uniform vec2 ttr; /* tx and ty of affine matrix */
13199     void main (void) {
13200       /* affine transformation */
13201       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13202       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13203       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13204     }
13205   };
13206 
13207   enum clipFragShaderFill = q{
13208     uniform vec2 viewSize;
13209     void main (void) {
13210       gl_FragColor = vec4(1, 1, 1, 1);
13211     }
13212   };
13213 
13214   enum clipVertShaderCopy = q{
13215     uniform vec2 viewSize;
13216     attribute vec2 vertex;
13217     void main (void) {
13218       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13219     }
13220   };
13221 
13222   enum clipFragShaderCopy = q{
13223     uniform sampler2D tex;
13224     uniform vec2 viewSize;
13225     void main (void) {
13226       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13227       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13228     }
13229   };
13230 
13231   glnvg__checkError(gl, "init");
13232 
13233   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13234   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13235   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13236   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13237 
13238   glnvg__checkError(gl, "uniform locations");
13239   glnvg__getUniforms(&gl.shader);
13240   glnvg__getUniforms(&gl.shaderFillFBO);
13241   glnvg__getUniforms(&gl.shaderCopyFBO);
13242 
13243   // Create dynamic vertex array
13244   glGenBuffers(1, &gl.vertBuf);
13245 
13246   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13247 
13248   glnvg__checkError(gl, "create done");
13249 
13250   glFinish();
13251 
13252   return true;
13253 }
13254 
13255 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13256   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13257   GLNVGtexture* tex = glnvg__allocTexture(gl);
13258 
13259   if (tex is null) return 0;
13260 
13261   glGenTextures(1, &tex.tex);
13262   tex.width = w;
13263   tex.height = h;
13264   tex.type = type;
13265   tex.flags = imageFlags;
13266   glnvg__bindTexture(gl, tex.tex);
13267 
13268   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13269 
13270   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13271   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13272   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13273   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13274 
13275 
13276 
13277   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13278   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13279   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13280   // it needs to be called after the glTexImage2D call
13281   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13282     glGenerateMipmap(GL_TEXTURE_2D);
13283 
13284   immutable tfmin =
13285     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13286      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13287      GL_LINEAR);
13288   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13289   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13290   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13291 
13292   int flag;
13293   if (imageFlags&NVGImageFlag.RepeatX)
13294     flag = GL_REPEAT;
13295   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13296     flag = GL_CLAMP_TO_BORDER;
13297   else
13298     flag = GL_CLAMP_TO_EDGE;
13299   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13300 
13301 
13302   if (imageFlags&NVGImageFlag.RepeatY)
13303     flag = GL_REPEAT;
13304   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13305     flag = GL_CLAMP_TO_BORDER;
13306   else
13307     flag = GL_CLAMP_TO_EDGE;
13308   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13309 
13310   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13311   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13312   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13313   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13314 
13315   glnvg__checkError(gl, "create tex");
13316   glnvg__bindTexture(gl, 0);
13317 
13318   return tex.id;
13319 }
13320 
13321 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13322   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13323   return glnvg__deleteTexture(gl, image);
13324 }
13325 
13326 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13327   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13328   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13329   if (tex is null) {
13330     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13331     return false;
13332   }
13333   import core.atomic : atomicOp;
13334   atomicOp!"+="(tex.rc, 1);
13335   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13336   return true;
13337 }
13338 
13339 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13340   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13341   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13342 
13343   if (tex is null) {
13344     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13345     return false;
13346   }
13347 
13348   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13349 
13350   glnvg__bindTexture(gl, tex.tex);
13351 
13352   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13353   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13354   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13355   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13356 
13357   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13358   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13359 
13360   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13361   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13362   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13363   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13364 
13365   glnvg__bindTexture(gl, 0);
13366 
13367   return true;
13368 }
13369 
13370 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13371   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13372   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13373   if (tex is null) {
13374     if (w !is null) *w = 0;
13375     if (h !is null) *h = 0;
13376     return false;
13377   } else {
13378     if (w !is null) *w = tex.width;
13379     if (h !is null) *h = tex.height;
13380     return true;
13381   }
13382 }
13383 
13384 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13385   assert(t.length >= 6);
13386   assert(m3.length >= 12);
13387   m3.ptr[0] = t.ptr[0];
13388   m3.ptr[1] = t.ptr[1];
13389   m3.ptr[2] = 0.0f;
13390   m3.ptr[3] = 0.0f;
13391   m3.ptr[4] = t.ptr[2];
13392   m3.ptr[5] = t.ptr[3];
13393   m3.ptr[6] = 0.0f;
13394   m3.ptr[7] = 0.0f;
13395   m3.ptr[8] = t.ptr[4];
13396   m3.ptr[9] = t.ptr[5];
13397   m3.ptr[10] = 1.0f;
13398   m3.ptr[11] = 0.0f;
13399 }
13400 
13401 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc {
13402   //pragma(inline, true);
13403   NVGColor res = void;
13404   res.r = c.r*c.a;
13405   res.g = c.g*c.a;
13406   res.b = c.b*c.a;
13407   res.a = c.a;
13408   return res;
13409 }
13410 
13411 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13412   import core.stdc.math : sqrtf;
13413   GLNVGtexture* tex = null;
13414   NVGMatrix invxform = void;
13415 
13416   memset(frag, 0, (*frag).sizeof);
13417 
13418   frag.innerCol = glnvg__premulColor(paint.innerColor);
13419   frag.middleCol = glnvg__premulColor(paint.middleColor);
13420   frag.outerCol = glnvg__premulColor(paint.outerColor);
13421   frag.midp = paint.midp;
13422 
13423   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13424     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13425     frag.scissorExt.ptr[0] = 1.0f;
13426     frag.scissorExt.ptr[1] = 1.0f;
13427     frag.scissorScale.ptr[0] = 1.0f;
13428     frag.scissorScale.ptr[1] = 1.0f;
13429   } else {
13430     //nvgTransformInverse(invxform[], scissor.xform[]);
13431     invxform = scissor.xform.inverted;
13432     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13433     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13434     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13435     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;
13436     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;
13437   }
13438 
13439   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13440   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13441   frag.strokeThr = strokeThr;
13442 
13443   if (paint.image.valid) {
13444     tex = glnvg__findTexture(gl, paint.image.id);
13445     if (tex is null) return false;
13446     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13447       /*
13448       NVGMatrix flipped;
13449       nvgTransformScale(flipped[], 1.0f, -1.0f);
13450       nvgTransformMultiply(flipped[], paint.xform[]);
13451       nvgTransformInverse(invxform[], flipped[]);
13452       */
13453       /*
13454       NVGMatrix m1 = void, m2 = void;
13455       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13456       nvgTransformMultiply(m1[], paint.xform[]);
13457       nvgTransformScale(m2[], 1.0f, -1.0f);
13458       nvgTransformMultiply(m2[], m1[]);
13459       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13460       nvgTransformMultiply(m1[], m2[]);
13461       nvgTransformInverse(invxform[], m1[]);
13462       */
13463       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13464       m1.mul(paint.xform);
13465       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13466       m2.mul(m1);
13467       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13468       m1.mul(m2);
13469       invxform = m1.inverted;
13470     } else {
13471       //nvgTransformInverse(invxform[], paint.xform[]);
13472       invxform = paint.xform.inverted;
13473     }
13474     frag.type = NSVG_SHADER_FILLIMG;
13475 
13476     if (tex.type == NVGtexture.RGBA) {
13477       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13478     } else {
13479       frag.texType = 2;
13480     }
13481     //printf("frag.texType = %d\n", frag.texType);
13482   } else {
13483     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13484     frag.radius = paint.radius;
13485     frag.feather = paint.feather;
13486     //nvgTransformInverse(invxform[], paint.xform[]);
13487     invxform = paint.xform.inverted;
13488   }
13489 
13490   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13491 
13492   return true;
13493 }
13494 
13495 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13496   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13497   glnvg__setFBOClipTexture(gl, frag);
13498   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13499   glnvg__checkError(gl, "glnvg__setUniforms");
13500   if (image != 0) {
13501     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13502     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13503     glnvg__checkError(gl, "tex paint tex");
13504   } else {
13505     glnvg__bindTexture(gl, 0);
13506   }
13507 }
13508 
13509 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13510   assert(clipmode != NVGClipMode.None);
13511 
13512   // fill FBO, clear stencil buffer
13513   //TODO: optimize with bounds?
13514   version(all) {
13515     //glnvg__resetAffine(gl);
13516     //glUseProgram(gl.shaderFillFBO.prog);
13517     glDisable(GL_CULL_FACE);
13518     glDisable(GL_BLEND);
13519     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13520     glEnable(GL_STENCIL_TEST);
13521     if (gl.doClipUnion) {
13522       // for "and" we should clear everything that is NOT stencil-masked
13523       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13524       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13525     } else {
13526       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13527       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13528     }
13529 
13530     immutable(NVGVertex[4]) vertices =
13531      [NVGVertex(0, 0, 0, 0),
13532       NVGVertex(0, gl.fboHeight, 0, 0),
13533       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13534       NVGVertex(gl.fboWidth, 0, 0, 0)];
13535 
13536     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13537     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13538 
13539     //glnvg__restoreAffine(gl);
13540   }
13541 
13542   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13543   glDisable(GL_COLOR_LOGIC_OP);
13544   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13545   glEnable(GL_BLEND);
13546   glDisable(GL_STENCIL_TEST);
13547   glEnable(GL_CULL_FACE);
13548   glUseProgram(gl.shader.prog);
13549 
13550   // set current FBO as used one
13551   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13552   if (gl.lastClipFBO != gl.msp-1) {
13553     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); }
13554     gl.lastClipFBO = gl.msp-1;
13555     glActiveTexture(GL_TEXTURE1);
13556     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13557     glActiveTexture(GL_TEXTURE0);
13558   }
13559 }
13560 
13561 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13562   assert(clipmode != NVGClipMode.None);
13563   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13564   // save uniform offset for `glnvg__finishClip()`
13565   gl.lastClipUniOfs = uniformOffset;
13566   // get FBO index, bind this FBO
13567   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13568   assert(clipTexId >= 0);
13569   glUseProgram(gl.shaderFillFBO.prog);
13570   glnvg__checkError(gl, "use");
13571   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13572   // set logic op for clip
13573   gl.doClipUnion = false;
13574   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13575     // it is cleared to zero, we can just draw a path
13576     glDisable(GL_COLOR_LOGIC_OP);
13577     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13578   } else {
13579     glEnable(GL_COLOR_LOGIC_OP);
13580     final switch (clipmode) {
13581       case NVGClipMode.None: assert(0, "wtf?!");
13582       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13583       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13584       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13585       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13586       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13587     }
13588   }
13589   // set affine matrix
13590   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13591   glnvg__checkError(gl, "affine 0");
13592   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13593   glnvg__checkError(gl, "affine 1");
13594   // setup common OpenGL parameters
13595   glDisable(GL_BLEND);
13596   glDisable(GL_CULL_FACE);
13597   glEnable(GL_STENCIL_TEST);
13598   glnvg__stencilMask(gl, 0xff);
13599   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13600   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13601   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13602 }
13603 
13604 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13605   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13606   gl.inFrame = true;
13607   gl.view.ptr[0] = cast(float)width;
13608   gl.view.ptr[1] = cast(float)height;
13609   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13610   if (width != gl.fboWidth || height != gl.fboHeight) {
13611     glnvg__killFBOs(gl);
13612     gl.fboWidth = width;
13613     gl.fboHeight = height;
13614   }
13615   gl.msp = 1;
13616   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13617   // texture cleanup
13618   import core.atomic : atomicLoad;
13619   if (atomicLoad(gl.mustCleanTextures)) {
13620     try {
13621       import core.thread : Thread;
13622       static if (__VERSION__ < 2076) {
13623         DGNoThrowNoGC(() {
13624           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13625         })();
13626       } else {
13627         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13628       }
13629       {
13630         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
13631         gl.mustCleanTextures = false;
13632         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13633           // no need to use atomic ops here, as we're locked
13634           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13635             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13636             import core.stdc.string : memset;
13637             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13638             memset(&tex, 0, tex.sizeof);
13639             tex.nextfree = gl.freetexid;
13640             gl.freetexid = cast(int)tidx;
13641           }
13642         }
13643       }
13644     } catch (Exception e) {}
13645   }
13646 }
13647 
13648 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13649   GLNVGpath* paths = &gl.paths[call.pathOffset];
13650   int npaths = call.pathCount;
13651 
13652   if (call.clipmode == NVGClipMode.None) {
13653     // Draw shapes
13654     glEnable(GL_STENCIL_TEST);
13655     glnvg__stencilMask(gl, 0xffU);
13656     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13657 
13658     glnvg__setUniforms(gl, call.uniformOffset, 0);
13659     glnvg__checkError(gl, "fill simple");
13660 
13661     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13662     if (call.evenOdd) {
13663       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13664       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13665       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13666     } else {
13667       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13668       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13669     }
13670     glDisable(GL_CULL_FACE);
13671     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13672     glEnable(GL_CULL_FACE);
13673 
13674     // Draw anti-aliased pixels
13675     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13676     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13677     glnvg__checkError(gl, "fill fill");
13678 
13679     if (gl.flags&NVGContextFlag.Antialias) {
13680       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13681       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13682       // Draw fringes
13683       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13684     }
13685 
13686     // Draw fill
13687     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13688     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13689     if (call.evenOdd) {
13690       glDisable(GL_CULL_FACE);
13691       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13692       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13693       glEnable(GL_CULL_FACE);
13694     } else {
13695       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13696     }
13697 
13698     glDisable(GL_STENCIL_TEST);
13699   } else {
13700     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13701     glnvg__checkError(gl, "fillclip simple");
13702     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13703     if (call.evenOdd) {
13704       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13705       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13706       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13707     } else {
13708       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13709       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13710     }
13711     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13712     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13713   }
13714 }
13715 
13716 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13717   GLNVGpath* paths = &gl.paths[call.pathOffset];
13718   int npaths = call.pathCount;
13719 
13720   if (call.clipmode == NVGClipMode.None) {
13721     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13722     glnvg__checkError(gl, "convex fill");
13723     if (call.evenOdd) glDisable(GL_CULL_FACE);
13724     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13725     if (gl.flags&NVGContextFlag.Antialias) {
13726       // Draw fringes
13727       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13728     }
13729     if (call.evenOdd) glEnable(GL_CULL_FACE);
13730   } else {
13731     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13732     glnvg__checkError(gl, "clip convex fill");
13733     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13734     if (gl.flags&NVGContextFlag.Antialias) {
13735       // Draw fringes
13736       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13737     }
13738     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13739   }
13740 }
13741 
13742 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13743   GLNVGpath* paths = &gl.paths[call.pathOffset];
13744   int npaths = call.pathCount;
13745 
13746   if (call.clipmode == NVGClipMode.None) {
13747     if (gl.flags&NVGContextFlag.StencilStrokes) {
13748       glEnable(GL_STENCIL_TEST);
13749       glnvg__stencilMask(gl, 0xff);
13750 
13751       // Fill the stroke base without overlap
13752       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13753       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13754       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13755       glnvg__checkError(gl, "stroke fill 0");
13756       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13757 
13758       // Draw anti-aliased pixels.
13759       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13760       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13761       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13762       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13763 
13764       // Clear stencil buffer.
13765       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13766       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13767       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13768       glnvg__checkError(gl, "stroke fill 1");
13769       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13770       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13771 
13772       glDisable(GL_STENCIL_TEST);
13773 
13774       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13775     } else {
13776       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13777       glnvg__checkError(gl, "stroke fill");
13778       // Draw Strokes
13779       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13780     }
13781   } else {
13782     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13783     glnvg__checkError(gl, "stroke fill 0");
13784     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13785     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13786   }
13787 }
13788 
13789 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13790   if (call.clipmode == NVGClipMode.None) {
13791     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13792     glnvg__checkError(gl, "triangles fill");
13793     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13794   } else {
13795     //TODO(?): use texture as mask?
13796   }
13797 }
13798 
13799 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13800   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13801   glnvg__checkError(gl, "affine");
13802   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13803   glnvg__checkError(gl, "affine");
13804   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13805 }
13806 
13807 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13808   scope(exit) gl.inFrame = false;
13809   if (clearTextures && gl.inFrame) {
13810     try {
13811       import core.thread : Thread;
13812       static if (__VERSION__ < 2076) {
13813         DGNoThrowNoGC(() {
13814           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13815         })();
13816       } else {
13817         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13818       }
13819     } catch (Exception e) {}
13820     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13821   }
13822   gl.nverts = 0;
13823   gl.npaths = 0;
13824   gl.ncalls = 0;
13825   gl.nuniforms = 0;
13826   gl.msp = 1;
13827   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13828 }
13829 
13830 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13831   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13832 }
13833 
13834 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13835   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13836   if (factor == NVGBlendFactor.One) return GL_ONE;
13837   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13838   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13839   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13840   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13841   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13842   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13843   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13844   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13845   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13846   return GL_INVALID_ENUM;
13847 }
13848 
13849 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13850   GLNVGblend res;
13851   res.simple = op.simple;
13852   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13853   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13854   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13855   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13856   if (res.simple) {
13857     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13858       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13859     }
13860   } else {
13861     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13862       res.simple = true;
13863       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13864     }
13865   }
13866   return res;
13867 }
13868 
13869 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc {
13870   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13871   static if (NANOVG_GL_USE_STATE_FILTER) {
13872     if (gl.blendFunc.simple == op.simple) {
13873       if (op.simple) {
13874         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13875       } else {
13876         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13877       }
13878     }
13879     gl.blendFunc = op;
13880   }
13881   if (op.simple) {
13882     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13883       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13884     } else {
13885       glBlendFunc(op.srcAlpha, op.dstAlpha);
13886     }
13887   } else {
13888     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13889       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13890     } else {
13891       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13892     }
13893   }
13894 }
13895 
13896 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc {
13897   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13898   GLNVGcall* call;
13899   // if last operation was GLNVG_AFFINE, simply replace the matrix
13900   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13901     call = &gl.calls[gl.ncalls-1];
13902   } else {
13903     call = glnvg__allocCall(gl);
13904     if (call is null) return;
13905     call.type = GLNVG_AFFINE;
13906   }
13907   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13908 }
13909 
13910 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13911 
13912 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13913   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13914   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13915   try {
13916     import core.thread : Thread;
13917     static if (__VERSION__ < 2076) {
13918       DGNoThrowNoGC(() {
13919         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13920       })();
13921     } else {
13922       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13923     }
13924   } catch (Exception e) {}
13925   scope(exit) gl.inFrame = false;
13926 
13927   glnvg__resetError!true(gl);
13928   {
13929     int vv = 0;
13930     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13931     if (glGetError() || vv < 0) vv = 0;
13932     gl.mainFBO = cast(uint)vv;
13933   }
13934 
13935   enum ShaderType { None, Fill, Clip }
13936   auto lastShader = ShaderType.None;
13937   if (gl.ncalls > 0) {
13938     gl.msp = 1;
13939     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13940 
13941     // Setup require GL state.
13942     glUseProgram(gl.shader.prog);
13943 
13944     glActiveTexture(GL_TEXTURE1);
13945     glBindTexture(GL_TEXTURE_2D, 0);
13946     glActiveTexture(GL_TEXTURE0);
13947     glnvg__resetFBOClipTextureCache(gl);
13948 
13949     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13950     static if (NANOVG_GL_USE_STATE_FILTER) {
13951       gl.blendFunc.simple = true;
13952       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13953     }
13954     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13955     glEnable(GL_CULL_FACE);
13956     glCullFace(GL_BACK);
13957     glFrontFace(GL_CCW);
13958     glEnable(GL_BLEND);
13959     glDisable(GL_DEPTH_TEST);
13960     glDisable(GL_SCISSOR_TEST);
13961     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13962     glStencilMask(0xffffffff);
13963     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13964     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13965     glActiveTexture(GL_TEXTURE0);
13966     glBindTexture(GL_TEXTURE_2D, 0);
13967     static if (NANOVG_GL_USE_STATE_FILTER) {
13968       gl.boundTexture = 0;
13969       gl.stencilMask = 0xffffffff;
13970       gl.stencilFunc = GL_ALWAYS;
13971       gl.stencilFuncRef = 0;
13972       gl.stencilFuncMask = 0xffffffff;
13973     }
13974     glnvg__checkError(gl, "OpenGL setup");
13975 
13976     // Upload vertex data
13977     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13978     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
13979     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13980     glEnableVertexAttribArray(0);
13981     glEnableVertexAttribArray(1);
13982     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13983     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13984     glnvg__checkError(gl, "vertex data uploading");
13985 
13986     // Set view and texture just once per frame.
13987     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13988     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13989       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13990       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13991     }
13992     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13993     glnvg__checkError(gl, "render shader setup");
13994 
13995     // Reset affine transformations.
13996     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13997     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13998     glnvg__checkError(gl, "affine setup");
13999 
14000     // set clip shaders params
14001     // fill
14002     glUseProgram(gl.shaderFillFBO.prog);
14003     glnvg__checkError(gl, "clip shaders setup (fill 0)");
14004     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14005     glnvg__checkError(gl, "clip shaders setup (fill 1)");
14006     // copy
14007     glUseProgram(gl.shaderCopyFBO.prog);
14008     glnvg__checkError(gl, "clip shaders setup (copy 0)");
14009     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14010     glnvg__checkError(gl, "clip shaders setup (copy 1)");
14011     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
14012     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14013     glnvg__checkError(gl, "clip shaders setup (copy 2)");
14014     // restore render shader
14015     glUseProgram(gl.shader.prog);
14016 
14017     //{ 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]); }
14018 
14019     gl.lastAffine.identity;
14020 
14021     foreach (int i; 0..gl.ncalls) {
14022       GLNVGcall* call = &gl.calls[i];
14023       switch (call.type) {
14024         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14025         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14026         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14027         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14028         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14029         // clip region management
14030         case GLNVG_PUSHCLIP:
14031           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]); }
14032           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14033           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14034             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14035           } else {
14036             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14037           }
14038           // no need to reset FBO cache here, as nothing was changed
14039           break;
14040         case GLNVG_POPCLIP:
14041           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14042           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]); }
14043           --gl.msp;
14044           assert(gl.msp > 0);
14045           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14046           // check popped item
14047           final switch (gl.maskStack.ptr[gl.msp]) {
14048             case GLMaskState.DontMask:
14049               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14050               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14051                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14052                 glnvg__resetFBOClipTextureCache(gl);
14053               }
14054               break;
14055             case GLMaskState.Uninitialized:
14056               // if last FBO texture was uninitialized, it means that nothing was changed,
14057               // so we can keep using cached FBO
14058               break;
14059             case GLMaskState.Initialized:
14060               // if last FBO was initialized, it means that something was definitely changed
14061               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14062               glnvg__resetFBOClipTextureCache(gl);
14063               break;
14064             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14065           }
14066           break;
14067         case GLNVG_RESETCLIP:
14068           // mark current mask as "don't mask"
14069           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]); }
14070           if (gl.msp > 0) {
14071             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14072               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14073               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14074               glnvg__resetFBOClipTextureCache(gl);
14075             }
14076           }
14077           break;
14078         case GLNVG_CLIP_DDUMP_ON:
14079           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14080           break;
14081         case GLNVG_CLIP_DDUMP_OFF:
14082           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14083           break;
14084         case GLNVG_NONE: break;
14085         default:
14086           {
14087             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14088           }
14089           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14090       }
14091       // and free texture, why not
14092       glnvg__deleteTexture(gl, call.image);
14093     }
14094 
14095     glDisableVertexAttribArray(0);
14096     glDisableVertexAttribArray(1);
14097     glDisable(GL_CULL_FACE);
14098     glBindBuffer(GL_ARRAY_BUFFER, 0);
14099     glUseProgram(0);
14100     glnvg__bindTexture(gl, 0);
14101   }
14102 
14103   // this will do all necessary cleanup
14104   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14105 }
14106 
14107 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14108   int count = 0;
14109   foreach (int i; 0..npaths) {
14110     count += paths[i].nfill;
14111     count += paths[i].nstroke;
14112   }
14113   return count;
14114 }
14115 
14116 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14117   GLNVGcall* ret = null;
14118   if (gl.ncalls+1 > gl.ccalls) {
14119     GLNVGcall* calls;
14120     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14121     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14122     if (calls is null) return null;
14123     gl.calls = calls;
14124     gl.ccalls = ccalls;
14125   }
14126   ret = &gl.calls[gl.ncalls++];
14127   memset(ret, 0, GLNVGcall.sizeof);
14128   return ret;
14129 }
14130 
14131 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14132   int ret = 0;
14133   if (gl.npaths+n > gl.cpaths) {
14134     GLNVGpath* paths;
14135     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14136     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14137     if (paths is null) return -1;
14138     gl.paths = paths;
14139     gl.cpaths = cpaths;
14140   }
14141   ret = gl.npaths;
14142   gl.npaths += n;
14143   return ret;
14144 }
14145 
14146 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14147   int ret = 0;
14148   if (gl.nverts+n > gl.cverts) {
14149     NVGVertex* verts;
14150     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14151     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14152     if (verts is null) return -1;
14153     gl.verts = verts;
14154     gl.cverts = cverts;
14155   }
14156   ret = gl.nverts;
14157   gl.nverts += n;
14158   return ret;
14159 }
14160 
14161 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14162   int ret = 0, structSize = gl.fragSize;
14163   if (gl.nuniforms+n > gl.cuniforms) {
14164     ubyte* uniforms;
14165     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14166     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14167     if (uniforms is null) return -1;
14168     gl.uniforms = uniforms;
14169     gl.cuniforms = cuniforms;
14170   }
14171   ret = gl.nuniforms*structSize;
14172   gl.nuniforms += n;
14173   return ret;
14174 }
14175 
14176 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14177   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14178 }
14179 
14180 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14181   vtx.x = x;
14182   vtx.y = y;
14183   vtx.u = u;
14184   vtx.v = v;
14185 }
14186 
14187 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 {
14188   if (npaths < 1) return;
14189 
14190   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14191   GLNVGcall* call = glnvg__allocCall(gl);
14192   NVGVertex* quad;
14193   GLNVGfragUniforms* frag;
14194   int maxverts, offset;
14195 
14196   if (call is null) return;
14197 
14198   call.type = GLNVG_FILL;
14199   call.evenOdd = evenOdd;
14200   call.clipmode = clipmode;
14201   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14202   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14203   call.triangleCount = 4;
14204   call.pathOffset = glnvg__allocPaths(gl, npaths);
14205   if (call.pathOffset == -1) goto error;
14206   call.pathCount = npaths;
14207   call.image = paint.image.id;
14208   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14209 
14210   if (npaths == 1 && paths[0].convex) {
14211     call.type = GLNVG_CONVEXFILL;
14212     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14213   }
14214 
14215   // Allocate vertices for all the paths.
14216   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14217   offset = glnvg__allocVerts(gl, maxverts);
14218   if (offset == -1) goto error;
14219 
14220   foreach (int i; 0..npaths) {
14221     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14222     const(NVGpath)* path = &paths[i];
14223     memset(copy, 0, GLNVGpath.sizeof);
14224     if (path.nfill > 0) {
14225       copy.fillOffset = offset;
14226       copy.fillCount = path.nfill;
14227       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14228       offset += path.nfill;
14229     }
14230     if (path.nstroke > 0) {
14231       copy.strokeOffset = offset;
14232       copy.strokeCount = path.nstroke;
14233       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14234       offset += path.nstroke;
14235     }
14236   }
14237 
14238   // Setup uniforms for draw calls
14239   if (call.type == GLNVG_FILL) {
14240     import core.stdc.string : memcpy;
14241     // Quad
14242     call.triangleOffset = offset;
14243     quad = &gl.verts[call.triangleOffset];
14244     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14245     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14246     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14247     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14248     // Get uniform
14249     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14250     if (call.uniformOffset == -1) goto error;
14251     // Simple shader for stencil
14252     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14253     memset(frag, 0, (*frag).sizeof);
14254     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14255     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14256     frag.strokeThr = -1.0f;
14257     frag.type = NSVG_SHADER_SIMPLE;
14258     // Fill shader
14259     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14260   } else {
14261     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14262     if (call.uniformOffset == -1) goto error;
14263     // Fill shader
14264     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14265   }
14266 
14267   return;
14268 
14269 error:
14270   // We get here if call alloc was ok, but something else is not.
14271   // Roll back the last call to prevent drawing it.
14272   if (gl.ncalls > 0) --gl.ncalls;
14273 }
14274 
14275 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 {
14276   if (npaths < 1) return;
14277 
14278   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14279   GLNVGcall* call = glnvg__allocCall(gl);
14280   int maxverts, offset;
14281 
14282   if (call is null) return;
14283 
14284   call.type = GLNVG_STROKE;
14285   call.clipmode = clipmode;
14286   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14287   call.pathOffset = glnvg__allocPaths(gl, npaths);
14288   if (call.pathOffset == -1) goto error;
14289   call.pathCount = npaths;
14290   call.image = paint.image.id;
14291   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14292 
14293   // Allocate vertices for all the paths.
14294   maxverts = glnvg__maxVertCount(paths, npaths);
14295   offset = glnvg__allocVerts(gl, maxverts);
14296   if (offset == -1) goto error;
14297 
14298   foreach (int i; 0..npaths) {
14299     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14300     const(NVGpath)* path = &paths[i];
14301     memset(copy, 0, GLNVGpath.sizeof);
14302     if (path.nstroke) {
14303       copy.strokeOffset = offset;
14304       copy.strokeCount = path.nstroke;
14305       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14306       offset += path.nstroke;
14307     }
14308   }
14309 
14310   if (gl.flags&NVGContextFlag.StencilStrokes) {
14311     // Fill shader
14312     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14313     if (call.uniformOffset == -1) goto error;
14314     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14315     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14316   } else {
14317     // Fill shader
14318     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14319     if (call.uniformOffset == -1) goto error;
14320     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14321   }
14322 
14323   return;
14324 
14325 error:
14326   // We get here if call alloc was ok, but something else is not.
14327   // Roll back the last call to prevent drawing it.
14328   if (gl.ncalls > 0) --gl.ncalls;
14329 }
14330 
14331 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14332   if (nverts < 1) return;
14333 
14334   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14335   GLNVGcall* call = glnvg__allocCall(gl);
14336   GLNVGfragUniforms* frag;
14337 
14338   if (call is null) return;
14339 
14340   call.type = GLNVG_TRIANGLES;
14341   call.clipmode = clipmode;
14342   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14343   call.image = paint.image.id;
14344   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14345 
14346   // Allocate vertices for all the paths.
14347   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14348   if (call.triangleOffset == -1) goto error;
14349   call.triangleCount = nverts;
14350 
14351   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14352 
14353   // Fill shader
14354   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14355   if (call.uniformOffset == -1) goto error;
14356   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14357   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14358   frag.type = NSVG_SHADER_IMG;
14359 
14360   return;
14361 
14362 error:
14363   // We get here if call alloc was ok, but something else is not.
14364   // Roll back the last call to prevent drawing it.
14365   if (gl.ncalls > 0) --gl.ncalls;
14366 }
14367 
14368 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14369   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14370   if (gl is null) return;
14371 
14372   glnvg__killFBOs(gl);
14373   glnvg__deleteShader(&gl.shader);
14374   glnvg__deleteShader(&gl.shaderFillFBO);
14375   glnvg__deleteShader(&gl.shaderCopyFBO);
14376 
14377   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14378 
14379   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14380     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14381       assert(tex.tex != 0);
14382       glDeleteTextures(1, &tex.tex);
14383     }
14384   }
14385   free(gl.textures);
14386 
14387   free(gl.paths);
14388   free(gl.verts);
14389   free(gl.uniforms);
14390   free(gl.calls);
14391 
14392   free(gl);
14393 }
14394 
14395 
14396 /** Creates NanoVega contexts for OpenGL2+.
14397  *
14398  * Specify creation flags as additional arguments, like this:
14399  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14400  *
14401  * If you won't specify any flags, defaults will be used:
14402  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14403  *
14404  * Group: context_management
14405  */
14406 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14407   version(aliced) {
14408     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14409   } else {
14410     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14411   }
14412   uint flags = 0;
14413   if (flagList.length != 0) {
14414     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14415   } else {
14416     flags = DefaultFlags;
14417   }
14418   NVGparams params = void;
14419   NVGContext ctx = null;
14420   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14421   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14422   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14423   if (gl is null) goto error;
14424   memset(gl, 0, GLNVGcontext.sizeof);
14425 
14426   memset(&params, 0, params.sizeof);
14427   params.renderCreate = &glnvg__renderCreate;
14428   params.renderCreateTexture = &glnvg__renderCreateTexture;
14429   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14430   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14431   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14432   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14433   params.renderViewport = &glnvg__renderViewport;
14434   params.renderCancel = &glnvg__renderCancel;
14435   params.renderFlush = &glnvg__renderFlush;
14436   params.renderPushClip = &glnvg__renderPushClip;
14437   params.renderPopClip = &glnvg__renderPopClip;
14438   params.renderResetClip = &glnvg__renderResetClip;
14439   params.renderFill = &glnvg__renderFill;
14440   params.renderStroke = &glnvg__renderStroke;
14441   params.renderTriangles = &glnvg__renderTriangles;
14442   params.renderSetAffine = &glnvg__renderSetAffine;
14443   params.renderDelete = &glnvg__renderDelete;
14444   params.userPtr = gl;
14445   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14446   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14447     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14448   } else {
14449     params.fontAA = NVG_INVERT_FONT_AA;
14450   }
14451 
14452   gl.flags = flags;
14453   gl.freetexid = -1;
14454 
14455   ctx = createInternal(&params);
14456   if (ctx is null) goto error;
14457 
14458   static if (__VERSION__ < 2076) {
14459     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14460   } else {
14461     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14462   }
14463 
14464   return ctx;
14465 
14466 error:
14467   // 'gl' is freed by nvgDeleteInternal.
14468   if (ctx !is null) ctx.deleteInternal();
14469   return null;
14470 }
14471 
14472 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14473 /// Group: images
14474 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14475   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14476   GLNVGtexture* tex = glnvg__allocTexture(gl);
14477 
14478   if (tex is null) return 0;
14479 
14480   tex.type = NVGtexture.RGBA;
14481   tex.tex = textureId;
14482   tex.flags = imageFlags;
14483   tex.width = w;
14484   tex.height = h;
14485 
14486   return tex.id;
14487 }
14488 
14489 /// Create NVGImage from OpenGL texture id.
14490 /// Group: images
14491 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14492   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14493 
14494   NVGImage res;
14495   if (id > 0) {
14496     res.id = id;
14497     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14498     res.ctx = ctx;
14499     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14500   }
14501   return res;
14502 }
14503 
14504 /// Returns OpenGL texture id for NanoVega image.
14505 /// Group: images
14506 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14507   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14508   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14509   return tex.tex;
14510 }
14511 
14512 
14513 // ////////////////////////////////////////////////////////////////////////// //
14514 private:
14515 
14516 static if (NanoVegaHasFontConfig) {
14517   version(nanovg_builtin_fontconfig_bindings) {
14518     pragma(lib, "fontconfig");
14519 
14520     private extern(C) nothrow @trusted @nogc {
14521       enum FC_FILE = "file"; /* String */
14522       alias FcBool = int;
14523       alias FcChar8 = char;
14524       struct FcConfig;
14525       struct FcPattern;
14526       alias FcMatchKind = int;
14527       enum : FcMatchKind {
14528         FcMatchPattern,
14529         FcMatchFont,
14530         FcMatchScan
14531       }
14532       alias FcResult = int;
14533       enum : FcResult {
14534         FcResultMatch,
14535         FcResultNoMatch,
14536         FcResultTypeMismatch,
14537         FcResultNoId,
14538         FcResultOutOfMemory
14539       }
14540       FcBool FcInit ();
14541       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14542       void FcDefaultSubstitute (FcPattern* pattern);
14543       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14544       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14545       FcPattern* FcNameParse (const(FcChar8)* name);
14546       void FcPatternDestroy (FcPattern* p);
14547       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14548     }
14549   }
14550 
14551   __gshared bool fontconfigAvailable = false;
14552   // initialize fontconfig
14553   shared static this () {
14554     if (FcInit()) {
14555       fontconfigAvailable = true;
14556     } else {
14557       import core.stdc.stdio : stderr, fprintf;
14558       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14559     }
14560   }
14561 }
14562 
14563 
14564 // ////////////////////////////////////////////////////////////////////////// //
14565 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14566 
14567 private static immutable ubyte[7641] baphometPath = [
14568   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14569   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14570   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14571   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14572   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14573   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14574   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14575   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14576   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14577   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14578   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14579   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14580   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14581   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14582   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14583   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14584   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14585   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14586   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14587   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14588   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14589   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14590   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14591   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14592   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14593   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14594   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14595   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14596   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14597   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14598   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14599   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14600   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14601   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14602   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14603   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14604   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14605   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14606   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14607   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14608   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14609   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14610   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14611   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14612   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14613   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14614   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14615   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14616   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14617   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14618   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14619   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14620   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14621   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14622   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14623   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14624   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14625   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14626   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14627   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14628   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14629   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14630   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14631   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14632   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14633   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14634   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14635   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14636   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14637   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14638   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14639   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14640   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14641   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14642   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14643   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14644   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14645   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14646   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14647   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14648   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14649   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14650   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14651   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14652   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14653   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14654   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14655   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14656   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14657   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14658   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14659   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14660   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14661   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14662   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14663   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14664   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14665   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14666   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14667   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14668   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14669   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14670   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14671   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14672   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14673   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14674   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14675   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14676   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14677   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14678   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14679   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14680   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14681   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14682   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14683   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14684   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14685   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14686   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14687   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14688   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14689   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14690   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14691   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14692   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14693   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14694   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14695   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14696   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14697   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14698   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14699   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14700   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14701   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14702   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14703   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14704   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14705   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14706   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14707   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14708   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14709   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14710   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14711   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14712   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14713   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14714   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14715   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14716   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14717   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14718   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14719   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14720   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14721   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14722   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14723   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14724   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14725   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14726   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14727   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14728   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14729   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14730   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14731   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14732   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14733   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14734   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14735   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14736   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14737   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14738   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14739   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14740   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14741   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14742   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14743   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14744   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14745   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14746   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14747   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14748   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14749   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14750   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14751   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14752   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14753   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14754   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14755   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14756   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14757   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14758   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14759   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14760   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14761   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14762   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14763   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14764   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14765   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14766   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14767   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14768   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14769   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14770   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14771   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14772   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14773   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14774   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14775   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14776   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14777   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14778   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14779   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14780   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14781   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14782   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14783   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14784   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14785   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14786   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14787   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14788   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14789   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14790   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14791   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14792   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14793   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14794   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14795   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14796   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14797   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14798   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14799   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14800   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14801   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14802   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14803   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14804   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14805   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14806   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14807   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14808   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14809   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14810   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14811   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14812   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14813   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14814   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14815   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14816   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14817   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14818   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14819   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14820   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14821   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14822   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14823   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14824   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14825   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14826   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14827   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14828   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14829   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14830   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14831   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14832   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14833   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14834   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14835   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14836   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14837   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14838   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14839   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14840   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14841   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14842   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14843   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14844   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14845   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14846   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14847   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14848   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14849   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14850   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14851   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14852   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14853   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14854   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14855   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14856   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14857   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14858   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14859   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14860   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14861   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14862   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14863   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14864   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14865   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14866   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14867   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14868   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14869   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14870   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14871   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14872   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14873   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14874   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14875   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14876   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14877   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14878   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14879   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14880   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14881   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14882   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14883   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14884   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14885   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14886   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14887   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14888   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14889   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14890   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14891   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14892   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14893   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14894   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14895   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14896   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14897   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14898   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14899   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14900   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14901   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14902   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14903   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14904   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14905   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14906   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14907   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14908   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14909   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14910   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14911   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14912   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14913   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14914   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14915   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14916   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14917   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14918   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14919   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14920   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14921   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14922   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14923   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14924   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14925   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14926   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14927   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14928   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14929   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14930   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14931   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14932   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14933   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14934   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14935   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14936   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14937   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14938   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14939   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14940   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14941   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14942   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14943   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14944   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14945   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14946   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14947   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14948   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14949   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14950   0x09,];
14951 
14952 private struct ThePath {
14953 public:
14954   enum Command {
14955     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14956     StrokeMode,
14957     FillMode,
14958     StrokeFillMode,
14959     NormalStroke,
14960     ThinStroke,
14961     MoveTo,
14962     LineTo,
14963     CubicTo, // cubic bezier
14964     EndPath,
14965   }
14966 
14967 public:
14968   const(ubyte)[] path;
14969   uint ppos;
14970 
14971 public:
14972   this (const(void)[] apath) pure nothrow @trusted @nogc {
14973     path = cast(const(ubyte)[])apath;
14974   }
14975 
14976   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14977 
14978   Command getCommand () nothrow @trusted @nogc {
14979     pragma(inline, true);
14980     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14981     return cast(Command)(path.ptr[ppos++]);
14982   }
14983 
14984   // number of (x,y) pairs for this command
14985   static int argCount (in Command cmd) nothrow @safe @nogc {
14986     version(aliced) pragma(inline, true);
14987          if (cmd == Command.Bounds) return 2;
14988     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14989     else if (cmd == Command.CubicTo) return 3;
14990     else return 0;
14991   }
14992 
14993   void skipArgs (int argc) nothrow @trusted @nogc {
14994     pragma(inline, true);
14995     ppos += cast(uint)(float.sizeof*2*argc);
14996   }
14997 
14998   float getFloat () nothrow @trusted @nogc {
14999     pragma(inline, true);
15000     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
15001     version(LittleEndian) {
15002       float res = *cast(const(float)*)(&path.ptr[ppos]);
15003       ppos += cast(uint)float.sizeof;
15004       return res;
15005     } else {
15006       static assert(float.sizeof == 4);
15007       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
15008       ppos += cast(uint)float.sizeof;
15009       return *cast(const(float)*)(&xp);
15010     }
15011   }
15012 }
15013 
15014 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
15015 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15016   if (nvg is null) return;
15017 
15018   auto path = ThePath(baphometPath);
15019 
15020   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15021   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15022 
15023   bool inPath = false;
15024   while (!path.empty) {
15025     auto cmd = path.getCommand();
15026     switch (cmd) {
15027       case ThePath.Command.MoveTo:
15028         inPath = true;
15029         immutable float ex = getScaledX();
15030         immutable float ey = getScaledY();
15031         nvg.moveTo(ex, ey);
15032         break;
15033       case ThePath.Command.LineTo:
15034         inPath = true;
15035         immutable float ex = getScaledX();
15036         immutable float ey = getScaledY();
15037         nvg.lineTo(ex, ey);
15038         break;
15039       case ThePath.Command.CubicTo: // cubic bezier
15040         inPath = true;
15041         immutable float x1 = getScaledX();
15042         immutable float y1 = getScaledY();
15043         immutable float x2 = getScaledX();
15044         immutable float y2 = getScaledY();
15045         immutable float ex = getScaledX();
15046         immutable float ey = getScaledY();
15047         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15048         break;
15049       case ThePath.Command.EndPath:
15050         if (inPath) return;
15051         break;
15052       default:
15053         path.skipArgs(path.argCount(cmd));
15054         break;
15055     }
15056   }
15057 }
15058 
15059 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15060 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 {
15061   // pupils starts with "fill-and-stroke" mode
15062   if (nvg is null) return;
15063 
15064   auto path = ThePath(baphometPath);
15065 
15066   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15067   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15068 
15069   bool inPath = false;
15070   bool pupLeft = true;
15071   while (!path.empty) {
15072     auto cmd = path.getCommand();
15073     switch (cmd) {
15074       case ThePath.Command.StrokeFillMode: inPath = true; break;
15075       case ThePath.Command.MoveTo:
15076         if (!inPath) goto default;
15077         static if (!left) { if (pupLeft) goto default; }
15078         static if (!right) { if (!pupLeft) goto default; }
15079         immutable float ex = getScaledX();
15080         immutable float ey = getScaledY();
15081         nvg.moveTo(ex, ey);
15082         break;
15083       case ThePath.Command.LineTo:
15084         if (!inPath) goto default;
15085         static if (!left) { if (pupLeft) goto default; }
15086         static if (!right) { if (!pupLeft) goto default; }
15087         immutable float ex = getScaledX();
15088         immutable float ey = getScaledY();
15089         nvg.lineTo(ex, ey);
15090         break;
15091       case ThePath.Command.CubicTo: // cubic bezier
15092         if (!inPath) goto default;
15093         static if (!left) { if (pupLeft) goto default; }
15094         static if (!right) { if (!pupLeft) goto default; }
15095         immutable float x1 = getScaledX();
15096         immutable float y1 = getScaledY();
15097         immutable float x2 = getScaledX();
15098         immutable float y2 = getScaledY();
15099         immutable float ex = getScaledX();
15100         immutable float ey = getScaledY();
15101         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15102         break;
15103       case ThePath.Command.EndPath:
15104         if (inPath) {
15105           if (pupLeft) pupLeft = false; else return;
15106         }
15107         break;
15108       default:
15109         path.skipArgs(path.argCount(cmd));
15110         break;
15111     }
15112   }
15113 }
15114 
15115 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15116 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15117   template hasChar(char ch, string s) {
15118          static if (s.length == 0) enum hasChar = false;
15119     else static if (s[0] == ch) enum hasChar = true;
15120     else enum hasChar = hasChar!(ch, s[1..$]);
15121   }
15122   enum AllowStroke = hasChar!('s', mode);
15123   enum AllowFill = hasChar!('f', mode);
15124   enum AllowWidth = hasChar!('w', mode);
15125   enum Contour = hasChar!('c', mode);
15126   //static assert(AllowWidth || AllowFill);
15127 
15128   if (nvg is null) return;
15129 
15130   auto path = ThePath(baphometPath);
15131 
15132   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15133   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15134 
15135   int mode = 0;
15136   int sw = ThePath.Command.NormalStroke;
15137   nvg.beginPath();
15138   while (!path.empty) {
15139     auto cmd = path.getCommand();
15140     switch (cmd) {
15141       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15142       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15143       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15144       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15145       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15146       case ThePath.Command.MoveTo:
15147         immutable float ex = getScaledX();
15148         immutable float ey = getScaledY();
15149         nvg.moveTo(ex, ey);
15150         break;
15151       case ThePath.Command.LineTo:
15152         immutable float ex = getScaledX();
15153         immutable float ey = getScaledY();
15154         nvg.lineTo(ex, ey);
15155         break;
15156       case ThePath.Command.CubicTo: // cubic bezier
15157         immutable float x1 = getScaledX();
15158         immutable float y1 = getScaledY();
15159         immutable float x2 = getScaledX();
15160         immutable float y2 = getScaledY();
15161         immutable float ex = getScaledX();
15162         immutable float ey = getScaledY();
15163         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15164         break;
15165       case ThePath.Command.EndPath:
15166         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15167           static if (AllowFill || Contour) {
15168             static if (Contour) {
15169               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15170             } else {
15171               nvg.fill();
15172             }
15173           }
15174         }
15175         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15176           static if (AllowStroke || Contour) {
15177             static if (AllowWidth) {
15178                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15179               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15180               else assert(0, "wtf?!");
15181             }
15182             nvg.stroke();
15183           }
15184         }
15185         nvg.newPath();
15186         break;
15187       default:
15188         path.skipArgs(path.argCount(cmd));
15189         break;
15190     }
15191   }
15192   nvg.newPath();
15193 }