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 Creating drawing context
112 ========================
113 
114 The drawing context is created using platform specific constructor function.
115 
116   ---
117   NVGContext vg = nvgCreateContext();
118   ---
119 
120 $(WARNING You must use created context ONLY in that thread where you created it.
121           There is no way to "transfer" context between threads. Trying to do so
122           will lead to UB.)
123 
124 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
125           do so will lead to UB.)
126 
127 
128 Drawing shapes with NanoVega
129 ============================
130 
131 Drawing a simple shape using NanoVega consists of four steps:
132 $(LIST
133   * begin a new shape,
134   * define the path to draw,
135   * set fill or stroke,
136   * and finally fill or stroke the path.
137 )
138 
139   ---
140   vg.beginPath();
141   vg.rect(100, 100, 120, 30);
142   vg.fillColor(nvgRGBA(255, 192, 0, 255));
143   vg.fill();
144   ---
145 
146 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
147 There are number of number of functions to define the path to draw, such as rectangle,
148 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
149 arcTo API to compose the paths step by step.
150 
151 
152 Understanding Composite Paths
153 =============================
154 
155 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
156 that is path consisting from multiple paths defining holes and fills, is a bit more
157 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
158 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
159 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
160 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
161 
162   ---
163   vg.beginPath();
164   vg.rect(100, 100, 120, 30);
165   vg.circle(120, 120, 5);
166   vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
167   vg.fillColor(nvgRGBA(255, 192, 0, 255));
168   vg.fill();
169   ---
170 
171 
172 Rendering is wrong, what to do?
173 ===============================
174 
175 $(LIST
176   * make sure you have created NanoVega context using [nvgCreateContext] call
177   * make sure you have initialised OpenGL with $(B stencil buffer)
178   * make sure you have cleared stencil buffer
179   * make sure all rendering calls happen between [beginFrame] and [endFrame]
180   * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
181 )
182 
183 
184 OpenGL state touched by the backend
185 ===================================
186 
187 The OpenGL back-end touches following states:
188 
189 When textures are uploaded or updated, the following pixel store is set to defaults:
190 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
191 Texture binding is also affected. Texture updates can happen when the user loads images,
192 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
193 and [endFrame].
194 
195 The data for the whole frame is buffered and flushed in [endFrame].
196 The following code illustrates the OpenGL state touched by the rendering code:
197 
198   ---
199   glUseProgram(prog);
200   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
201   glEnable(GL_CULL_FACE);
202   glCullFace(GL_BACK);
203   glFrontFace(GL_CCW);
204   glEnable(GL_BLEND);
205   glDisable(GL_DEPTH_TEST);
206   glDisable(GL_SCISSOR_TEST);
207   glDisable(GL_COLOR_LOGIC_OP);
208   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
209   glStencilMask(0xffffffff);
210   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
211   glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
212   glActiveTexture(GL_TEXTURE1);
213   glActiveTexture(GL_TEXTURE0);
214   glBindBuffer(GL_UNIFORM_BUFFER, buf);
215   glBindVertexArray(arr);
216   glBindBuffer(GL_ARRAY_BUFFER, buf);
217   glBindTexture(GL_TEXTURE_2D, tex);
218   glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
219   ---
220 
221   Symbol_groups:
222 
223   context_management =
224     ## Context Management
225 
226     Functions to create and destory NanoVega context.
227 
228   frame_management =
229     ## Frame Management
230 
231     To start drawing with NanoVega context, you have to "begin frame", and then
232     "end frame" to flush your rendering commands to GPU.
233 
234   composite_operation =
235     ## Composite Operation
236 
237     The composite operations in NanoVega are modeled after HTML Canvas API, and
238     the blend func is based on OpenGL (see corresponding manuals for more info).
239     The colors in the blending state have premultiplied alpha.
240 
241   color_utils =
242     ## Color Utils
243 
244     Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
245 
246   matrices =
247     ## Matrices and Transformations
248 
249     The paths, gradients, patterns and scissor region are transformed by an transformation
250     matrix at the time when they are passed to the API.
251     The current transformation matrix is an affine matrix:
252 
253     ----------------------
254       [sx kx tx]
255       [ky sy ty]
256       [ 0  0  1]
257     ----------------------
258 
259     Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
260     The last row is assumed to be (0, 0, 1) and is not stored.
261 
262     Apart from [resetTransform], each transformation function first creates
263     specific transformation matrix and pre-multiplies the current transformation by it.
264 
265     Current coordinate system (transformation) can be saved and restored using [save] and [restore].
266 
267     The following functions can be used to make calculations on 2x3 transformation matrices.
268     A 2x3 matrix is represented as float[6].
269 
270   state_handling =
271     ## State Handling
272 
273     NanoVega contains state which represents how paths will be rendered.
274     The state contains transform, fill and stroke styles, text and font styles,
275     and scissor clipping.
276 
277   render_styles =
278     ## Render Styles
279 
280     Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
281     Solid color is simply defined as a color value, different kinds of paints can be created
282     using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
283 
284     Current render style can be saved and restored using [save] and [restore].
285 
286     Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
287     and use `integerCoord+0.5f` as pixel coordinates.
288 
289   render_transformations =
290     ## Render Transformations
291 
292     Transformation matrix management for the current rendering style. Transformations are applied in
293     backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
294     it's origin, and then translated to the destination point.
295 
296   scissoring =
297     ## Scissoring
298 
299     Scissoring allows you to clip the rendering into a rectangle. This is useful for various
300     user interface cases like rendering a text edit or a timeline.
301 
302   images =
303     ## Images
304 
305     NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
306     In addition you can upload your own image.
307     The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
308 
309     If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
310     [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
311 
312   paints =
313     ## Paints
314 
315     NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
316     These can be used as paints for strokes and fills.
317 
318   gpu_affine =
319     ## Render-Time Affine Transformations
320 
321     It is possible to set affine transformation matrix for GPU. That matrix will
322     be applied by the shader code. This can be used to quickly translate and rotate
323     saved paths. Call this $(B only) between [beginFrame] and [endFrame].
324 
325     Note that [beginFrame] resets this matrix to identity one.
326 
327     $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
328 
329   paths =
330     ## Paths
331 
332     Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
333     Then you define one or more paths and sub-paths which describe the shape. The are functions
334     to draw common shapes like rectangles and circles, and lower level step-by-step functions,
335     which allow to define a path curve by curve.
336 
337     NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
338     winding and holes should have counter clockwise order. To specify winding of a path you can
339     call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
340 
341     Finally you can fill the path using current fill style by calling [fill], and stroke it
342     with current stroke style by calling [stroke].
343 
344     The curve segments and sub-paths are transformed by the current transform.
345 
346   picking_api =
347     ## Picking API
348 
349     This is picking API that works directly on paths, without rasterizing them first.
350 
351     [beginFrame] resets picking state. Then you can create paths as usual, but
352     there is a possibility to perform hit checks $(B before) rasterizing a path.
353     Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
354     immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
355     before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
356     effects, for example.
357 
358     Also note that picking API is ignoring GPU affine transformation matrix.
359     You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
360 
361     $(WARNING Picking API completely ignores clipping. If you want to check for
362               clip regions, you have to manually register them as fill/stroke paths,
363               and perform the necessary logic. See [hitTestForId] function.)
364 
365   clipping =
366     ## Clipping with paths
367 
368     If scissoring is not enough for you, you can clip rendering with arbitrary path,
369     or with combination of paths. Clip region is saved by [save] and restored by
370     [restore] NanoVega functions. You can combine clip paths with various logic
371     operations, see [NVGClipMode].
372 
373     Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
374     is created as if there was no scissor set). Actual rendering is affected by
375     scissors, though.
376 
377   text_api =
378     ## Text
379 
380     NanoVega allows you to load .ttf files and use the font to render text.
381     You have to load some font, and set proper font size before doing anything
382     with text, as there is no "default" font provided by NanoVega. Also, don't
383     forget to check return value of `createFont()`, 'cause NanoVega won't fail
384     if it cannot load font, it will silently try to render nothing.
385 
386     The appearance of the text can be defined by setting the current text style
387     and by specifying the fill color. Common text and font settings such as
388     font size, letter spacing and text align are supported. Font blur allows you
389     to create simple text effects such as drop shadows.
390 
391     At render time the font face can be set based on the font handles or name.
392 
393     Font measure functions return values in local space, the calculations are
394     carried in the same resolution as the final rendering. This is done because
395     the text glyph positions are snapped to the nearest pixels sharp rendering.
396 
397     The local space means that values are not rotated or scale as per the current
398     transformation. For example if you set font size to 12, which would mean that
399     line height is 16, then regardless of the current scaling and rotation, the
400     returned line height is always 16. Some measures may vary because of the scaling
401     since aforementioned pixel snapping.
402 
403     While this may sound a little odd, the setup allows you to always render the
404     same way regardless of scaling. I.e. following works regardless of scaling:
405 
406     ----------------------
407        string txt = "Text me up.";
408        vg.textBounds(x, y, txt, bounds);
409        vg.beginPath();
410        vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
411        vg.fill();
412     ----------------------
413 
414     Note: currently only solid color fill is supported for text.
415 
416   font_stash =
417     ## Low-Level Font Engine (FontStash)
418 
419     FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
420     You don't need any graphics context to use FontStash, so you can do things like text
421     layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
422     to create new FontStash, copy fonts from NanoVega context into it, and use that new
423     FontStash to do some UI layouting, for example. Also note that you can get text metrics
424     without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
425     you don't need to waste CPU and memory resources to render unneeded images into font atlas,
426     and you can layout alot of text very fast.
427 
428     Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
429     word "fons" in it, this is not a typo, and it should not read "font" intead.
430 
431     TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
432  */
433 module arsd.nanovega;
434 
435 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class.
436 unittest {
437 	import arsd.simpledisplay;
438 
439 	import arsd.nanovega;
440 
441 	void main () {
442 	  NVGContext nvg; // our NanoVega context
443 
444 	  // we need at least OpenGL3 with GLSL to use NanoVega,
445 	  // so let's tell simpledisplay about that
446 	  setOpenGLContextVersion(3, 0);
447 
448 	  // now create OpenGL window
449 	  auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing);
450 
451 	  // we need to destroy NanoVega context on window close
452 	  // stricly speaking, it is not necessary, as nothing fatal
453 	  // will happen if you'll forget it, but let's be polite.
454 	  // note that we cannot do that *after* our window was closed,
455 	  // as we need alive OpenGL context to do proper cleanup.
456 	  sdmain.onClosing = delegate () {
457 	    nvg.kill();
458 	  };
459 
460 	  // this is called just before our window will be shown for the first time.
461 	  // we must create NanoVega context here, as it needs to initialize
462 	  // internal OpenGL subsystem with valid OpenGL context.
463 	  sdmain.visibleForTheFirstTime = delegate () {
464 	    // yes, that's all
465 	    nvg = nvgCreateContext();
466 	    if (nvg is null) assert(0, "cannot initialize NanoVega");
467 	  };
468 
469 	  // this callback will be called when we will need to repaint our window
470 	  sdmain.redrawOpenGlScene = delegate () {
471 	    // fix viewport (we can do this in resize event, or here, it doesn't matter)
472 	    glViewport(0, 0, sdmain.width, sdmain.height);
473 
474 	    // clear window
475 	    glClearColor(0, 0, 0, 0);
476 	    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
477 
478 	    {
479 	      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
480 	      scope(exit) nvg.endFrame(); // and flush render queue on exit
481 
482 	      nvg.beginPath(); // start new path
483 	      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
484 	      // now set filling mode for our rectangle
485 	      // you can create colors using HTML syntax, or with convenient constants
486 	      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
487 	      // now fill our rect
488 	      nvg.fill();
489 	      // and draw a nice outline
490 	      nvg.strokeColor = NVGColor.white;
491 	      nvg.strokeWidth = 2;
492 	      nvg.stroke();
493 	      // that's all, folks!
494 	    }
495 	  };
496 
497 	  sdmain.eventLoop(0, // no pulse timer required
498 	    delegate (KeyEvent event) {
499 	      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
500 	    },
501 	  );
502 
503 	  flushGui(); // let OS do it's cleanup
504 	}
505 }
506 
507 private:
508 
509 version(aliced) {
510   import iv.meta;
511   import iv.vfs;
512 } else {
513   private alias usize = size_t;
514   // i fear phobos!
515   private template Unqual(T) {
516          static if (is(T U ==          immutable U)) alias Unqual = U;
517     else static if (is(T U == shared inout const U)) alias Unqual = U;
518     else static if (is(T U == shared inout       U)) alias Unqual = U;
519     else static if (is(T U == shared       const U)) alias Unqual = U;
520     else static if (is(T U == shared             U)) alias Unqual = U;
521     else static if (is(T U ==        inout const U)) alias Unqual = U;
522     else static if (is(T U ==        inout       U)) alias Unqual = U;
523     else static if (is(T U ==              const U)) alias Unqual = U;
524     else alias Unqual = T;
525   }
526   private template isAnyCharType(T, bool unqual=false) {
527     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
528     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
529   }
530   private template isWideCharType(T, bool unqual=false) {
531     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
532     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
533   }
534 }
535 version(nanovg_disable_vfs) {
536   enum NanoVegaHasIVVFS = false;
537 } else {
538   static if (is(typeof((){import iv.vfs;}))) {
539     enum NanoVegaHasIVVFS = true;
540     import iv.vfs;
541   } else {
542     enum NanoVegaHasIVVFS = false;
543   }
544 }
545 
546 // ////////////////////////////////////////////////////////////////////////// //
547 // engine
548 // ////////////////////////////////////////////////////////////////////////// //
549 import core.stdc.stdlib : malloc, realloc, free;
550 import core.stdc.string : memset, memcpy, strlen;
551 import std.math : PI;
552 
553 //version = nanovg_force_stb_ttf;
554 
555 version(Posix) {
556   version = nanovg_use_freetype;
557 } else {
558   version = nanovg_disable_fontconfig;
559 }
560 
561 version (bindbc) {
562   version = nanovg_builtin_fontconfig_bindings;
563   version = nanovg_bindbc_opengl_bindings;
564   version = nanovg_bindbc_freetype_bindings;
565   version(BindFT_Dynamic)
566     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
567   else {
568     version(BindFT_Static) {}
569     else
570       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
571   }
572 } else version(aliced) {
573   version = nanovg_default_no_font_aa;
574   version = nanovg_builtin_fontconfig_bindings;
575   version = nanovg_builtin_freetype_bindings;
576   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
577 } else {
578   version = nanovg_builtin_fontconfig_bindings;
579   version = nanovg_builtin_freetype_bindings;
580   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
581 }
582 
583 version(nanovg_disable_fontconfig) {
584   public enum NanoVegaHasFontConfig = false;
585 } else {
586   public enum NanoVegaHasFontConfig = true;
587   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
588 }
589 
590 //version = nanovg_bench_flatten;
591 
592 /++
593 	Annotation to indicate the marked function is compatible with [arsd.script].
594 
595 
596 	Any function that takes a [Color] argument will be passed a string instead.
597 
598 	Scriptable Functions
599 	====================
600 
601 	$(UDA_USES)
602 
603 	$(ALWAYS_DOCUMENT)
604 +/
605 private enum scriptable = "arsd_jsvar_compatible";
606 
607 public:
608 alias NVG_PI = PI;
609 
610 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
611 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
612 
613 static if (NanoVegaHasArsdColor) private import arsd.color;
614 static if (NanoVegaHasArsdImage) {
615   private import arsd.image;
616 } else {
617   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
618   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
619   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
620   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
621   void stbi_image_free (void* retval_from_stbi_load) {}
622 }
623 
624 version(nanovg_default_no_font_aa) {
625   __gshared bool NVG_INVERT_FONT_AA = false;
626 } else {
627   __gshared bool NVG_INVERT_FONT_AA = true;
628 }
629 
630 
631 /// this is branchless for ints on x86, and even for longs on x86_64
632 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
633   static if (__VERSION__ > 2067) pragma(inline, true);
634   static if (T.sizeof == 2 || T.sizeof == 4) {
635     static if (__traits(isUnsigned, T)) {
636       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
637     } else {
638       n &= -cast(int)(n >= 0);
639       return cast(ubyte)(n|((255-cast(int)n)>>31));
640     }
641   } else static if (T.sizeof == 1) {
642     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
643     return cast(ubyte)n;
644   } else static if (T.sizeof == 8) {
645     static if (__traits(isUnsigned, T)) {
646       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
647     } else {
648       n &= -cast(long)(n >= 0);
649       return cast(ubyte)(n|((255-cast(long)n)>>63));
650     }
651   } else {
652     static assert(false, "clampToByte: integer too big");
653   }
654 }
655 
656 
657 /// NanoVega RGBA color
658 /// Group: color_utils
659 public align(1) struct NVGColor {
660 align(1):
661 public:
662   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
663 
664 public:
665   @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
666 
667 public:
668   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
669   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
670 
671   enum aliceblue = NVGColor(240, 248, 255);
672   enum antiquewhite = NVGColor(250, 235, 215);
673   enum aqua = NVGColor(0, 255, 255);
674   enum aquamarine = NVGColor(127, 255, 212);
675   enum azure = NVGColor(240, 255, 255);
676   enum beige = NVGColor(245, 245, 220);
677   enum bisque = NVGColor(255, 228, 196);
678   enum black = NVGColor(0, 0, 0); // basic color
679   enum blanchedalmond = NVGColor(255, 235, 205);
680   enum blue = NVGColor(0, 0, 255); // basic color
681   enum blueviolet = NVGColor(138, 43, 226);
682   enum brown = NVGColor(165, 42, 42);
683   enum burlywood = NVGColor(222, 184, 135);
684   enum cadetblue = NVGColor(95, 158, 160);
685   enum chartreuse = NVGColor(127, 255, 0);
686   enum chocolate = NVGColor(210, 105, 30);
687   enum coral = NVGColor(255, 127, 80);
688   enum cornflowerblue = NVGColor(100, 149, 237);
689   enum cornsilk = NVGColor(255, 248, 220);
690   enum crimson = NVGColor(220, 20, 60);
691   enum cyan = NVGColor(0, 255, 255); // basic color
692   enum darkblue = NVGColor(0, 0, 139);
693   enum darkcyan = NVGColor(0, 139, 139);
694   enum darkgoldenrod = NVGColor(184, 134, 11);
695   enum darkgray = NVGColor(169, 169, 169);
696   enum darkgreen = NVGColor(0, 100, 0);
697   enum darkgrey = NVGColor(169, 169, 169);
698   enum darkkhaki = NVGColor(189, 183, 107);
699   enum darkmagenta = NVGColor(139, 0, 139);
700   enum darkolivegreen = NVGColor(85, 107, 47);
701   enum darkorange = NVGColor(255, 140, 0);
702   enum darkorchid = NVGColor(153, 50, 204);
703   enum darkred = NVGColor(139, 0, 0);
704   enum darksalmon = NVGColor(233, 150, 122);
705   enum darkseagreen = NVGColor(143, 188, 143);
706   enum darkslateblue = NVGColor(72, 61, 139);
707   enum darkslategray = NVGColor(47, 79, 79);
708   enum darkslategrey = NVGColor(47, 79, 79);
709   enum darkturquoise = NVGColor(0, 206, 209);
710   enum darkviolet = NVGColor(148, 0, 211);
711   enum deeppink = NVGColor(255, 20, 147);
712   enum deepskyblue = NVGColor(0, 191, 255);
713   enum dimgray = NVGColor(105, 105, 105);
714   enum dimgrey = NVGColor(105, 105, 105);
715   enum dodgerblue = NVGColor(30, 144, 255);
716   enum firebrick = NVGColor(178, 34, 34);
717   enum floralwhite = NVGColor(255, 250, 240);
718   enum forestgreen = NVGColor(34, 139, 34);
719   enum fuchsia = NVGColor(255, 0, 255);
720   enum gainsboro = NVGColor(220, 220, 220);
721   enum ghostwhite = NVGColor(248, 248, 255);
722   enum gold = NVGColor(255, 215, 0);
723   enum goldenrod = NVGColor(218, 165, 32);
724   enum gray = NVGColor(128, 128, 128); // basic color
725   enum green = NVGColor(0, 128, 0); // basic color
726   enum greenyellow = NVGColor(173, 255, 47);
727   enum grey = NVGColor(128, 128, 128); // basic color
728   enum honeydew = NVGColor(240, 255, 240);
729   enum hotpink = NVGColor(255, 105, 180);
730   enum indianred = NVGColor(205, 92, 92);
731   enum indigo = NVGColor(75, 0, 130);
732   enum ivory = NVGColor(255, 255, 240);
733   enum khaki = NVGColor(240, 230, 140);
734   enum lavender = NVGColor(230, 230, 250);
735   enum lavenderblush = NVGColor(255, 240, 245);
736   enum lawngreen = NVGColor(124, 252, 0);
737   enum lemonchiffon = NVGColor(255, 250, 205);
738   enum lightblue = NVGColor(173, 216, 230);
739   enum lightcoral = NVGColor(240, 128, 128);
740   enum lightcyan = NVGColor(224, 255, 255);
741   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
742   enum lightgray = NVGColor(211, 211, 211);
743   enum lightgreen = NVGColor(144, 238, 144);
744   enum lightgrey = NVGColor(211, 211, 211);
745   enum lightpink = NVGColor(255, 182, 193);
746   enum lightsalmon = NVGColor(255, 160, 122);
747   enum lightseagreen = NVGColor(32, 178, 170);
748   enum lightskyblue = NVGColor(135, 206, 250);
749   enum lightslategray = NVGColor(119, 136, 153);
750   enum lightslategrey = NVGColor(119, 136, 153);
751   enum lightsteelblue = NVGColor(176, 196, 222);
752   enum lightyellow = NVGColor(255, 255, 224);
753   enum lime = NVGColor(0, 255, 0);
754   enum limegreen = NVGColor(50, 205, 50);
755   enum linen = NVGColor(250, 240, 230);
756   enum magenta = NVGColor(255, 0, 255); // basic color
757   enum maroon = NVGColor(128, 0, 0);
758   enum mediumaquamarine = NVGColor(102, 205, 170);
759   enum mediumblue = NVGColor(0, 0, 205);
760   enum mediumorchid = NVGColor(186, 85, 211);
761   enum mediumpurple = NVGColor(147, 112, 219);
762   enum mediumseagreen = NVGColor(60, 179, 113);
763   enum mediumslateblue = NVGColor(123, 104, 238);
764   enum mediumspringgreen = NVGColor(0, 250, 154);
765   enum mediumturquoise = NVGColor(72, 209, 204);
766   enum mediumvioletred = NVGColor(199, 21, 133);
767   enum midnightblue = NVGColor(25, 25, 112);
768   enum mintcream = NVGColor(245, 255, 250);
769   enum mistyrose = NVGColor(255, 228, 225);
770   enum moccasin = NVGColor(255, 228, 181);
771   enum navajowhite = NVGColor(255, 222, 173);
772   enum navy = NVGColor(0, 0, 128);
773   enum oldlace = NVGColor(253, 245, 230);
774   enum olive = NVGColor(128, 128, 0);
775   enum olivedrab = NVGColor(107, 142, 35);
776   enum orange = NVGColor(255, 165, 0);
777   enum orangered = NVGColor(255, 69, 0);
778   enum orchid = NVGColor(218, 112, 214);
779   enum palegoldenrod = NVGColor(238, 232, 170);
780   enum palegreen = NVGColor(152, 251, 152);
781   enum paleturquoise = NVGColor(175, 238, 238);
782   enum palevioletred = NVGColor(219, 112, 147);
783   enum papayawhip = NVGColor(255, 239, 213);
784   enum peachpuff = NVGColor(255, 218, 185);
785   enum peru = NVGColor(205, 133, 63);
786   enum pink = NVGColor(255, 192, 203);
787   enum plum = NVGColor(221, 160, 221);
788   enum powderblue = NVGColor(176, 224, 230);
789   enum purple = NVGColor(128, 0, 128);
790   enum red = NVGColor(255, 0, 0); // basic color
791   enum rosybrown = NVGColor(188, 143, 143);
792   enum royalblue = NVGColor(65, 105, 225);
793   enum saddlebrown = NVGColor(139, 69, 19);
794   enum salmon = NVGColor(250, 128, 114);
795   enum sandybrown = NVGColor(244, 164, 96);
796   enum seagreen = NVGColor(46, 139, 87);
797   enum seashell = NVGColor(255, 245, 238);
798   enum sienna = NVGColor(160, 82, 45);
799   enum silver = NVGColor(192, 192, 192);
800   enum skyblue = NVGColor(135, 206, 235);
801   enum slateblue = NVGColor(106, 90, 205);
802   enum slategray = NVGColor(112, 128, 144);
803   enum slategrey = NVGColor(112, 128, 144);
804   enum snow = NVGColor(255, 250, 250);
805   enum springgreen = NVGColor(0, 255, 127);
806   enum steelblue = NVGColor(70, 130, 180);
807   enum tan = NVGColor(210, 180, 140);
808   enum teal = NVGColor(0, 128, 128);
809   enum thistle = NVGColor(216, 191, 216);
810   enum tomato = NVGColor(255, 99, 71);
811   enum turquoise = NVGColor(64, 224, 208);
812   enum violet = NVGColor(238, 130, 238);
813   enum wheat = NVGColor(245, 222, 179);
814   enum white = NVGColor(255, 255, 255); // basic color
815   enum whitesmoke = NVGColor(245, 245, 245);
816   enum yellow = NVGColor(255, 255, 0); // basic color
817   enum yellowgreen = NVGColor(154, 205, 50);
818 
819 nothrow @safe @nogc:
820 public:
821   ///
822   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
823     pragma(inline, true);
824     r = ar/255.0f;
825     g = ag/255.0f;
826     b = ab/255.0f;
827     a = aa/255.0f;
828   }
829 
830   ///
831   this (float ar, float ag, float ab, float aa=1.0f) pure {
832     pragma(inline, true);
833     r = ar;
834     g = ag;
835     b = ab;
836     a = aa;
837   }
838 
839   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
840   this (uint c) pure {
841     pragma(inline, true);
842     r = (c&0xff)/255.0f;
843     g = ((c>>8)&0xff)/255.0f;
844     b = ((c>>16)&0xff)/255.0f;
845     a = ((c>>24)&0xff)/255.0f;
846   }
847 
848   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
849   this (const(char)[] srgb) {
850     static int c2d (char ch) pure nothrow @safe @nogc {
851       pragma(inline, true);
852       return
853         ch >= '0' && ch <= '9' ? ch-'0' :
854         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
855         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
856         -1;
857     }
858     int[8] digs;
859     int dc = -1;
860     foreach (immutable char ch; srgb) {
861       if (ch <= ' ') continue;
862       if (ch == '#') {
863         if (dc != -1) { dc = -1; break; }
864         dc = 0;
865       } else {
866         if (dc >= digs.length) { dc = -1; break; }
867         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
868       }
869     }
870     switch (dc) {
871       case 3: // rgb
872         a = 1.0f;
873         r = digs[0]/15.0f;
874         g = digs[1]/15.0f;
875         b = digs[2]/15.0f;
876         break;
877       case 4: // argb
878         a = digs[0]/15.0f;
879         r = digs[1]/15.0f;
880         g = digs[2]/15.0f;
881         b = digs[3]/15.0f;
882         break;
883       case 6: // rrggbb
884         a = 1.0f;
885         r = (digs[0]*16+digs[1])/255.0f;
886         g = (digs[2]*16+digs[3])/255.0f;
887         b = (digs[4]*16+digs[5])/255.0f;
888         break;
889       case 8: // aarrggbb
890         a = (digs[0]*16+digs[1])/255.0f;
891         r = (digs[2]*16+digs[3])/255.0f;
892         g = (digs[4]*16+digs[5])/255.0f;
893         b = (digs[6]*16+digs[7])/255.0f;
894         break;
895       default:
896         break;
897     }
898   }
899 
900   /// Is this color completely opaque?
901   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
902   /// Is this color completely transparent?
903   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
904 
905   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
906   @property uint asUint () const pure {
907     pragma(inline, true);
908     return
909       cast(uint)(r*255)|
910       (cast(uint)(g*255)<<8)|
911       (cast(uint)(b*255)<<16)|
912       (cast(uint)(a*255)<<24);
913   }
914 
915   alias asUintABGR = asUint; /// Ditto.
916 
917   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
918   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
919 
920   alias fromUintABGR = fromUint; /// Ditto.
921 
922   /// AARRGGBB
923   @property uint asUintARGB () const pure {
924     pragma(inline, true);
925     return
926       cast(uint)(b*255)|
927       (cast(uint)(g*255)<<8)|
928       (cast(uint)(r*255)<<16)|
929       (cast(uint)(a*255)<<24);
930   }
931 
932   /// AARRGGBB
933   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
934 
935   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
936   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
937   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
938   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
939 
940   ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc {
941     if (tint.a == 0) return this;
942     foreach (immutable idx, ref float v; rgba[0..4]) {
943       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
944     }
945     return this;
946   }
947 
948   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
949   static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
950 
951   static if (NanoVegaHasArsdColor) {
952     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
953     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
954     ///
955     this (in Color c) {
956       version(aliced) pragma(inline, true);
957       r = c.r/255.0f;
958       g = c.g/255.0f;
959       b = c.b/255.0f;
960       a = c.a/255.0f;
961     }
962   }
963 }
964 
965 
966 /// NanoVega A-HSL color
967 /// Group: color_utils
968 public align(1) struct NVGHSL {
969 align(1):
970   float h=0, s=0, l=1, a=1; ///
971 
972   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)); }
973 
974 nothrow @safe @nogc:
975 public:
976   ///
977   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
978 
979   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
980 
981   // taken from Adam's arsd.color
982   /** Converts an RGB color into an HSL triplet.
983    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
984    * which is more sensitive to green than red and more to red than blue.
985    * If it is false, it just does average of the rgb. */
986   static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure {
987     NVGHSL res;
988     res.a = c.a;
989     float r1 = c.r;
990     float g1 = c.g;
991     float b1 = c.b;
992 
993     float maxColor = r1;
994     if (g1 > maxColor) maxColor = g1;
995     if (b1 > maxColor) maxColor = b1;
996     float minColor = r1;
997     if (g1 < minColor) minColor = g1;
998     if (b1 < minColor) minColor = b1;
999 
1000     res.l = (maxColor+minColor)/2;
1001     if (useWeightedLightness) {
1002       // the colors don't affect the eye equally
1003       // this is a little more accurate than plain HSL numbers
1004       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
1005     }
1006     if (maxColor != minColor) {
1007       if (res.l < 0.5) {
1008         res.s = (maxColor-minColor)/(maxColor+minColor);
1009       } else {
1010         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
1011       }
1012       if (r1 == maxColor) {
1013         res.h = (g1-b1)/(maxColor-minColor);
1014       } else if(g1 == maxColor) {
1015         res.h = 2.0+(b1-r1)/(maxColor-minColor);
1016       } else {
1017         res.h = 4.0+(r1-g1)/(maxColor-minColor);
1018       }
1019     }
1020 
1021     res.h = res.h*60;
1022     if (res.h < 0) res.h += 360;
1023     res.h /= 360;
1024 
1025     return res;
1026   }
1027 }
1028 
1029 
1030 //version = nanovega_debug_image_manager;
1031 //version = nanovega_debug_image_manager_rc;
1032 
1033 /** NanoVega image handle.
1034  *
1035  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1036  *
1037  * Group: images
1038  */
1039 struct NVGImage {
1040 	enum isOpaqueStruct = true;
1041 private:
1042   NVGContext ctx;
1043   int id; // backend image id
1044 
1045 public:
1046   ///
1047   this() (in auto ref NVGImage src) nothrow @trusted @nogc {
1048     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); }
1049     if (src.id > 0 && src.ctx !is null) {
1050       ctx = cast(NVGContext)src.ctx;
1051       id = src.id;
1052       ctx.nvg__imageIncRef(id);
1053     }
1054   }
1055 
1056   ///
1057   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1058 
1059   ///
1060   this (this) nothrow @trusted @nogc {
1061     version(aliced) pragma(inline, true);
1062     if (id > 0 && ctx !is null) {
1063       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1064       ctx.nvg__imageIncRef(id);
1065     }
1066   }
1067 
1068   ///
1069   void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc {
1070     if (src.id <= 0 || src.ctx is null) {
1071       clear();
1072     } else {
1073       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); }
1074       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1075       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1076       ctx = cast(NVGContext)src.ctx;
1077       id = src.id;
1078     }
1079   }
1080 
1081   /// Free this image.
1082   void clear () nothrow @trusted @nogc {
1083     if (id > 0 && ctx !is null) {
1084       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1085       ctx.nvg__imageDecRef(id);
1086     }
1087     id = 0;
1088     ctx = null;
1089   }
1090 
1091   /// Is this image valid?
1092   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1093 
1094   /// Is the given image valid and comes from the same context?
1095   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1096 
1097   /// Returns image width, or zero for invalid image.
1098   int width () const nothrow @trusted @nogc {
1099     int w = 0;
1100     if (valid) {
1101       int h = void;
1102       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1103     }
1104     return w;
1105   }
1106 
1107   /// Returns image height, or zero for invalid image.
1108   int height () const nothrow @trusted @nogc {
1109     int h = 0;
1110     if (valid) {
1111       int w = void;
1112       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1113     }
1114     return h;
1115   }
1116 }
1117 
1118 
1119 /// Paint parameters for various fills. Don't change anything here!
1120 /// Group: render_styles
1121 public struct NVGPaint {
1122   enum isOpaqueStruct = true;
1123 
1124   NVGMatrix xform;
1125   float[2] extent = 0.0f;
1126   float radius = 0.0f;
1127   float feather = 0.0f;
1128   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1129   NVGColor middleColor;
1130   NVGColor outerColor;
1131   float midp = -1; // middle stop for 3-color gradient
1132   NVGImage image;
1133   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1134 
1135   this() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1136     xform = p.xform;
1137     extent[] = p.extent[];
1138     radius = p.radius;
1139     feather = p.feather;
1140     innerColor = p.innerColor;
1141     middleColor = p.middleColor;
1142     midp = p.midp;
1143     outerColor = p.outerColor;
1144     image = p.image;
1145     simpleColor = p.simpleColor;
1146   }
1147 
1148   void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1149     xform = p.xform;
1150     extent[] = p.extent[];
1151     radius = p.radius;
1152     feather = p.feather;
1153     innerColor = p.innerColor;
1154     middleColor = p.middleColor;
1155     midp = p.midp;
1156     outerColor = p.outerColor;
1157     image = p.image;
1158     simpleColor = p.simpleColor;
1159   }
1160 
1161   void clear () nothrow @trusted @nogc {
1162     version(aliced) pragma(inline, true);
1163     import core.stdc.string : memset;
1164     image.clear();
1165     memset(&this, 0, this.sizeof);
1166     simpleColor = true;
1167   }
1168 }
1169 
1170 /// Path winding.
1171 /// Group: paths
1172 public enum NVGWinding {
1173   CCW = 1, /// Winding for solid shapes
1174   CW = 2,  /// Winding for holes
1175 }
1176 
1177 /// Path solidity.
1178 /// Group: paths
1179 public enum NVGSolidity {
1180   Solid = 1, /// Solid shape (CCW winding).
1181   Hole = 2, /// Hole (CW winding).
1182 }
1183 
1184 /// Line cap style.
1185 /// Group: render_styles
1186 public enum NVGLineCap {
1187   Butt, ///
1188   Round, ///
1189   Square, ///
1190   Bevel, ///
1191   Miter, ///
1192 }
1193 
1194 /// Text align.
1195 /// Group: text_api
1196 public align(1) struct NVGTextAlign {
1197 align(1):
1198   /// Horizontal align.
1199   enum H : ubyte {
1200     Left   = 0, /// Default, align text horizontally to left.
1201     Center = 1, /// Align text horizontally to center.
1202     Right  = 2, /// Align text horizontally to right.
1203   }
1204 
1205   /// Vertical align.
1206   enum V : ubyte {
1207     Baseline = 0, /// Default, align text vertically to baseline.
1208     Top      = 1, /// Align text vertically to top.
1209     Middle   = 2, /// Align text vertically to middle.
1210     Bottom   = 3, /// Align text vertically to bottom.
1211   }
1212 
1213 pure nothrow @safe @nogc:
1214 public:
1215   this (H h) { pragma(inline, true); value = h; } ///
1216   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1217   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1218   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1219   void reset () { pragma(inline, true); value = 0; } ///
1220   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1221   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1222 @property:
1223   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1224   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1225   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1226   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1227   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1228   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1229   //
1230   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1231   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1232   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1233   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1234   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1235   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1236   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1237   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1238   //
1239   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1240   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1241   //
1242   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1243   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1244   //
1245 private:
1246   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1247 }
1248 
1249 /// Blending type.
1250 /// Group: composite_operation
1251 public enum NVGBlendFactor {
1252   Zero = 1<<0, ///
1253   One = 1<<1, ///
1254   SrcColor = 1<<2, ///
1255   OneMinusSrcColor = 1<<3, ///
1256   DstColor = 1<<4, ///
1257   OneMinusDstColor = 1<<5, ///
1258   SrcAlpha = 1<<6, ///
1259   OneMinusSrcAlpha = 1<<7, ///
1260   DstAlpha = 1<<8, ///
1261   OneMinusDstAlpha = 1<<9, ///
1262   SrcAlphaSaturate = 1<<10, ///
1263 }
1264 
1265 /// Composite operation (HTML5-alike).
1266 /// Group: composite_operation
1267 public enum NVGCompositeOperation {
1268   SourceOver, ///
1269   SourceIn, ///
1270   SourceOut, ///
1271   SourceAtop, ///
1272   DestinationOver, ///
1273   DestinationIn, ///
1274   DestinationOut, ///
1275   DestinationAtop, ///
1276   Lighter, ///
1277   Copy, ///
1278   Xor, ///
1279 }
1280 
1281 /// Composite operation state.
1282 /// Group: composite_operation
1283 public struct NVGCompositeOperationState {
1284   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1285   NVGBlendFactor srcRGB; ///
1286   NVGBlendFactor dstRGB; ///
1287   NVGBlendFactor srcAlpha; ///
1288   NVGBlendFactor dstAlpha; ///
1289 }
1290 
1291 /// Mask combining more
1292 /// Group: clipping
1293 public enum NVGClipMode {
1294   None, /// normal rendering (i.e. render path instead of modifying clip region)
1295   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1296   Or, /// new mask will be added to the current one (logical `OR` operation);
1297   Xor, /// new mask will be logically `XOR`ed with the current one
1298   Sub, /// "subtract" current path from mask
1299   Replace, /// replace current mask
1300   Add = Or, /// Synonym
1301 }
1302 
1303 /// Glyph position info.
1304 /// Group: text_api
1305 public struct NVGGlyphPosition {
1306   usize strpos;     /// Position of the glyph in the input string.
1307   float x;          /// The x-coordinate of the logical glyph position.
1308   float minx, maxx; /// The bounds of the glyph shape.
1309 }
1310 
1311 /// Text row storage.
1312 /// Group: text_api
1313 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1314   alias CharType = CT;
1315   const(CT)[] s;
1316   int start;        /// Index in the input text where the row starts.
1317   int end;          /// Index in the input text where the row ends (one past the last character).
1318   float width;      /// Logical width of the row.
1319   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1320   /// Get rest of the string.
1321   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1322   /// Get current row.
1323   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1324   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1325   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1326 }
1327 
1328 /// Image creation flags.
1329 /// Group: images
1330 public enum NVGImageFlag : uint {
1331   None            =    0, /// Nothing special.
1332   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1333   RepeatX         = 1<<1, /// Repeat image in X direction.
1334   RepeatY         = 1<<2, /// Repeat image in Y direction.
1335   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1336   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1337   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1338   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1339   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1340   Nearest = NoFiltering,  /// compatibility with original NanoVG
1341   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1342 }
1343 
1344 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1345 
1346 
1347 // ////////////////////////////////////////////////////////////////////////// //
1348 private:
1349 
1350 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1351   import core.stdc.stdlib : malloc;
1352   import core.stdc.string : memcpy;
1353   if (count == 0) return null;
1354   T* res = cast(T*)malloc(T.sizeof*count);
1355   if (res is null) assert(0, "NanoVega: out of memory");
1356   memcpy(res, ptr, T.sizeof*count);
1357   return res;
1358 }
1359 
1360 // Internal Render API
1361 enum NVGtexture {
1362   Alpha = 0x01,
1363   RGBA  = 0x02,
1364 }
1365 
1366 struct NVGscissor {
1367   NVGMatrix xform;
1368   float[2] extent = -1.0f;
1369 }
1370 
1371 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1372 public struct NVGVertex {
1373   float x, y, u, v;
1374 }
1375 
1376 struct NVGpath {
1377   int first;
1378   int count;
1379   bool closed;
1380   int nbevel;
1381   NVGVertex* fill;
1382   int nfill;
1383   NVGVertex* stroke;
1384   int nstroke;
1385   NVGWinding mWinding;
1386   bool convex;
1387   bool cloned;
1388 
1389   @disable this (this); // no copies
1390   void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); }
1391 
1392   void clear () nothrow @trusted @nogc {
1393     import core.stdc.stdlib : free;
1394     import core.stdc.string : memset;
1395     if (cloned) {
1396       if (stroke !is null && stroke !is fill) free(stroke);
1397       if (fill !is null) free(fill);
1398     }
1399     memset(&this, 0, this.sizeof);
1400   }
1401 
1402   // won't clear current path
1403   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1404     import core.stdc.string : memcpy;
1405     assert(src !is null);
1406     memcpy(&this, src, NVGpath.sizeof);
1407     this.fill = xdup(src.fill, src.nfill);
1408     if (src.stroke is src.fill) {
1409       this.stroke = this.fill;
1410     } else {
1411       this.stroke = xdup(src.stroke, src.nstroke);
1412     }
1413     this.cloned = true;
1414   }
1415 
1416   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1417     pragma(inline, true);
1418     return (nfill > 0 ? fill[0..nfill] : null);
1419   }
1420 
1421   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1422     pragma(inline, true);
1423     return (nstroke > 0 ? stroke[0..nstroke] : null);
1424   }
1425 
1426   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1427   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1428 }
1429 
1430 
1431 struct NVGparams {
1432   void* userPtr;
1433   bool edgeAntiAlias;
1434   bool fontAA;
1435   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1436   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1437   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1438   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
1439   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1440   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1441   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1442   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1443   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1444   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1445   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1446   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1447   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;
1448   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;
1449   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1450   void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1451   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1452 }
1453 
1454 // ////////////////////////////////////////////////////////////////////////// //
1455 private:
1456 
1457 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1458 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1459 enum NVG_MAX_FONTIMAGES      = 4;
1460 
1461 enum NVG_INIT_COMMANDS_SIZE = 256;
1462 enum NVG_INIT_POINTS_SIZE   = 128;
1463 enum NVG_INIT_PATHS_SIZE    = 16;
1464 enum NVG_INIT_VERTS_SIZE    = 256;
1465 enum NVG_MAX_STATES         = 32;
1466 
1467 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1468 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1469 
1470 enum Command {
1471   MoveTo = 0,
1472   LineTo = 1,
1473   BezierTo = 2,
1474   Close = 3,
1475   Winding = 4,
1476 }
1477 
1478 enum PointFlag : int {
1479   Corner = 0x01,
1480   Left = 0x02,
1481   Bevel = 0x04,
1482   InnerBevelPR = 0x08,
1483 }
1484 
1485 struct NVGstate {
1486   NVGCompositeOperationState compositeOperation;
1487   bool shapeAntiAlias = true;
1488   NVGPaint fill;
1489   NVGPaint stroke;
1490   float strokeWidth = 1.0f;
1491   float miterLimit = 10.0f;
1492   NVGLineCap lineJoin = NVGLineCap.Miter;
1493   NVGLineCap lineCap = NVGLineCap.Butt;
1494   float alpha = 1.0f;
1495   NVGMatrix xform;
1496   NVGscissor scissor;
1497   float fontSize = 16.0f;
1498   float letterSpacing = 0.0f;
1499   float lineHeight = 1.0f;
1500   float fontBlur = 0.0f;
1501   NVGTextAlign textAlign;
1502   int fontId = 0;
1503   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1504   // dashing
1505   enum MaxDashes = 32; // max 16 dashes
1506   float[MaxDashes] dashes;
1507   uint dashCount = 0;
1508   uint lastFlattenDashCount = 0;
1509   float dashStart = 0;
1510   float totalDashLen;
1511   bool firstDashIsGap = false;
1512   // dasher state for flattener
1513   bool dasherActive = false;
1514 
1515   void clearPaint () nothrow @trusted @nogc {
1516     fill.clear();
1517     stroke.clear();
1518   }
1519 }
1520 
1521 struct NVGpoint {
1522   float x, y;
1523   float dx, dy;
1524   float len;
1525   float dmx, dmy;
1526   ubyte flags;
1527 }
1528 
1529 struct NVGpathCache {
1530   NVGpoint* points;
1531   int npoints;
1532   int cpoints;
1533   NVGpath* paths;
1534   int npaths;
1535   int cpaths;
1536   NVGVertex* verts;
1537   int nverts;
1538   int cverts;
1539   float[4] bounds;
1540   // this is required for saved paths
1541   bool strokeReady;
1542   bool fillReady;
1543   float strokeAlphaMul;
1544   float strokeWidth;
1545   float fringeWidth;
1546   bool evenOddMode;
1547   NVGClipMode clipmode;
1548   // non-saved path will not have this
1549   float* commands;
1550   int ncommands;
1551 
1552   @disable this (this); // no copies
1553   void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1554 
1555   // won't clear current path
1556   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1557     import core.stdc.stdlib : malloc;
1558     import core.stdc.string : memcpy, memset;
1559     assert(src !is null);
1560     memcpy(&this, src, NVGpathCache.sizeof);
1561     this.points = xdup(src.points, src.npoints);
1562     this.cpoints = src.npoints;
1563     this.verts = xdup(src.verts, src.nverts);
1564     this.cverts = src.nverts;
1565     this.commands = xdup(src.commands, src.ncommands);
1566     if (src.npaths > 0) {
1567       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1568       memset(this.paths, 0, npaths*NVGpath.sizeof);
1569       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1570       this.cpaths = src.npaths;
1571     } else {
1572       this.npaths = this.cpaths = 0;
1573     }
1574   }
1575 
1576   void clear () nothrow @trusted @nogc {
1577     import core.stdc.stdlib : free;
1578     import core.stdc.string : memset;
1579     if (paths !is null) {
1580       foreach (ref p; paths[0..npaths]) p.clear();
1581       free(paths);
1582     }
1583     if (points !is null) free(points);
1584     if (verts !is null) free(verts);
1585     if (commands !is null) free(commands);
1586     memset(&this, 0, this.sizeof);
1587   }
1588 }
1589 
1590 /// Pointer to opaque NanoVega context structure.
1591 /// Group: context_management
1592 public alias NVGContext = NVGcontextinternal*;
1593 
1594 /// FontStash context
1595 /// Group: font_stash
1596 public alias FONSContext = FONScontextInternal*;
1597 
1598 /// Returns FontStash context of the given NanoVega context.
1599 /// Group: font_stash
1600 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1601 
1602 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1603 /// Group: font_stash
1604 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1605   pragma(inline, true);
1606   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1607 }
1608 
1609 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1610 /// Returns `false` if FontStash or NanoVega context is not active.
1611 /// Group: font_stash
1612 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1613   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1614   NVGstate* state = nvg__getState(ctx);
1615   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1616   stash.size = state.fontSize*scale;
1617   stash.spacing = state.letterSpacing*scale;
1618   stash.blur = state.fontBlur*scale;
1619   stash.textAlign = state.textAlign;
1620   stash.fontId = state.fontId;
1621   return true;
1622 }
1623 
1624 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1625 /// Returns `false` if FontStash or NanoVega context is not active.
1626 /// Group: font_stash
1627 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1628   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1629   NVGstate* state = nvg__getState(ctx);
1630   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1631   state.fontSize = stash.size;
1632   state.letterSpacing = stash.spacing;
1633   state.fontBlur = stash.blur;
1634   state.textAlign = stash.textAlign;
1635   state.fontId = stash.fontId;
1636   return true;
1637 }
1638 
1639 /** Bezier curve rasterizer.
1640  *
1641  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1642  * It doesn't really matter in practice.
1643  *
1644  * AFD tesselator is somewhat slower, but does cusps better.
1645  *
1646  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1647  * never notice any visial difference (and this code is not really debugged), so you probably should
1648  * not use it. It is there for further experiments.
1649  */
1650 public enum NVGTesselation {
1651   DeCasteljau, /// default: standard well-known tesselation algorithm
1652   AFD, /// adaptive forward differencing
1653   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1654 }
1655 
1656 /// Default tesselator for Bezier curves.
1657 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1658 
1659 
1660 // some public info
1661 
1662 /// valid only inside [beginFrame]/[endFrame]
1663 /// Group: context_management
1664 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1665 
1666 /// valid only inside [beginFrame]/[endFrame]
1667 /// Group: context_management
1668 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1669 
1670 /// valid only inside [beginFrame]/[endFrame]
1671 /// Group: context_management
1672 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1673 
1674 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1675 /// Group: context_management
1676 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1677 
1678 // path autoregistration
1679 
1680 /// [pickid] to stop autoregistration.
1681 /// Group: context_management
1682 public enum NVGNoPick = -1;
1683 
1684 /// >=0: this pickid will be assigned to all filled/stroked paths
1685 /// Group: context_management
1686 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1687 
1688 /// >=0: this pickid will be assigned to all filled/stroked paths
1689 /// Group: context_management
1690 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1691 
1692 /// pick autoregistration mode; see [NVGPickKind]
1693 /// Group: context_management
1694 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1695 
1696 /// pick autoregistration mode; see [NVGPickKind]
1697 /// Group: context_management
1698 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); }
1699 
1700 // tesselator options
1701 
1702 /// Get current Bezier tesselation mode. See [NVGTesselation].
1703 /// Group: context_management
1704 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1705 
1706 /// Set current Bezier tesselation mode. See [NVGTesselation].
1707 /// Group: context_management
1708 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1709 
1710 
1711 private struct NVGcontextinternal {
1712 private:
1713   NVGparams params;
1714   float* commands;
1715   int ccommands;
1716   int ncommands;
1717   float commandx, commandy;
1718   NVGstate[NVG_MAX_STATES] states;
1719   int nstates;
1720   NVGpathCache* cache;
1721   public float tessTol;
1722   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1723   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1724   float distTol;
1725   public float fringeWidth;
1726   float devicePxRatio;
1727   FONSContext fs;
1728   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1729   int fontImageIdx;
1730   int drawCallCount;
1731   int fillTriCount;
1732   int strokeTriCount;
1733   int textTriCount;
1734   NVGTesselation tesselatortype;
1735   // picking API
1736   NVGpickScene* pickScene;
1737   int pathPickId; // >=0: register all paths for picking using this id
1738   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
1739   // path recording
1740   NVGPathSet recset;
1741   int recstart; // used to cancel recording
1742   bool recblockdraw;
1743   // internals
1744   NVGMatrix gpuAffine;
1745   int mWidth, mHeight;
1746   // image manager
1747   shared int imageCount; // number of alive images in this context
1748   bool contextAlive; // context can be dead, but still contain some images
1749 
1750   @disable this (this); // no copies
1751   void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1752 
1753   // debug feature
1754   public @property int getImageCount () nothrow @trusted @nogc {
1755     import core.atomic;
1756     return atomicLoad(imageCount);
1757   }
1758 }
1759 
1760 /** Returns number of tesselated pathes in context.
1761  *
1762  * Tesselated pathes are either triangle strips (for strokes), or
1763  * triangle fans (for fills). Note that NanoVega can generate some
1764  * surprising pathes (like fringe stroke for antialiasing, for example).
1765  *
1766  * One render path can contain vertices both for fill, and for stroke triangles.
1767  */
1768 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1769   pragma(inline, true);
1770   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1771 }
1772 
1773 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1774  *
1775  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1776  *           (except the calls to render path accessors), and using it in such
1777  *           case is UB. So copy vertices to freshly allocated array if you want
1778  *           to keep them for further processing.)
1779  */
1780 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1781   pragma(inline, true);
1782   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1783 }
1784 
1785 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1786  *
1787  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1788  *           (except the calls to render path accessors), and using it in such
1789  *           case is UB. So copy vertices to freshly allocated array if you want
1790  *           to keep them for further processing.)
1791  */
1792 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1793   pragma(inline, true);
1794   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1795 
1796 }
1797 
1798 /// Returns winding for the given render path.
1799 public NVGWinding renderPathWinding (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].winding : NVGWinding.CCW);
1802 
1803 }
1804 
1805 /// Returns "complex path" flag for the given render path.
1806 public bool renderPathComplex (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].complex : false);
1809 
1810 }
1811 
1812 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1813   if (ctx !is null && imgid > 0) {
1814     import core.atomic : atomicOp;
1815     atomicOp!"+="(ctx.imageCount, 1);
1816     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1817     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1818   }
1819 }
1820 
1821 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1822   if (ctx !is null && imgid > 0) {
1823     import core.atomic : atomicOp;
1824     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1825     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1826     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1827     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1828     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); }
1829     if (!ctx.contextAlive && icnt == 0) {
1830       // it is finally safe to free context memory
1831       import core.stdc.stdlib : free;
1832       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1833       free(ctx);
1834     }
1835   }
1836 }
1837 
1838 
1839 public import core.stdc.math :
1840   nvg__sqrtf = sqrtf,
1841   nvg__modf = fmodf,
1842   nvg__sinf = sinf,
1843   nvg__cosf = cosf,
1844   nvg__tanf = tanf,
1845   nvg__atan2f = atan2f,
1846   nvg__acosf = acosf,
1847   nvg__ceilf = ceilf;
1848 
1849 version(Windows) {
1850   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1851 } else {
1852   public import core.stdc.math : nvg__lrintf = lrintf;
1853 }
1854 
1855 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1856 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1857 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1858 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1859 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1860 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1861 
1862 //public import core.stdc.math : nvg__absf = fabsf;
1863 public import core.math : nvg__absf = fabs;
1864 
1865 
1866 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1867   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1868   if (d > 1e-6f) {
1869     immutable float id = 1.0f/d;
1870     *x *= id;
1871     *y *= id;
1872   }
1873   return d;
1874 }
1875 
1876 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1877   if (c !is null) {
1878     c.clear();
1879     free(c);
1880   }
1881 }
1882 
1883 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1884   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1885   if (c is null) goto error;
1886   memset(c, 0, NVGpathCache.sizeof);
1887 
1888   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1889   if (c.points is null) goto error;
1890   assert(c.npoints == 0);
1891   c.cpoints = NVG_INIT_POINTS_SIZE;
1892 
1893   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1894   if (c.paths is null) goto error;
1895   assert(c.npaths == 0);
1896   c.cpaths = NVG_INIT_PATHS_SIZE;
1897 
1898   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1899   if (c.verts is null) goto error;
1900   assert(c.nverts == 0);
1901   c.cverts = NVG_INIT_VERTS_SIZE;
1902 
1903   return c;
1904 
1905 error:
1906   nvg__deletePathCache(c);
1907   return null;
1908 }
1909 
1910 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1911   ctx.tessTol = 0.25f/ratio;
1912   ctx.distTol = 0.01f/ratio;
1913   ctx.fringeWidth = 1.0f/ratio;
1914   ctx.devicePxRatio = ratio;
1915 }
1916 
1917 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1918   NVGCompositeOperationState state;
1919   NVGBlendFactor sfactor, dfactor;
1920 
1921        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1922   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1923   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1924   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1925   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1926   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1927   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1928   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1929   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1930   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1931   else if (op == NVGCompositeOperation.Xor) {
1932     state.simple = false;
1933     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1934     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1935     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1936     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1937     return state;
1938   }
1939   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1940 
1941   state.simple = true;
1942   state.srcAlpha = sfactor;
1943   state.dstAlpha = dfactor;
1944   return state;
1945 }
1946 
1947 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1948   pragma(inline, true);
1949   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1950   return &ctx.states.ptr[ctx.nstates-1];
1951 }
1952 
1953 // Constructor called by the render back-end.
1954 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1955   FONSParams fontParams;
1956   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1957   if (ctx is null) goto error;
1958   memset(ctx, 0, NVGcontextinternal.sizeof);
1959 
1960   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1961   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1962 
1963   ctx.contextAlive = true;
1964 
1965   ctx.params = *params;
1966   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1967 
1968   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1969   if (ctx.commands is null) goto error;
1970   ctx.ncommands = 0;
1971   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1972 
1973   ctx.cache = nvg__allocPathCache();
1974   if (ctx.cache is null) goto error;
1975 
1976   ctx.save();
1977   ctx.reset();
1978 
1979   nvg__setDevicePixelRatio(ctx, 1.0f);
1980   ctx.mWidth = ctx.mHeight = 0;
1981 
1982   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1983 
1984   // init font rendering
1985   memset(&fontParams, 0, fontParams.sizeof);
1986   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1987   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1988   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1989   fontParams.renderCreate = null;
1990   fontParams.renderUpdate = null;
1991   fontParams.renderDelete = null;
1992   fontParams.userPtr = null;
1993   ctx.fs = FONSContext.create(fontParams);
1994   if (ctx.fs is null) goto error;
1995 
1996   // create font texture
1997   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
1998   if (ctx.fontImages[0].id == 0) goto error;
1999   ctx.fontImages[0].ctx = ctx;
2000   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
2001   ctx.fontImageIdx = 0;
2002 
2003   ctx.pathPickId = -1;
2004   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
2005 
2006   return ctx;
2007 
2008 error:
2009   ctx.deleteInternal();
2010   return null;
2011 }
2012 
2013 // Called by render backend.
2014 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
2015   return &ctx.params;
2016 }
2017 
2018 // Destructor called by the render back-end.
2019 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
2020   if (ctx is null) return;
2021   if (ctx.contextAlive) {
2022     if (ctx.commands !is null) free(ctx.commands);
2023     nvg__deletePathCache(ctx.cache);
2024 
2025     if (ctx.fs) ctx.fs.kill();
2026 
2027     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
2028 
2029     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
2030 
2031     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
2032 
2033     ctx.contextAlive = false;
2034 
2035     import core.atomic : atomicLoad;
2036     if (atomicLoad(ctx.imageCount) == 0) {
2037       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2038       free(ctx);
2039     } else {
2040       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2041     }
2042   }
2043 }
2044 
2045 /// Delete NanoVega context.
2046 /// Group: context_management
2047 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2048   if (ctx !is null) {
2049     ctx.deleteInternal();
2050     ctx = null;
2051   }
2052 }
2053 
2054 /// Returns `true` if the given context is not `null` and can be used for painting.
2055 /// Group: context_management
2056 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2057 
2058 
2059 // ////////////////////////////////////////////////////////////////////////// //
2060 // Frame Management
2061 
2062 /** Begin drawing a new frame.
2063  *
2064  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2065  *
2066  * [beginFrame] defines the size of the window to render to in relation currently
2067  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2068  * control the rendering on Hi-DPI devices.
2069  *
2070  * For example, GLFW returns two dimension for an opened window: window size and
2071  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2072  * devicePixelRatio to: `windowWidth/windowHeight`.
2073  *
2074  * Default ratio is `1`.
2075  *
2076  * Note that fractional ratio can (and will) distort your fonts and images.
2077  *
2078  * This call also resets pick marks (see picking API for non-rasterized paths),
2079  * path recording, and GPU affine transformatin matrix.
2080  *
2081  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2082  *
2083  * Group: frame_management
2084  */
2085 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2086   import std.math : isNaN;
2087   /*
2088   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2089          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2090          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2091   */
2092   if (ctx.nstates > 0) ctx.cancelFrame();
2093 
2094   if (windowWidth < 1) windowWidth = 1;
2095   if (windowHeight < 1) windowHeight = 1;
2096 
2097   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2098 
2099   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2100   ctx.nstates = 0;
2101   ctx.save();
2102   ctx.reset();
2103 
2104   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2105 
2106   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2107   ctx.mWidth = windowWidth;
2108   ctx.mHeight = windowHeight;
2109 
2110   ctx.recset = null;
2111   ctx.recstart = -1;
2112 
2113   ctx.pathPickId = NVGNoPick;
2114   ctx.pathPickRegistered = 0;
2115 
2116   ctx.drawCallCount = 0;
2117   ctx.fillTriCount = 0;
2118   ctx.strokeTriCount = 0;
2119   ctx.textTriCount = 0;
2120 
2121   ctx.ncommands = 0;
2122   ctx.pathPickRegistered = 0;
2123   nvg__clearPathCache(ctx);
2124 
2125   ctx.gpuAffine = NVGMatrix.Identity;
2126 
2127   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2128 }
2129 
2130 /// Cancels drawing the current frame. Cancels path recording.
2131 /// Group: frame_management
2132 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2133   ctx.cancelRecording();
2134   //ctx.mWidth = 0;
2135   //ctx.mHeight = 0;
2136   // cancel render queue
2137   ctx.params.renderCancel(ctx.params.userPtr);
2138   // clear saved states (this may free some textures)
2139   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2140   ctx.nstates = 0;
2141 }
2142 
2143 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2144 /// Group: frame_management
2145 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2146   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2147   ctx.stopRecording();
2148   //ctx.mWidth = 0;
2149   //ctx.mHeight = 0;
2150   // flush render queue
2151   NVGstate* state = nvg__getState(ctx);
2152   ctx.params.renderFlush(ctx.params.userPtr);
2153   if (ctx.fontImageIdx != 0) {
2154     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2155     int j = 0, iw, ih;
2156     // delete images that smaller than current one
2157     if (!fontImage.valid) return;
2158     ctx.imageSize(fontImage, iw, ih);
2159     foreach (int i; 0..ctx.fontImageIdx) {
2160       if (ctx.fontImages[i].valid) {
2161         int nw, nh;
2162         ctx.imageSize(ctx.fontImages[i], nw, nh);
2163         if (nw < iw || nh < ih) {
2164           ctx.deleteImage(ctx.fontImages[i]);
2165         } else {
2166           ctx.fontImages[j++] = ctx.fontImages[i];
2167         }
2168       }
2169     }
2170     // make current font image to first
2171     ctx.fontImages[j++] = ctx.fontImages[0];
2172     ctx.fontImages[0] = fontImage;
2173     ctx.fontImageIdx = 0;
2174     // clear all images after j
2175     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2176   }
2177   // clear saved states (this may free some textures)
2178   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2179   ctx.nstates = 0;
2180 }
2181 
2182 
2183 // ////////////////////////////////////////////////////////////////////////// //
2184 // Recording and Replaying Pathes
2185 
2186 // Saved path set.
2187 // Group: path_recording
2188 public alias NVGPathSet = NVGPathSetS*;
2189 
2190 
2191 //TODO: save scissor info?
2192 struct NVGPathSetS {
2193 private:
2194   // either path cache, or text item
2195   static struct Node {
2196     NVGPaint paint;
2197     NVGpathCache* path;
2198   }
2199 
2200 private:
2201   Node* nodes;
2202   int nnodes, cnodes;
2203   NVGpickScene* pickscene;
2204   //int npickscenes, cpickscenes;
2205   NVGContext svctx; // used to do some sanity checks, and to free resources
2206 
2207 private:
2208   Node* allocNode () nothrow @trusted @nogc {
2209     import core.stdc.string : memset;
2210     // grow buffer if necessary
2211     if (nnodes+1 > cnodes) {
2212       import core.stdc.stdlib : realloc;
2213       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2214       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2215       if (nodes is null) assert(0, "NanoVega: out of memory");
2216       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2217       cnodes = newsz;
2218     }
2219     assert(nnodes < cnodes);
2220     memset(nodes+nnodes, 0, Node.sizeof);
2221     return &nodes[nnodes++];
2222   }
2223 
2224   Node* allocPathNode () nothrow @trusted @nogc {
2225     import core.stdc.stdlib : malloc;
2226     import core.stdc.string : memset;
2227     auto node = allocNode();
2228     // allocate path cache
2229     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2230     if (pc is null) assert(0, "NanoVega: out of memory");
2231     node.path = pc;
2232     return node;
2233   }
2234 
2235   void clearNode (int idx) nothrow @trusted @nogc {
2236     if (idx < 0 || idx >= nnodes) return;
2237     Node* node = &nodes[idx];
2238     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2239     if (node.path !is null) node.path.clear();
2240   }
2241 
2242 private:
2243   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2244     NVGpickScene* ps = ctx.pickScene;
2245     if (ps is null) return; // nothing to do
2246     if (ps.npaths == 0) return; // pick scene is empty
2247     ctx.pickScene = null;
2248     pickscene = ps;
2249   }
2250 
2251   void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc {
2252     NVGstate* state = nvg__getState(ctx);
2253     foreach (ref node; nodes[0..nnodes]) {
2254       if (auto cc = node.path) {
2255         if (cc.npaths <= 0) continue;
2256 
2257         if (cc.fillReady) {
2258           NVGPaint fillPaint = node.paint;
2259 
2260           // apply global alpha
2261           fillPaint.innerColor.a *= state.alpha;
2262           fillPaint.middleColor.a *= state.alpha;
2263           fillPaint.outerColor.a *= state.alpha;
2264 
2265           fillPaint.innerColor.applyTint(fillTint);
2266           fillPaint.middleColor.applyTint(fillTint);
2267           fillPaint.outerColor.applyTint(fillTint);
2268 
2269           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2270 
2271           // count triangles
2272           foreach (int i; 0..cc.npaths) {
2273             NVGpath* path = &cc.paths[i];
2274             ctx.fillTriCount += path.nfill-2;
2275             ctx.fillTriCount += path.nstroke-2;
2276             ctx.drawCallCount += 2;
2277           }
2278         }
2279 
2280         if (cc.strokeReady) {
2281           NVGPaint strokePaint = node.paint;
2282 
2283           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2284           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2285           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2286 
2287           // apply global alpha
2288           strokePaint.innerColor.a *= state.alpha;
2289           strokePaint.middleColor.a *= state.alpha;
2290           strokePaint.outerColor.a *= state.alpha;
2291 
2292           strokePaint.innerColor.applyTint(strokeTint);
2293           strokePaint.middleColor.applyTint(strokeTint);
2294           strokePaint.outerColor.applyTint(strokeTint);
2295 
2296           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2297 
2298           // count triangles
2299           foreach (int i; 0..cc.npaths) {
2300             NVGpath* path = &cc.paths[i];
2301             ctx.strokeTriCount += path.nstroke-2;
2302             ++ctx.drawCallCount;
2303           }
2304         }
2305       }
2306     }
2307   }
2308 
2309 public:
2310   @disable this (this); // no copies
2311   void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2312 
2313   // pick test
2314   // Call delegate [dg] for each path under the specified position (in no particular order).
2315   // Returns the id of the path for which delegate [dg] returned true or -1.
2316   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2317   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2318     if (pickscene is null) return -1;
2319 
2320     NVGpickScene* ps = pickscene;
2321     int levelwidth = 1<<(ps.nlevels-1);
2322     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2323     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2324     int npicked = 0;
2325 
2326     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2327       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2328       while (pp !is null) {
2329         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2330           int hit = 0;
2331           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2332           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2333           if (hit) {
2334             static if (IsGoodHitTestDG!DG) {
2335               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2336                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2337               } else {
2338                 dg(pp.id, cast(int)pp.order);
2339               }
2340             } else {
2341               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2342                 if (dg(pp)) return pp.id;
2343               } else {
2344                 dg(pp);
2345               }
2346             }
2347           }
2348         }
2349         pp = pp.next;
2350       }
2351       cellx >>= 1;
2352       celly >>= 1;
2353       levelwidth >>= 1;
2354     }
2355 
2356     return -1;
2357   }
2358 
2359   // Fills ids with a list of the top most hit ids under the specified position.
2360   // Returns the slice of [ids].
2361   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2362     if (pickscene is null || ids.length == 0) return ids[0..0];
2363 
2364     int npicked = 0;
2365     NVGpickScene* ps = pickscene;
2366 
2367     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2368       if (npicked == ps.cpicked) {
2369         int cpicked = ps.cpicked+ps.cpicked;
2370         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2371         if (picked is null) return true; // abort
2372         ps.cpicked = cpicked;
2373         ps.picked = picked;
2374       }
2375       ps.picked[npicked] = pp;
2376       ++npicked;
2377       return false; // go on
2378     });
2379 
2380     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2381 
2382     assert(npicked >= 0);
2383     if (npicked > ids.length) npicked = cast(int)ids.length;
2384     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2385 
2386     return ids[0..npicked];
2387   }
2388 
2389   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2390   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2391     if (pickscene is null) return -1;
2392 
2393     int bestOrder = -1;
2394     int bestID = -1;
2395 
2396     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2397       if (pp.order > bestOrder) {
2398         bestOrder = pp.order;
2399         bestID = pp.id;
2400       }
2401     });
2402 
2403     return bestID;
2404   }
2405 }
2406 
2407 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2408 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc {
2409   if (ctx is null || svp is null) return;
2410   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2411   if (ctx.ncommands == 0) {
2412     assert(ctx.cache.npaths == 0);
2413     return;
2414   }
2415   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2416 
2417   // tesselate current path
2418   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2419   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2420 
2421   auto node = svp.allocPathNode();
2422   NVGpathCache* cc = node.path;
2423   cc.copyFrom(ctx.cache);
2424   node.paint = paint;
2425   // copy path commands (we may need 'em for picking)
2426   version(all) {
2427     cc.ncommands = ctx.ncommands;
2428     if (cc.ncommands) {
2429       import core.stdc.stdlib : malloc;
2430       import core.stdc.string : memcpy;
2431       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2432       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2433       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2434     } else {
2435       cc.commands = null;
2436     }
2437   }
2438 }
2439 
2440 // Create new empty path set.
2441 // Group: path_recording
2442 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2443   import core.stdc.stdlib : malloc;
2444   import core.stdc.string : memset;
2445   if (ctx is null) return null;
2446   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2447   if (res is null) assert(0, "NanoVega: out of memory");
2448   memset(res, 0, NVGPathSetS.sizeof);
2449   res.svctx = ctx;
2450   return res;
2451 }
2452 
2453 // Is the given path set empty? Empty path set can be `null`.
2454 // Group: path_recording
2455 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2456 
2457 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2458 // Group: path_recording
2459 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2460   if (svp !is null) {
2461     import core.stdc.stdlib : free;
2462     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2463     svp.nnodes = 0;
2464   }
2465 }
2466 
2467 // Destroy path set (frees all allocated memory).
2468 // Group: path_recording
2469 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2470   if (svp !is null) {
2471     import core.stdc.stdlib : free;
2472     svp.clear();
2473     if (svp.nodes !is null) free(svp.nodes);
2474     free(svp);
2475     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2476     svp = null;
2477   }
2478 }
2479 
2480 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2481 // Group: path_recording
2482 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2483   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2484   ctx.stopRecording();
2485   ctx.recset = svp;
2486   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2487   ctx.recblockdraw = false;
2488 }
2489 
2490 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2491  *
2492  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2493  * Commiting or cancelling will re-enable rendering.
2494  * You can call this with `null` svp to block rendering without recording any paths.
2495  *
2496  * Group: path_recording
2497  */
2498 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2499   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2500   ctx.stopRecording();
2501   ctx.recset = svp;
2502   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2503   ctx.recblockdraw = true;
2504 }
2505 
2506 // Commit recorded paths. It is safe to call this when recording is not started.
2507 // Group: path_recording
2508 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2509   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2510   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2511   ctx.recset = null;
2512   ctx.recstart = -1;
2513   ctx.recblockdraw = false;
2514 }
2515 
2516 // Cancel path recording.
2517 // Group: path_recording
2518 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2519   if (ctx.recset !is null) {
2520     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2521     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2522     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2523     ctx.recset.nnodes = ctx.recstart;
2524     ctx.recset = null;
2525     ctx.recstart = -1;
2526   }
2527   ctx.recblockdraw = false;
2528 }
2529 
2530 /* Replay saved path set.
2531  *
2532  * Replaying record while you're recording another one is undefined behavior.
2533  *
2534  * Group: path_recording
2535  */
2536 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2537   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2538   svp.replay(ctx, fillTint, strokeTint);
2539 }
2540 
2541 /// Ditto.
2542 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2543 
2544 /// Ditto.
2545 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2546 
2547 
2548 // ////////////////////////////////////////////////////////////////////////// //
2549 // Composite operation
2550 
2551 /// Sets the composite operation.
2552 /// Group: composite_operation
2553 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2554   NVGstate* state = nvg__getState(ctx);
2555   state.compositeOperation = nvg__compositeOperationState(op);
2556 }
2557 
2558 /// Sets the composite operation with custom pixel arithmetic.
2559 /// Group: composite_operation
2560 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2561   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2562 }
2563 
2564 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2565 /// Group: composite_operation
2566 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2567   NVGCompositeOperationState op;
2568   op.simple = false;
2569   op.srcRGB = srcRGB;
2570   op.dstRGB = dstRGB;
2571   op.srcAlpha = srcAlpha;
2572   op.dstAlpha = dstAlpha;
2573   NVGstate* state = nvg__getState(ctx);
2574   state.compositeOperation = op;
2575 }
2576 
2577 
2578 // ////////////////////////////////////////////////////////////////////////// //
2579 // Color utils
2580 
2581 /// Returns a color value from string form.
2582 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2583 /// Group: color_utils
2584 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2585 
2586 /// Ditto.
2587 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2588 
2589 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2590 /// Group: color_utils
2591 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2592 
2593 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2594 /// Group: color_utils
2595 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2596 
2597 /// Returns a color value from red, green, blue and alpha values.
2598 /// Group: color_utils
2599 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)); }
2600 
2601 /// Returns a color value from red, green, blue and alpha values.
2602 /// Group: color_utils
2603 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); }
2604 
2605 /// Returns new color with transparency (alpha) set to [a].
2606 /// Group: color_utils
2607 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2608   pragma(inline, true);
2609   c.a = a/255.0f;
2610   return c;
2611 }
2612 
2613 /// Ditto.
2614 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2615   pragma(inline, true);
2616   c.a = a;
2617   return c;
2618 }
2619 
2620 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2621 /// Group: color_utils
2622 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2623   NVGColor cint = void;
2624   u = nvg__clamp(u, 0.0f, 1.0f);
2625   float oneminu = 1.0f-u;
2626   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2627   return cint;
2628 }
2629 
2630 /* see below
2631 public NVGColor nvgHSL() (float h, float s, float l) {
2632   //pragma(inline, true); // alas
2633   return nvgHSLA(h, s, l, 255);
2634 }
2635 */
2636 
2637 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2638   if (h < 0) h += 1;
2639   if (h > 1) h -= 1;
2640   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2641   if (h < 3.0f/6.0f) return m2;
2642   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2643   return m1;
2644 }
2645 
2646 /// Returns color value specified by hue, saturation and lightness.
2647 /// HSL values are all in range [0..1], alpha will be set to 255.
2648 /// Group: color_utils
2649 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2650 
2651 /// Returns color value specified by hue, saturation and lightness and alpha.
2652 /// HSL values are all in range [0..1], alpha in range [0..255].
2653 /// Group: color_utils
2654 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2655   pragma(inline, true);
2656   NVGColor col = void;
2657   h = nvg__modf(h, 1.0f);
2658   if (h < 0.0f) h += 1.0f;
2659   s = nvg__clamp(s, 0.0f, 1.0f);
2660   l = nvg__clamp(l, 0.0f, 1.0f);
2661   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2662   immutable float m1 = 2*l-m2;
2663   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2664   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2665   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2666   col.a = a/255.0f;
2667   return col;
2668 }
2669 
2670 /// Returns color value specified by hue, saturation and lightness and alpha.
2671 /// HSL values and alpha are all in range [0..1].
2672 /// Group: color_utils
2673 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2674   // sorry for copypasta, it is for inliner
2675   static if (__VERSION__ >= 2072) pragma(inline, true);
2676   NVGColor col = void;
2677   h = nvg__modf(h, 1.0f);
2678   if (h < 0.0f) h += 1.0f;
2679   s = nvg__clamp(s, 0.0f, 1.0f);
2680   l = nvg__clamp(l, 0.0f, 1.0f);
2681   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2682   immutable m1 = 2*l-m2;
2683   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2684   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2685   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2686   col.a = a;
2687   return col;
2688 }
2689 
2690 
2691 // ////////////////////////////////////////////////////////////////////////// //
2692 // Matrices and Transformations
2693 
2694 /** Matrix class.
2695  *
2696  * Group: matrices
2697  */
2698 public align(1) struct NVGMatrix {
2699 align(1):
2700 private:
2701   static immutable float[6] IdentityMat = [
2702     1.0f, 0.0f,
2703     0.0f, 1.0f,
2704     0.0f, 0.0f,
2705   ];
2706 
2707 public:
2708   /// Matrix values. Initial value is identity matrix.
2709   float[6] mat = [
2710     1.0f, 0.0f,
2711     0.0f, 1.0f,
2712     0.0f, 0.0f,
2713   ];
2714 
2715 public nothrow @trusted @nogc:
2716   /// Create Matrix with the given values.
2717   this (const(float)[] amat...) {
2718     pragma(inline, true);
2719     if (amat.length >= 6) {
2720       mat.ptr[0..6] = amat.ptr[0..6];
2721     } else {
2722       mat.ptr[0..6] = 0;
2723       mat.ptr[0..amat.length] = amat[];
2724     }
2725   }
2726 
2727   /// Can be used to check validity of [inverted] result
2728   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2729 
2730   /// Returns `true` if this matrix is identity matrix.
2731   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2732 
2733   /// Returns new inverse matrix.
2734   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2735   NVGMatrix inverted () const {
2736     NVGMatrix res = this;
2737     res.invert;
2738     return res;
2739   }
2740 
2741   /// Inverts this matrix.
2742   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2743   ref NVGMatrix invert () return {
2744     float[6] inv = void;
2745     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2746     if (det > -1e-6 && det < 1e-6) {
2747       inv[] = float.nan;
2748     } else {
2749       immutable double invdet = 1.0/det;
2750       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2751       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2752       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2753       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2754       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2755       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2756     }
2757     mat.ptr[0..6] = inv.ptr[0..6];
2758     return this;
2759   }
2760 
2761   /// Sets this matrix to identity matrix.
2762   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2763 
2764   /// Translate this matrix.
2765   ref NVGMatrix translate (in float tx, in float ty) return {
2766     version(aliced) pragma(inline, true);
2767     return this.mul(Translated(tx, ty));
2768   }
2769 
2770   /// Scale this matrix.
2771   ref NVGMatrix scale (in float sx, in float sy) return {
2772     version(aliced) pragma(inline, true);
2773     return this.mul(Scaled(sx, sy));
2774   }
2775 
2776   /// Rotate this matrix.
2777   ref NVGMatrix rotate (in float a) return {
2778     version(aliced) pragma(inline, true);
2779     return this.mul(Rotated(a));
2780   }
2781 
2782   /// Skew this matrix by X axis.
2783   ref NVGMatrix skewX (in float a) return {
2784     version(aliced) pragma(inline, true);
2785     return this.mul(SkewedX(a));
2786   }
2787 
2788   /// Skew this matrix by Y axis.
2789   ref NVGMatrix skewY (in float a) return {
2790     version(aliced) pragma(inline, true);
2791     return this.mul(SkewedY(a));
2792   }
2793 
2794   /// Skew this matrix by both axes.
2795   ref NVGMatrix skewY (in float ax, in float ay) return {
2796     version(aliced) pragma(inline, true);
2797     return this.mul(SkewedXY(ax, ay));
2798   }
2799 
2800   /// Transform point with this matrix. `null` destinations are allowed.
2801   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2802   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2803     version(aliced) pragma(inline, true);
2804     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2805     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2806   }
2807 
2808   /// Transform point with this matrix.
2809   void point (ref float x, ref float y) nothrow @trusted @nogc {
2810     version(aliced) pragma(inline, true);
2811     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2812     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2813     x = nx;
2814     y = ny;
2815   }
2816 
2817   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2818   ref NVGMatrix mul() (in auto ref NVGMatrix s) {
2819     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2820     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2821     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2822     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2823     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2824     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2825     mat.ptr[0] = t0;
2826     mat.ptr[2] = t2;
2827     mat.ptr[4] = t4;
2828     return this;
2829   }
2830 
2831   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2832   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2833   /// Group: matrices
2834   ref NVGMatrix premul() (in auto ref NVGMatrix s) {
2835     NVGMatrix s2 = s;
2836     s2.mul(this);
2837     mat[] = s2.mat[];
2838     return this;
2839   }
2840 
2841   /// Multiply this matrix by [s], return result as new matrix.
2842   /// Performs operations in this left-to-right order.
2843   NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const {
2844     version(aliced) pragma(inline, true);
2845     NVGMatrix res = this;
2846     res.mul(s);
2847     return res;
2848   }
2849 
2850   /// Multiply this matrix by [s].
2851   /// Performs operations in this left-to-right order.
2852   ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) {
2853     version(aliced) pragma(inline, true);
2854     return this.mul(s);
2855   }
2856 
2857   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.
2858   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.
2859   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2860   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2861   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2862 
2863   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2864   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2865   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2866   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2867   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2868 
2869   /// Utility function to be used in `setXXX()`.
2870   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2871   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2872     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2873     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2874     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2875     mat.ptr[4] = tx; mat.ptr[5] = ty;
2876     return this;
2877   }
2878 
2879   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2880   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2881     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2882     mat.ptr[0] = cs; mat.ptr[1] = sn;
2883     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2884     mat.ptr[4] = tx; mat.ptr[5] = ty;
2885     return this;
2886   }
2887 
2888   /// Returns new identity matrix.
2889   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2890 
2891   /// Returns new translation matrix.
2892   static NVGMatrix Translated (in float tx, in float ty) {
2893     version(aliced) pragma(inline, true);
2894     NVGMatrix res = void;
2895     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2896     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2897     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2898     return res;
2899   }
2900 
2901   /// Returns new scaling matrix.
2902   static NVGMatrix Scaled (in float sx, in float sy) {
2903     version(aliced) pragma(inline, true);
2904     NVGMatrix res = void;
2905     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2906     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2907     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2908     return res;
2909   }
2910 
2911   /// Returns new rotation matrix. Angle is specified in radians.
2912   static NVGMatrix Rotated (in float a) {
2913     version(aliced) pragma(inline, true);
2914     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2915     NVGMatrix res = void;
2916     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2917     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2918     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2919     return res;
2920   }
2921 
2922   /// Returns new x-skewing matrix. Angle is specified in radians.
2923   static NVGMatrix SkewedX (in float a) {
2924     version(aliced) pragma(inline, true);
2925     NVGMatrix res = void;
2926     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2927     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2928     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2929     return res;
2930   }
2931 
2932   /// Returns new y-skewing matrix. Angle is specified in radians.
2933   static NVGMatrix SkewedY (in float a) {
2934     version(aliced) pragma(inline, true);
2935     NVGMatrix res = void;
2936     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2937     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2938     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2939     return res;
2940   }
2941 
2942   /// Returns new xy-skewing matrix. Angles are specified in radians.
2943   static NVGMatrix SkewedXY (in float ax, in float ay) {
2944     version(aliced) pragma(inline, true);
2945     NVGMatrix res = void;
2946     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2947     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2948     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2949     return res;
2950   }
2951 
2952   /// Utility function to be used in `setXXX()`.
2953   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2954   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2955     NVGMatrix res = void;
2956     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2957     return res;
2958   }
2959 
2960   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2961   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2962     NVGMatrix res = void;
2963     res.rotateTransform(a, tx, ty);
2964     return res;
2965   }
2966 }
2967 
2968 
2969 /// Converts degrees to radians.
2970 /// Group: matrices
2971 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2972 
2973 /// Converts radians to degrees.
2974 /// Group: matrices
2975 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2976 
2977 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2978 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2979 
2980 
2981 // ////////////////////////////////////////////////////////////////////////// //
2982 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc {
2983   p.clear();
2984   p.xform.identity;
2985   p.radius = 0.0f;
2986   p.feather = 1.0f;
2987   p.innerColor = p.middleColor = p.outerColor = color;
2988   p.midp = -1;
2989   p.simpleColor = true;
2990 }
2991 
2992 
2993 // ////////////////////////////////////////////////////////////////////////// //
2994 // State handling
2995 
2996 version(nanovega_debug_clipping) {
2997   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
2998   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
2999 }
3000 
3001 /** Pushes and saves the current render state into a state stack.
3002  * A matching [restore] must be used to restore the state.
3003  * Returns `false` if state stack overflowed.
3004  *
3005  * Group: state_handling
3006  */
3007 public bool save (NVGContext ctx) nothrow @trusted @nogc {
3008   if (ctx.nstates >= NVG_MAX_STATES) return false;
3009   if (ctx.nstates > 0) {
3010     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
3011     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
3012     ctx.params.renderPushClip(ctx.params.userPtr);
3013   }
3014   ++ctx.nstates;
3015   return true;
3016 }
3017 
3018 /// Pops and restores current render state.
3019 /// Group: state_handling
3020 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
3021   if (ctx.nstates <= 1) return false;
3022   ctx.states[ctx.nstates-1].clearPaint();
3023   ctx.params.renderPopClip(ctx.params.userPtr);
3024   --ctx.nstates;
3025   return true;
3026 }
3027 
3028 /// Resets current render state to default values. Does not affect the render state stack.
3029 /// Group: state_handling
3030 public void reset (NVGContext ctx) nothrow @trusted @nogc {
3031   NVGstate* state = nvg__getState(ctx);
3032   state.clearPaint();
3033 
3034   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3035   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3036   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3037   state.shapeAntiAlias = true;
3038   state.strokeWidth = 1.0f;
3039   state.miterLimit = 10.0f;
3040   state.lineCap = NVGLineCap.Butt;
3041   state.lineJoin = NVGLineCap.Miter;
3042   state.alpha = 1.0f;
3043   state.xform.identity;
3044 
3045   state.scissor.extent[] = -1.0f;
3046 
3047   state.fontSize = 16.0f;
3048   state.letterSpacing = 0.0f;
3049   state.lineHeight = 1.0f;
3050   state.fontBlur = 0.0f;
3051   state.textAlign.reset;
3052   state.fontId = 0;
3053   state.evenOddMode = false;
3054   state.dashCount = 0;
3055   state.lastFlattenDashCount = 0;
3056   state.dashStart = 0;
3057   state.firstDashIsGap = false;
3058   state.dasherActive = false;
3059 
3060   ctx.params.renderResetClip(ctx.params.userPtr);
3061 }
3062 
3063 /** Returns `true` if we have any room in state stack.
3064  * It is guaranteed to have at least 32 stack slots.
3065  *
3066  * Group: state_handling
3067  */
3068 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3069 
3070 /** Returns `true` if we have any saved state.
3071  *
3072  * Group: state_handling
3073  */
3074 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3075 
3076 /// Returns `true` if rendering is currently blocked.
3077 /// Group: state_handling
3078 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3079 
3080 /// Blocks/unblocks rendering
3081 /// Group: state_handling
3082 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3083 
3084 /// Blocks/unblocks rendering; returns previous state.
3085 /// Group: state_handling
3086 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; }
3087 
3088 
3089 // ////////////////////////////////////////////////////////////////////////// //
3090 // Render styles
3091 
3092 /// Sets filling mode to "even-odd".
3093 /// Group: render_styles
3094 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3095   NVGstate* state = nvg__getState(ctx);
3096   state.evenOddMode = true;
3097 }
3098 
3099 /// Sets filling mode to "non-zero" (this is default mode).
3100 /// Group: render_styles
3101 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3102   NVGstate* state = nvg__getState(ctx);
3103   state.evenOddMode = false;
3104 }
3105 
3106 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3107 /// Group: render_styles
3108 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3109   NVGstate* state = nvg__getState(ctx);
3110   state.shapeAntiAlias = enabled;
3111 }
3112 
3113 /// Sets the stroke width of the stroke style.
3114 /// Group: render_styles
3115 @scriptable
3116 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3117   NVGstate* state = nvg__getState(ctx);
3118   state.strokeWidth = width;
3119 }
3120 
3121 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3122 /// Group: render_styles
3123 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3124   NVGstate* state = nvg__getState(ctx);
3125   state.miterLimit = limit;
3126 }
3127 
3128 /// Sets how the end of the line (cap) is drawn,
3129 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3130 /// Group: render_styles
3131 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3132   NVGstate* state = nvg__getState(ctx);
3133   state.lineCap = cap;
3134 }
3135 
3136 /// Sets how sharp path corners are drawn.
3137 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3138 /// Group: render_styles
3139 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3140   NVGstate* state = nvg__getState(ctx);
3141   state.lineJoin = join;
3142 }
3143 
3144 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3145 /// Current limit is 16 pairs.
3146 /// Resets dash start to zero.
3147 /// Group: render_styles
3148 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3149   NVGstate* state = nvg__getState(ctx);
3150   state.dashCount = 0;
3151   state.dashStart = 0;
3152   state.firstDashIsGap = false;
3153   if (dashdata.length >= 2) {
3154     bool curFIsGap = true; // trick
3155     foreach (immutable idx, float f; dashdata) {
3156       curFIsGap = !curFIsGap;
3157       if (f < 0.01f) continue; // skip it
3158       if (idx == 0) {
3159         // register first dash
3160         state.firstDashIsGap = curFIsGap;
3161         state.dashes.ptr[state.dashCount++] = f;
3162       } else {
3163         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3164           // oops, continuation
3165           state.dashes[state.dashCount-1] += f;
3166         } else {
3167           if (state.dashCount == state.dashes.length) break;
3168           state.dashes[state.dashCount++] = f;
3169         }
3170       }
3171     }
3172     if (state.dashCount&1) {
3173       if (state.dashCount == 1) {
3174         state.dashCount = 0;
3175       } else {
3176         assert(state.dashCount < state.dashes.length);
3177         state.dashes[state.dashCount++] = 0;
3178       }
3179     }
3180     // calculate total dash path length
3181     state.totalDashLen = 0;
3182     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3183     if (state.totalDashLen < 0.01f) {
3184       state.dashCount = 0; // nothing to do
3185     } else {
3186       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3187     }
3188   }
3189 }
3190 
3191 public alias lineDash = setLineDash; /// Ditto.
3192 
3193 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3194 /// Current limit is 16 pairs.
3195 /// Group: render_styles
3196 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3197   NVGstate* state = nvg__getState(ctx);
3198   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3199     state.lastFlattenDashCount = uint.max; // force re-flattening
3200   }
3201   state.dashStart = dashStart;
3202 }
3203 
3204 public alias lineDashStart = setLineDashStart; /// Ditto.
3205 
3206 /// Sets the transparency applied to all rendered shapes.
3207 /// Already transparent paths will get proportionally more transparent as well.
3208 /// Group: render_styles
3209 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3210   NVGstate* state = nvg__getState(ctx);
3211   state.alpha = alpha;
3212 }
3213 
3214 private void strokeColor() {}
3215 
3216 static if (NanoVegaHasArsdColor) {
3217 /// Sets current stroke style to a solid color.
3218 /// Group: render_styles
3219 @scriptable
3220 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3221   NVGstate* state = nvg__getState(ctx);
3222   nvg__setPaintColor(state.stroke, NVGColor(color));
3223 }
3224 }
3225 
3226 /// Sets current stroke style to a solid color.
3227 /// Group: render_styles
3228 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3229   NVGstate* state = nvg__getState(ctx);
3230   nvg__setPaintColor(state.stroke, color);
3231 }
3232 
3233 @scriptable
3234 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3235 	strokePaint(ctx, *paint);
3236 }
3237 
3238 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3239 /// Group: render_styles
3240 @scriptable
3241 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3242   NVGstate* state = nvg__getState(ctx);
3243   state.stroke = paint;
3244   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3245   state.stroke.xform.mul(state.xform);
3246 }
3247 
3248 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3249 // for scriptable reflection. it just needs to be declared first among the overloads
3250 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3251 
3252 static if (NanoVegaHasArsdColor) {
3253 /// Sets current fill style to a solid color.
3254 /// Group: render_styles
3255 @scriptable
3256 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3257   NVGstate* state = nvg__getState(ctx);
3258   nvg__setPaintColor(state.fill, NVGColor(color));
3259 }
3260 }
3261 
3262 /// Sets current fill style to a solid color.
3263 /// Group: render_styles
3264 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3265   NVGstate* state = nvg__getState(ctx);
3266   nvg__setPaintColor(state.fill, color);
3267 
3268 }
3269 
3270 @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)
3271 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3272 	fillPaint(ctx, *paint);
3273 }
3274 
3275 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3276 /// Group: render_styles
3277 @scriptable
3278 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3279   NVGstate* state = nvg__getState(ctx);
3280   state.fill = paint;
3281   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3282   state.fill.xform.mul(state.xform);
3283 }
3284 
3285 /// Sets current fill style to a multistop linear gradient.
3286 /// Group: render_styles
3287 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3288   if (!lgs.valid) {
3289     NVGPaint p = void;
3290     memset(&p, 0, p.sizeof);
3291     nvg__setPaintColor(p, NVGColor.red);
3292     ctx.fillPaint = p;
3293   } else if (lgs.midp >= -1) {
3294     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3295     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3296   } else {
3297     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3298   }
3299 }
3300 
3301 /// Returns current transformation matrix.
3302 /// Group: render_transformations
3303 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3304   NVGstate* state = nvg__getState(ctx);
3305   return state.xform;
3306 }
3307 
3308 /// Sets current transformation matrix.
3309 /// Group: render_transformations
3310 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc {
3311   NVGstate* state = nvg__getState(ctx);
3312   state.xform = m;
3313 }
3314 
3315 /// Resets current transform to an identity matrix.
3316 /// Group: render_transformations
3317 @scriptable
3318 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3319   NVGstate* state = nvg__getState(ctx);
3320   state.xform.identity;
3321 }
3322 
3323 /// Premultiplies current coordinate system by specified matrix.
3324 /// Group: render_transformations
3325 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc {
3326   NVGstate* state = nvg__getState(ctx);
3327   //nvgTransformPremultiply(state.xform[], t[]);
3328   state.xform *= mt;
3329 }
3330 
3331 /// Translates current coordinate system.
3332 /// Group: render_transformations
3333 @scriptable
3334 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3335   NVGstate* state = nvg__getState(ctx);
3336   //NVGMatrix t = void;
3337   //nvgTransformTranslate(t[], x, y);
3338   //nvgTransformPremultiply(state.xform[], t[]);
3339   state.xform.premul(NVGMatrix.Translated(x, y));
3340 }
3341 
3342 /// Rotates current coordinate system. Angle is specified in radians.
3343 /// Group: render_transformations
3344 @scriptable
3345 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3346   NVGstate* state = nvg__getState(ctx);
3347   //NVGMatrix t = void;
3348   //nvgTransformRotate(t[], angle);
3349   //nvgTransformPremultiply(state.xform[], t[]);
3350   state.xform.premul(NVGMatrix.Rotated(angle));
3351 }
3352 
3353 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3354 /// Group: render_transformations
3355 @scriptable
3356 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3357   NVGstate* state = nvg__getState(ctx);
3358   //NVGMatrix t = void;
3359   //nvgTransformSkewX(t[], angle);
3360   //nvgTransformPremultiply(state.xform[], t[]);
3361   state.xform.premul(NVGMatrix.SkewedX(angle));
3362 }
3363 
3364 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3365 /// Group: render_transformations
3366 @scriptable
3367 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3368   NVGstate* state = nvg__getState(ctx);
3369   //NVGMatrix t = void;
3370   //nvgTransformSkewY(t[], angle);
3371   //nvgTransformPremultiply(state.xform[], t[]);
3372   state.xform.premul(NVGMatrix.SkewedY(angle));
3373 }
3374 
3375 /// Scales the current coordinate system.
3376 /// Group: render_transformations
3377 @scriptable
3378 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3379   NVGstate* state = nvg__getState(ctx);
3380   //NVGMatrix t = void;
3381   //nvgTransformScale(t[], x, y);
3382   //nvgTransformPremultiply(state.xform[], t[]);
3383   state.xform.premul(NVGMatrix.Scaled(x, y));
3384 }
3385 
3386 
3387 // ////////////////////////////////////////////////////////////////////////// //
3388 // Images
3389 
3390 /// Creates image by loading it from the disk from specified file name.
3391 /// Returns handle to the image or 0 on error.
3392 /// Group: images
3393 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3394   static if (NanoVegaHasArsdImage) {
3395     import arsd.image;
3396     // do we have new arsd API to load images?
3397     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3398       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3399     }
3400     try {
3401       auto oimg = MemoryImage.fromImageFile(filename);
3402       if (auto img = cast(TrueColorImage)oimg) {
3403         scope(exit) oimg.clearInternal();
3404         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3405       } else {
3406         TrueColorImage img = oimg.getAsTrueColorImage;
3407         scope(exit) img.clearInternal();
3408         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3409         oimg = null;
3410         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3411       }
3412     } catch (Exception) {}
3413     return NVGImage.init;
3414   } else {
3415     import std.internal.cstring;
3416     ubyte* img;
3417     int w, h, n;
3418     stbi_set_unpremultiply_on_load(1);
3419     stbi_convert_iphone_png_to_rgb(1);
3420     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3421     if (img is null) {
3422       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3423       return NVGImage.init;
3424     }
3425     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3426     stbi_image_free(img);
3427     return image;
3428   }
3429 }
3430 
3431 static if (NanoVegaHasArsdImage) {
3432   /// Creates image by loading it from the specified memory image.
3433   /// Returns handle to the image or 0 on error.
3434   /// Group: images
3435   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3436     if (img is null) return NVGImage.init;
3437     if (auto tc = cast(TrueColorImage)img) {
3438       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3439     } else {
3440       auto tc = img.getAsTrueColorImage;
3441       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3442       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3443     }
3444   }
3445 } else {
3446   /// Creates image by loading it from the specified chunk of memory.
3447   /// Returns handle to the image or 0 on error.
3448   /// Group: images
3449   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3450     int w, h, n, image;
3451     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3452     if (img is null) {
3453       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3454       return NVGImage.init;
3455     }
3456     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3457     stbi_image_free(img);
3458     return image;
3459   }
3460 }
3461 
3462 /// Creates image from specified image data.
3463 /// Returns handle to the image or 0 on error.
3464 /// Group: images
3465 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3466   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3467   uint imageFlags = 0;
3468   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3469   NVGImage res;
3470   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3471   if (res.id > 0) {
3472     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3473     res.ctx = ctx;
3474     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3475   }
3476   return res;
3477 }
3478 
3479 /// Updates image data specified by image handle.
3480 /// Group: images
3481 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3482   if (image.valid) {
3483     int w, h;
3484     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3485     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3486     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3487   }
3488 }
3489 
3490 /// Returns the dimensions of a created image.
3491 /// Group: images
3492 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3493   if (image.valid) {
3494     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3495     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3496   }
3497 }
3498 
3499 /// Deletes created image.
3500 /// Group: images
3501 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3502   if (ctx is null || !image.valid) return;
3503   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3504   image.clear();
3505 }
3506 
3507 
3508 // ////////////////////////////////////////////////////////////////////////// //
3509 // Paints
3510 
3511 private void linearGradient() {} // hack for dmd bug
3512 
3513 static if (NanoVegaHasArsdColor) {
3514 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3515  * of the linear gradient, icol specifies the start color and ocol the end color.
3516  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3517  *
3518  * Group: paints
3519  */
3520 @scriptable
3521 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 {
3522   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3523 }
3524 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3525  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3526  * range `(0..1)`, and ocol the end color.
3527  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3528  *
3529  * Group: paints
3530  */
3531 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 {
3532   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3533 }
3534 }
3535 
3536 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3537  * of the linear gradient, icol specifies the start color and ocol the end color.
3538  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3539  *
3540  * Group: paints
3541  */
3542 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3543   enum large = 1e5f;
3544 
3545   NVGPaint p = void;
3546   memset(&p, 0, p.sizeof);
3547   p.simpleColor = false;
3548 
3549   // Calculate transform aligned to the line
3550   float dx = ex-sx;
3551   float dy = ey-sy;
3552   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3553   if (d > 0.0001f) {
3554     dx /= d;
3555     dy /= d;
3556   } else {
3557     dx = 0;
3558     dy = 1;
3559   }
3560 
3561   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3562   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3563   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3564 
3565   p.extent.ptr[0] = large;
3566   p.extent.ptr[1] = large+d*0.5f;
3567 
3568   p.radius = 0.0f;
3569 
3570   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3571 
3572   p.innerColor = p.middleColor = icol;
3573   p.outerColor = ocol;
3574   p.midp = -1;
3575 
3576   return p;
3577 }
3578 
3579 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3580  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3581  * range `(0..1)`, and ocol the end color.
3582  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3583  *
3584  * Group: paints
3585  */
3586 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in float midp, in auto ref NVGColor mcol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3587   enum large = 1e5f;
3588 
3589   NVGPaint p = void;
3590   memset(&p, 0, p.sizeof);
3591   p.simpleColor = false;
3592 
3593   // Calculate transform aligned to the line
3594   float dx = ex-sx;
3595   float dy = ey-sy;
3596   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3597   if (d > 0.0001f) {
3598     dx /= d;
3599     dy /= d;
3600   } else {
3601     dx = 0;
3602     dy = 1;
3603   }
3604 
3605   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3606   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3607   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3608 
3609   p.extent.ptr[0] = large;
3610   p.extent.ptr[1] = large+d*0.5f;
3611 
3612   p.radius = 0.0f;
3613 
3614   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3615 
3616   if (midp <= 0) {
3617     p.innerColor = p.middleColor = mcol;
3618     p.midp = -1;
3619   } else if (midp > 1) {
3620     p.innerColor = p.middleColor = icol;
3621     p.midp = -1;
3622   } else {
3623     p.innerColor = icol;
3624     p.middleColor = mcol;
3625     p.midp = midp;
3626   }
3627   p.outerColor = ocol;
3628 
3629   return p;
3630 }
3631 
3632 static if (NanoVegaHasArsdColor) {
3633 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3634  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3635  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3636  *
3637  * Group: paints
3638  */
3639 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 {
3640   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3641 }
3642 }
3643 
3644 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3645  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3646  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3647  *
3648  * Group: paints
3649  */
3650 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3651   immutable float r = (inr+outr)*0.5f;
3652   immutable float f = (outr-inr);
3653 
3654   NVGPaint p = void;
3655   memset(&p, 0, p.sizeof);
3656   p.simpleColor = false;
3657 
3658   p.xform.identity;
3659   p.xform.mat.ptr[4] = cx;
3660   p.xform.mat.ptr[5] = cy;
3661 
3662   p.extent.ptr[0] = r;
3663   p.extent.ptr[1] = r;
3664 
3665   p.radius = r;
3666 
3667   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3668 
3669   p.innerColor = p.middleColor = icol;
3670   p.outerColor = ocol;
3671   p.midp = -1;
3672 
3673   return p;
3674 }
3675 
3676 static if (NanoVegaHasArsdColor) {
3677 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3678  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3679  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3680  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3681  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3682  *
3683  * Group: paints
3684  */
3685 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 {
3686   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3687 }
3688 }
3689 
3690 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3691  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3692  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3693  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3694  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3695  *
3696  * Group: paints
3697  */
3698 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3699   NVGPaint p = void;
3700   memset(&p, 0, p.sizeof);
3701   p.simpleColor = false;
3702 
3703   p.xform.identity;
3704   p.xform.mat.ptr[4] = x+w*0.5f;
3705   p.xform.mat.ptr[5] = y+h*0.5f;
3706 
3707   p.extent.ptr[0] = w*0.5f;
3708   p.extent.ptr[1] = h*0.5f;
3709 
3710   p.radius = r;
3711 
3712   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3713 
3714   p.innerColor = p.middleColor = icol;
3715   p.outerColor = ocol;
3716   p.midp = -1;
3717 
3718   return p;
3719 }
3720 
3721 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3722  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3723  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3724  *
3725  * Group: paints
3726  */
3727 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc {
3728   NVGPaint p = void;
3729   memset(&p, 0, p.sizeof);
3730   p.simpleColor = false;
3731 
3732   p.xform.identity.rotate(angle);
3733   p.xform.mat.ptr[4] = cx;
3734   p.xform.mat.ptr[5] = cy;
3735 
3736   p.extent.ptr[0] = w;
3737   p.extent.ptr[1] = h;
3738 
3739   p.image = image;
3740 
3741   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3742   p.midp = -1;
3743 
3744   return p;
3745 }
3746 
3747 /// Linear gradient with multiple stops.
3748 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3749 /// Group: paints
3750 public struct NVGLGS {
3751 private:
3752   NVGColor ic, mc, oc; // inner, middle, out
3753   float midp;
3754   NVGImage imgid;
3755   // [imagePattern] arguments
3756   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3757   public float angle; ///
3758 
3759 public:
3760   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3761   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3762 }
3763 
3764 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3765  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3766  *
3767  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3768  * Group: paints
3769  */
3770 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3771   if (!lgs.valid) {
3772     NVGPaint p = void;
3773     memset(&p, 0, p.sizeof);
3774     nvg__setPaintColor(p, NVGColor.red);
3775     return p;
3776   } else if (lgs.midp >= -1) {
3777     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3778   } else {
3779     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3780   }
3781 }
3782 
3783 /// Gradient Stop Point.
3784 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3785 /// Group: paints
3786 public struct NVGGradientStop {
3787   float offset = 0; /// [0..1]
3788   NVGColor color; ///
3789 
3790   this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3791   static if (NanoVegaHasArsdColor) {
3792     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3793   }
3794 }
3795 
3796 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3797 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3798 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3799 /// Group: paints
3800 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3801   // based on the code by Jorge Acereda <jacereda@gmail.com>
3802   enum NVG_GRADIENT_SAMPLES = 1024;
3803   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3804     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3805     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3806     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3807     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3808     uint sc = 0xffffffffU;
3809     uint sh = 24;
3810     uint r = cast(uint)(s0.color.rgba[0]*sc);
3811     uint g = cast(uint)(s0.color.rgba[1]*sc);
3812     uint b = cast(uint)(s0.color.rgba[2]*sc);
3813     uint a = cast(uint)(s0.color.rgba[3]*sc);
3814     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3815     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3816     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3817     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3818     dst += s;
3819     foreach (immutable _; s..e) {
3820       version(BigEndian) {
3821         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3822       } else {
3823         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3824       }
3825       r += dr;
3826       g += dg;
3827       b += db;
3828       a += da;
3829     }
3830   }
3831 
3832   NVGLGS res;
3833   res.cx = sx;
3834   res.cy = sy;
3835 
3836   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3837     // create simple linear gradient
3838     res.ic = res.mc = stops.ptr[0].color;
3839     res.oc = stops.ptr[1].color;
3840     res.midp = -1;
3841     res.dimx = ex;
3842     res.dimy = ey;
3843   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3844     // create simple linear gradient with middle stop
3845     res.ic = stops.ptr[0].color;
3846     res.mc = stops.ptr[1].color;
3847     res.oc = stops.ptr[2].color;
3848     res.midp = stops.ptr[1].offset;
3849     res.dimx = ex;
3850     res.dimy = ey;
3851   } else {
3852     // create image gradient
3853     uint[NVG_GRADIENT_SAMPLES] data = void;
3854     immutable float w = ex-sx;
3855     immutable float h = ey-sy;
3856     res.dimx = nvg__sqrtf(w*w+h*h);
3857     res.dimy = 1; //???
3858 
3859     res.angle =
3860       (/*nvg__absf(h) < 0.0001 ? 0 :
3861        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3862        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3863 
3864     if (stops.length > 0) {
3865       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3866       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3867       if (stops.length > 64) stops = stops[0..64];
3868       if (stops.length) {
3869         s0.color = stops[0].color;
3870         s1.color = stops[$-1].color;
3871       }
3872       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3873       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3874       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3875       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3876     }
3877   }
3878   return res;
3879 }
3880 
3881 
3882 // ////////////////////////////////////////////////////////////////////////// //
3883 // Scissoring
3884 
3885 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3886 /// Group: scissoring
3887 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3888   NVGstate* state = nvg__getState(ctx);
3889 
3890   w = nvg__max(0.0f, w);
3891   h = nvg__max(0.0f, h);
3892 
3893   state.scissor.xform.identity;
3894   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3895   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3896   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3897   state.scissor.xform.mul(state.xform);
3898 
3899   state.scissor.extent.ptr[0] = w*0.5f;
3900   state.scissor.extent.ptr[1] = h*0.5f;
3901 }
3902 
3903 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3904 /// Arguments: [x, y, w, h]*
3905 /// Group: scissoring
3906 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3907   enum ArgC = 4;
3908   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3909   if (args.length < ArgC) return;
3910   NVGstate* state = nvg__getState(ctx);
3911   const(float)* aptr = args.ptr;
3912   foreach (immutable idx; 0..args.length/ArgC) {
3913     immutable x = *aptr++;
3914     immutable y = *aptr++;
3915     immutable w = nvg__max(0.0f, *aptr++);
3916     immutable h = nvg__max(0.0f, *aptr++);
3917 
3918     state.scissor.xform.identity;
3919     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3920     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3921     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3922     state.scissor.xform.mul(state.xform);
3923 
3924     state.scissor.extent.ptr[0] = w*0.5f;
3925     state.scissor.extent.ptr[1] = h*0.5f;
3926   }
3927 }
3928 
3929 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3930   immutable float minx = nvg__max(ax, bx);
3931   immutable float miny = nvg__max(ay, by);
3932   immutable float maxx = nvg__min(ax+aw, bx+bw);
3933   immutable float maxy = nvg__min(ay+ah, by+bh);
3934   dst[0] = minx;
3935   dst[1] = miny;
3936   dst[2] = nvg__max(0.0f, maxx-minx);
3937   dst[3] = nvg__max(0.0f, maxy-miny);
3938 }
3939 
3940 /** Intersects current scissor rectangle with the specified rectangle.
3941  * The scissor rectangle is transformed by the current transform.
3942  * Note: in case the rotation of previous scissor rect differs from
3943  * the current one, the intersection will be done between the specified
3944  * rectangle and the previous scissor rectangle transformed in the current
3945  * transform space. The resulting shape is always rectangle.
3946  *
3947  * Group: scissoring
3948  */
3949 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3950   NVGstate* state = nvg__getState(ctx);
3951 
3952   // If no previous scissor has been set, set the scissor as current scissor.
3953   if (state.scissor.extent.ptr[0] < 0) {
3954     ctx.scissor(x, y, w, h);
3955     return;
3956   }
3957 
3958   NVGMatrix pxform = void;
3959   NVGMatrix invxorm = void;
3960   float[4] rect = void;
3961 
3962   // Transform the current scissor rect into current transform space.
3963   // If there is difference in rotation, this will be approximation.
3964   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3965   pxform = state.scissor.xform;
3966   immutable float ex = state.scissor.extent.ptr[0];
3967   immutable float ey = state.scissor.extent.ptr[1];
3968   //nvgTransformInverse(invxorm[], state.xform[]);
3969   invxorm = state.xform.inverted;
3970   //nvgTransformMultiply(pxform[], invxorm[]);
3971   pxform.mul(invxorm);
3972   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3973   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3974 
3975   // Intersect rects.
3976   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3977 
3978   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3979   ctx.scissor(rect.ptr[0..4]);
3980 }
3981 
3982 /** Intersects current scissor rectangle with the specified rectangle.
3983  * The scissor rectangle is transformed by the current transform.
3984  * Note: in case the rotation of previous scissor rect differs from
3985  * the current one, the intersection will be done between the specified
3986  * rectangle and the previous scissor rectangle transformed in the current
3987  * transform space. The resulting shape is always rectangle.
3988  *
3989  * Arguments: [x, y, w, h]*
3990  *
3991  * Group: scissoring
3992  */
3993 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3994   enum ArgC = 4;
3995   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
3996   if (args.length < ArgC) return;
3997   const(float)* aptr = args.ptr;
3998   foreach (immutable idx; 0..args.length/ArgC) {
3999     immutable x = *aptr++;
4000     immutable y = *aptr++;
4001     immutable w = *aptr++;
4002     immutable h = *aptr++;
4003     ctx.intersectScissor(x, y, w, h);
4004   }
4005 }
4006 
4007 /// Reset and disables scissoring.
4008 /// Group: scissoring
4009 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
4010   NVGstate* state = nvg__getState(ctx);
4011   state.scissor.xform.mat[] = 0.0f;
4012   state.scissor.extent[] = -1.0f;
4013 }
4014 
4015 
4016 // ////////////////////////////////////////////////////////////////////////// //
4017 // Render-Time Affine Transformations
4018 
4019 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
4020 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
4021 /// Group: gpu_affine
4022 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc {
4023   ctx.gpuAffine = mat;
4024   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
4025 }
4026 
4027 /// Get current GPU affine transformatin matrix.
4028 /// Group: gpu_affine
4029 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
4030   pragma(inline, true);
4031   return ctx.gpuAffine;
4032 }
4033 
4034 /// "Untransform" point using current GPU affine matrix.
4035 /// Group: gpu_affine
4036 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4037   if (ctx.gpuAffine.isIdentity) {
4038     if (dx !is null) *dx = x;
4039     if (dy !is null) *dy = y;
4040   } else {
4041     // inverse GPU transformation
4042     NVGMatrix igpu = ctx.gpuAffine.inverted;
4043     igpu.point(dx, dy, x, y);
4044   }
4045 }
4046 
4047 
4048 // ////////////////////////////////////////////////////////////////////////// //
4049 // rasterization (tesselation) code
4050 
4051 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4052   //pragma(inline, true);
4053   immutable float dx = x2-x1;
4054   immutable float dy = y2-y1;
4055   return dx*dx+dy*dy < tol*tol;
4056 }
4057 
4058 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4059   immutable float pqx = qx-px;
4060   immutable float pqy = qy-py;
4061   float dx = x-px;
4062   float dy = y-py;
4063   immutable float d = pqx*pqx+pqy*pqy;
4064   float t = pqx*dx+pqy*dy;
4065   if (d > 0) t /= d;
4066   if (t < 0) t = 0; else if (t > 1) t = 1;
4067   dx = px+t*pqx-x;
4068   dy = py+t*pqy-y;
4069   return dx*dx+dy*dy;
4070 }
4071 
4072 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4073   int nvals = cast(int)vals.length;
4074   static if (useCommand) {
4075     enum addon = 1;
4076   } else {
4077     enum addon = 0;
4078     if (nvals == 0) return; // nothing to do
4079   }
4080 
4081   NVGstate* state = nvg__getState(ctx);
4082 
4083   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4084     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4085     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4086     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4087     if (commands is null) assert(0, "NanoVega: out of memory");
4088     ctx.commands = commands;
4089     ctx.ccommands = ccommands;
4090     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4091   }
4092 
4093   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4094 
4095   if (acmd != Command.Close && acmd != Command.Winding) {
4096     //assert(nvals+addon >= 3);
4097     ctx.commandx = vals.ptr[nvals-2];
4098     ctx.commandy = vals.ptr[nvals-1];
4099   }
4100 
4101   // copy commands
4102   float* vp = ctx.commands+ctx.ncommands;
4103   static if (useCommand) {
4104     vp[0] = cast(float)acmd;
4105     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4106   } else {
4107     memcpy(vp, vals.ptr, nvals*float.sizeof);
4108   }
4109   ctx.ncommands += nvals+addon;
4110 
4111   // transform commands
4112   int i = nvals+addon;
4113   while (i > 0) {
4114     int nlen = 1;
4115     final switch (cast(Command)(*vp)) {
4116       case Command.MoveTo:
4117       case Command.LineTo:
4118         assert(i >= 3);
4119         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4120         nlen = 3;
4121         break;
4122       case Command.BezierTo:
4123         assert(i >= 7);
4124         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4125         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4126         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4127         nlen = 7;
4128         break;
4129       case Command.Close:
4130         nlen = 1;
4131         break;
4132       case Command.Winding:
4133         nlen = 2;
4134         break;
4135     }
4136     assert(nlen > 0 && nlen <= i);
4137     i -= nlen;
4138     vp += nlen;
4139   }
4140 }
4141 
4142 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4143   // no need to clear paths, as data is not copied there
4144   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4145   ctx.cache.npoints = 0;
4146   ctx.cache.npaths = 0;
4147   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4148   ctx.cache.clipmode = NVGClipMode.None;
4149 }
4150 
4151 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4152   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4153 }
4154 
4155 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4156   import core.stdc.stdlib : realloc;
4157   import core.stdc.string : memset;
4158 
4159   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4160     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4161     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4162     if (paths is null) assert(0, "NanoVega: out of memory");
4163     ctx.cache.paths = paths;
4164     ctx.cache.cpaths = cpaths;
4165   }
4166 
4167   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4168   memset(path, 0, NVGpath.sizeof);
4169   path.first = ctx.cache.npoints;
4170   path.mWinding = NVGWinding.CCW;
4171 }
4172 
4173 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4174   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4175 }
4176 
4177 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4178   NVGpath* path = nvg__lastPath(ctx);
4179   if (path is null) return;
4180 
4181   if (path.count > 0 && ctx.cache.npoints > 0) {
4182     NVGpoint* pt = nvg__lastPoint(ctx);
4183     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4184       pt.flags |= flags;
4185       return;
4186     }
4187   }
4188 
4189   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4190     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4191     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4192     if (points is null) return;
4193     ctx.cache.points = points;
4194     ctx.cache.cpoints = cpoints;
4195   }
4196 
4197   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4198   memset(pt, 0, (*pt).sizeof);
4199   pt.x = x;
4200   pt.y = y;
4201   pt.flags = cast(ubyte)flags;
4202 
4203   ++ctx.cache.npoints;
4204   ++path.count;
4205 }
4206 
4207 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4208   NVGpath* path = nvg__lastPath(ctx);
4209   if (path is null) return;
4210   path.closed = true;
4211 }
4212 
4213 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4214   NVGpath* path = nvg__lastPath(ctx);
4215   if (path is null) return;
4216   path.mWinding = winding;
4217 }
4218 
4219 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4220   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4221   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4222   return (sx+sy)*0.5f;
4223 }
4224 
4225 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4226   if (nverts > ctx.cache.cverts) {
4227     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4228     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4229     if (verts is null) return null;
4230     ctx.cache.verts = verts;
4231     ctx.cache.cverts = cverts;
4232   }
4233 
4234   return ctx.cache.verts;
4235 }
4236 
4237 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4238   immutable float abx = bx-ax;
4239   immutable float aby = by-ay;
4240   immutable float acx = cx-ax;
4241   immutable float acy = cy-ay;
4242   return acx*aby-abx*acy;
4243 }
4244 
4245 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4246   float area = 0;
4247   foreach (int i; 2..npts) {
4248     NVGpoint* a = &pts[0];
4249     NVGpoint* b = &pts[i-1];
4250     NVGpoint* c = &pts[i];
4251     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4252   }
4253   return area*0.5f;
4254 }
4255 
4256 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4257   NVGpoint tmp = void;
4258   int i = 0, j = npts-1;
4259   while (i < j) {
4260     tmp = pts[i];
4261     pts[i] = pts[j];
4262     pts[j] = tmp;
4263     ++i;
4264     --j;
4265   }
4266 }
4267 
4268 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4269   vtx.x = x;
4270   vtx.y = y;
4271   vtx.u = u;
4272   vtx.v = v;
4273 }
4274 
4275 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 {
4276   if (level > 10) return;
4277 
4278   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4279   /*
4280   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4281     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4282       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4283       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4284     }
4285     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4286       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4287       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4288       return;
4289     }
4290   }
4291   */
4292 
4293   immutable float x12 = (x1+x2)*0.5f;
4294   immutable float y12 = (y1+y2)*0.5f;
4295   immutable float x23 = (x2+x3)*0.5f;
4296   immutable float y23 = (y2+y3)*0.5f;
4297   immutable float x34 = (x3+x4)*0.5f;
4298   immutable float y34 = (y3+y4)*0.5f;
4299   immutable float x123 = (x12+x23)*0.5f;
4300   immutable float y123 = (y12+y23)*0.5f;
4301 
4302   immutable float dx = x4-x1;
4303   immutable float dy = y4-y1;
4304   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4305   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4306 
4307   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4308     nvg__addPoint(ctx, x4, y4, type);
4309     return;
4310   }
4311 
4312   immutable float x234 = (x23+x34)*0.5f;
4313   immutable float y234 = (y23+y34)*0.5f;
4314   immutable float x1234 = (x123+x234)*0.5f;
4315   immutable float y1234 = (y123+y234)*0.5f;
4316 
4317   // "taxicab" / "manhattan" check for flat curves
4318   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) {
4319     nvg__addPoint(ctx, x1234, y1234, type);
4320     return;
4321   }
4322 
4323   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4324   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4325 }
4326 
4327 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4328 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4329 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 {
4330   enum CollinearEPS = 0.00000001f; // 0.00001f;
4331   enum AngleTolEPS = 0.01f;
4332 
4333   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4334     pragma(inline, true);
4335     immutable float dx = x2-x1;
4336     immutable float dy = y2-y1;
4337     return dx*dx+dy*dy;
4338   }
4339 
4340   if (level == 0) {
4341     nvg__addPoint(ctx, x1, y1, 0);
4342     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4343     nvg__addPoint(ctx, x4, y4, type);
4344     return;
4345   }
4346 
4347   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4348 
4349   // calculate all the mid-points of the line segments
4350   immutable float x12 = (x1+x2)*0.5f;
4351   immutable float y12 = (y1+y2)*0.5f;
4352   immutable float x23 = (x2+x3)*0.5f;
4353   immutable float y23 = (y2+y3)*0.5f;
4354   immutable float x34 = (x3+x4)*0.5f;
4355   immutable float y34 = (y3+y4)*0.5f;
4356   immutable float x123 = (x12+x23)*0.5f;
4357   immutable float y123 = (y12+y23)*0.5f;
4358   immutable float x234 = (x23+x34)*0.5f;
4359   immutable float y234 = (y23+y34)*0.5f;
4360   immutable float x1234 = (x123+x234)*0.5f;
4361   immutable float y1234 = (y123+y234)*0.5f;
4362 
4363   // try to approximate the full cubic curve by a single straight line
4364   immutable float dx = x4-x1;
4365   immutable float dy = y4-y1;
4366 
4367   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4368   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4369   //immutable float da1, da2, k;
4370 
4371   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4372     case 0:
4373       // all collinear or p1 == p4
4374       float k = dx*dx+dy*dy;
4375       if (k == 0) {
4376         d2 = distSquared(x1, y1, x2, y2);
4377         d3 = distSquared(x4, y4, x3, y3);
4378       } else {
4379         k = 1.0f/k;
4380         float da1 = x2-x1;
4381         float da2 = y2-y1;
4382         d2 = k*(da1*dx+da2*dy);
4383         da1 = x3-x1;
4384         da2 = y3-y1;
4385         d3 = k*(da1*dx+da2*dy);
4386         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4387           // Simple collinear case, 1---2---3---4
4388           // We can leave just two endpoints
4389           return;
4390         }
4391              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4392         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4393         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4394 
4395              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4396         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4397         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4398       }
4399       if (d2 > d3) {
4400         if (d2 < ctx.tessTol) {
4401           nvg__addPoint(ctx, x2, y2, type);
4402           return;
4403         }
4404       } if (d3 < ctx.tessTol) {
4405         nvg__addPoint(ctx, x3, y3, type);
4406         return;
4407       }
4408       break;
4409     case 1:
4410       // p1,p2,p4 are collinear, p3 is significant
4411       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4412         if (ctx.angleTol < AngleTolEPS) {
4413           nvg__addPoint(ctx, x23, y23, type);
4414           return;
4415         } else {
4416           // angle condition
4417           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4418           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4419           if (da1 < ctx.angleTol) {
4420             nvg__addPoint(ctx, x2, y2, type);
4421             nvg__addPoint(ctx, x3, y3, type);
4422             return;
4423           }
4424           if (ctx.cuspLimit != 0.0) {
4425             if (da1 > ctx.cuspLimit) {
4426               nvg__addPoint(ctx, x3, y3, type);
4427               return;
4428             }
4429           }
4430         }
4431       }
4432       break;
4433     case 2:
4434       // p1,p3,p4 are collinear, p2 is significant
4435       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4436         if (ctx.angleTol < AngleTolEPS) {
4437           nvg__addPoint(ctx, x23, y23, type);
4438           return;
4439         } else {
4440           // angle condition
4441           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4442           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4443           if (da1 < ctx.angleTol) {
4444             nvg__addPoint(ctx, x2, y2, type);
4445             nvg__addPoint(ctx, x3, y3, type);
4446             return;
4447           }
4448           if (ctx.cuspLimit != 0.0) {
4449             if (da1 > ctx.cuspLimit) {
4450               nvg__addPoint(ctx, x2, y2, type);
4451               return;
4452             }
4453           }
4454         }
4455       }
4456       break;
4457     case 3:
4458       // regular case
4459       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4460         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4461         if (ctx.angleTol < AngleTolEPS) {
4462           nvg__addPoint(ctx, x23, y23, type);
4463           return;
4464         } else {
4465           // angle and cusp condition
4466           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4467           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4468           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4469           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4470           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4471           if (da1+da2 < ctx.angleTol) {
4472             // finally we can stop the recursion
4473             nvg__addPoint(ctx, x23, y23, type);
4474             return;
4475           }
4476           if (ctx.cuspLimit != 0.0) {
4477             if (da1 > ctx.cuspLimit) {
4478               nvg__addPoint(ctx, x2, y2, type);
4479               return;
4480             }
4481             if (da2 > ctx.cuspLimit) {
4482               nvg__addPoint(ctx, x3, y3, type);
4483               return;
4484             }
4485           }
4486         }
4487       }
4488       break;
4489   }
4490 
4491   // continue subdivision
4492   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4493   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4494 }
4495 
4496 
4497 // Adaptive forward differencing for bezier tesselation.
4498 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4499 // "Adaptive forward differencing for rendering curves and surfaces."
4500 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4501 // original code by Taylor Holliday <taylor@audulus.com>
4502 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 {
4503   enum AFD_ONE = (1<<10);
4504 
4505   // power basis
4506   immutable float ax = -x1+3*x2-3*x3+x4;
4507   immutable float ay = -y1+3*y2-3*y3+y4;
4508   immutable float bx = 3*x1-6*x2+3*x3;
4509   immutable float by = 3*y1-6*y2+3*y3;
4510   immutable float cx = -3*x1+3*x2;
4511   immutable float cy = -3*y1+3*y2;
4512 
4513   // Transform to forward difference basis (stepsize 1)
4514   float px = x1;
4515   float py = y1;
4516   float dx = ax+bx+cx;
4517   float dy = ay+by+cy;
4518   float ddx = 6*ax+2*bx;
4519   float ddy = 6*ay+2*by;
4520   float dddx = 6*ax;
4521   float dddy = 6*ay;
4522 
4523   //printf("dx: %f, dy: %f\n", dx, dy);
4524   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4525   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4526 
4527   int t = 0;
4528   int dt = AFD_ONE;
4529 
4530   immutable float tol = ctx.tessTol*4;
4531 
4532   while (t < AFD_ONE) {
4533     // Flatness measure.
4534     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4535 
4536     // printf("d: %f, th: %f\n", d, th);
4537 
4538     // Go to higher resolution if we're moving a lot or overshooting the end.
4539     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4540       // printf("up\n");
4541 
4542       // Apply L to the curve. Increase curve resolution.
4543       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4544       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4545       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4546       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4547       dddx = (1.0f/8.0f)*dddx;
4548       dddy = (1.0f/8.0f)*dddy;
4549 
4550       // Half the stepsize.
4551       dt >>= 1;
4552 
4553       // Recompute d
4554       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4555     }
4556 
4557     // Go to lower resolution if we're really flat
4558     // and we aren't going to overshoot the end.
4559     // XXX: tol/32 is just a guess for when we are too flat.
4560     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4561       // printf("down\n");
4562 
4563       // Apply L^(-1) to the curve. Decrease curve resolution.
4564       dx = 2*dx+ddx;
4565       dy = 2*dy+ddy;
4566       ddx = 4*ddx+4*dddx;
4567       ddy = 4*ddy+4*dddy;
4568       dddx = 8*dddx;
4569       dddy = 8*dddy;
4570 
4571       // Double the stepsize.
4572       dt <<= 1;
4573 
4574       // Recompute d
4575       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4576     }
4577 
4578     // Forward differencing.
4579     px += dx;
4580     py += dy;
4581     dx += ddx;
4582     dy += ddy;
4583     ddx += dddx;
4584     ddy += dddy;
4585 
4586     // Output a point.
4587     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4588 
4589     // Advance along the curve.
4590     t += dt;
4591 
4592     // Ensure we don't overshoot.
4593     assert(t <= AFD_ONE);
4594   }
4595 }
4596 
4597 
4598 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4599   import core.stdc.stdlib : realloc;
4600   import core.stdc.string : memcpy;
4601 
4602   NVGpathCache* cache = ctx.cache;
4603   if (cache.npaths == 0) return;
4604 
4605   NVGpath* path = nvg__lastPath(ctx);
4606   if (path is null) return;
4607 
4608   NVGstate* state = nvg__getState(ctx);
4609   if (!state.dasherActive) return;
4610 
4611   static NVGpoint* pts = null;
4612   static uint ptsCount = 0;
4613   static uint ptsSize = 0;
4614 
4615   if (path.count < 2) return; // just in case
4616 
4617   // copy path points (reserve one point for closed pathes)
4618   if (ptsSize < path.count+1) {
4619     ptsSize = cast(uint)(path.count+1);
4620     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4621     if (pts is null) assert(0, "NanoVega: out of memory");
4622   }
4623   ptsCount = cast(uint)path.count;
4624   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4625   // add closing point for closed pathes
4626   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4627     pts[ptsCount++] = pts[0];
4628   }
4629 
4630   // remove last path (with its points)
4631   --cache.npaths;
4632   cache.npoints -= path.count;
4633 
4634   // add stroked pathes
4635   const(float)* dashes = state.dashes.ptr;
4636   immutable uint dashCount = state.dashCount;
4637   float currDashStart = 0;
4638   uint currDashIdx = 0;
4639   immutable bool firstIsGap = state.firstDashIsGap;
4640 
4641   // calculate lengthes
4642   {
4643     NVGpoint* v1 = &pts[0];
4644     NVGpoint* v2 = &pts[1];
4645     foreach (immutable _; 0..ptsCount) {
4646       float dx = v2.x-v1.x;
4647       float dy = v2.y-v1.y;
4648       v1.len = nvg__normalize(&dx, &dy);
4649       v1 = v2++;
4650     }
4651   }
4652 
4653   void calcDashStart (float ds) {
4654     if (ds < 0) {
4655       ds = ds%state.totalDashLen;
4656       while (ds < 0) ds += state.totalDashLen;
4657     }
4658     currDashIdx = 0;
4659     currDashStart = 0;
4660     while (ds > 0) {
4661       if (ds > dashes[currDashIdx]) {
4662         ds -= dashes[currDashIdx];
4663         ++currDashIdx;
4664         currDashStart = 0;
4665         if (currDashIdx >= dashCount) currDashIdx = 0;
4666       } else {
4667         currDashStart = ds;
4668         ds = 0;
4669       }
4670     }
4671   }
4672 
4673   calcDashStart(state.dashStart);
4674 
4675   uint srcPointIdx = 1;
4676   const(NVGpoint)* v1 = &pts[0];
4677   const(NVGpoint)* v2 = &pts[1];
4678   float currRest = v1.len;
4679   nvg__addPath(ctx);
4680   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4681 
4682   void fixLastPoint () {
4683     auto lpt = nvg__lastPath(ctx);
4684     if (lpt !is null && lpt.count > 0) {
4685       // fix last point
4686       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4687       // fix first point
4688       NVGpathCache* cache = ctx.cache;
4689       cache.points[lpt.first].flags = PointFlag.Corner;
4690     }
4691   }
4692 
4693   for (;;) {
4694     immutable float dlen = dashes[currDashIdx];
4695     if (dlen == 0) {
4696       ++currDashIdx;
4697       if (currDashIdx >= dashCount) currDashIdx = 0;
4698       continue;
4699     }
4700     immutable float dashRest = dlen-currDashStart;
4701     if ((currDashIdx&1) != firstIsGap) {
4702       // this is "moveto" command, so create new path
4703       fixLastPoint();
4704       nvg__addPath(ctx);
4705     }
4706     if (currRest > dashRest) {
4707       currRest -= dashRest;
4708       ++currDashIdx;
4709       if (currDashIdx >= dashCount) currDashIdx = 0;
4710       currDashStart = 0;
4711       nvg__addPoint(ctx,
4712         v2.x-(v2.x-v1.x)*currRest/v1.len,
4713         v2.y-(v2.y-v1.y)*currRest/v1.len,
4714         PointFlag.Corner
4715       );
4716     } else {
4717       currDashStart += currRest;
4718       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4719       ++srcPointIdx;
4720       v1 = v2;
4721       currRest = v1.len;
4722       if (srcPointIdx >= ptsCount) break;
4723       v2 = &pts[srcPointIdx];
4724     }
4725   }
4726   fixLastPoint();
4727 }
4728 
4729 
4730 version(nanovg_bench_flatten) import iv.timer : Timer;
4731 
4732 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4733   version(nanovg_bench_flatten) {
4734     Timer timer;
4735     char[128] tmbuf;
4736     int bzcount;
4737   }
4738   NVGpathCache* cache = ctx.cache;
4739   NVGstate* state = nvg__getState(ctx);
4740 
4741   // check if we already did flattening
4742   static if (asStroke) {
4743     if (state.dashCount >= 2) {
4744       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4745       state.dasherActive = true;
4746       state.lastFlattenDashCount = state.dashCount;
4747     } else {
4748       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4749       state.dasherActive = false;
4750       state.lastFlattenDashCount = 0;
4751     }
4752   } else {
4753     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4754     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4755     state.dasherActive = false;
4756   }
4757 
4758   // clear path cache
4759   cache.npaths = 0;
4760   cache.npoints = 0;
4761 
4762   // flatten
4763   version(nanovg_bench_flatten) timer.restart();
4764   int i = 0;
4765   while (i < ctx.ncommands) {
4766     final switch (cast(Command)ctx.commands[i]) {
4767       case Command.MoveTo:
4768         //assert(i+3 <= ctx.ncommands);
4769         static if (asStroke) {
4770           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4771         }
4772         nvg__addPath(ctx);
4773         const p = &ctx.commands[i+1];
4774         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4775         i += 3;
4776         break;
4777       case Command.LineTo:
4778         //assert(i+3 <= ctx.ncommands);
4779         const p = &ctx.commands[i+1];
4780         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4781         i += 3;
4782         break;
4783       case Command.BezierTo:
4784         //assert(i+7 <= ctx.ncommands);
4785         const last = nvg__lastPoint(ctx);
4786         if (last !is null) {
4787           const cp1 = &ctx.commands[i+1];
4788           const cp2 = &ctx.commands[i+3];
4789           const p = &ctx.commands[i+5];
4790           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4791             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4792           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4793             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4794           } else {
4795             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4796           }
4797           version(nanovg_bench_flatten) ++bzcount;
4798         }
4799         i += 7;
4800         break;
4801       case Command.Close:
4802         //assert(i+1 <= ctx.ncommands);
4803         nvg__closePath(ctx);
4804         i += 1;
4805         break;
4806       case Command.Winding:
4807         //assert(i+2 <= ctx.ncommands);
4808         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4809         i += 2;
4810         break;
4811     }
4812   }
4813   static if (asStroke) {
4814     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4815   }
4816   version(nanovg_bench_flatten) {{
4817     timer.stop();
4818     auto xb = timer.toBuffer(tmbuf[]);
4819     import core.stdc.stdio : printf;
4820     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4821   }}
4822 
4823   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4824   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4825 
4826   // calculate the direction and length of line segments
4827   version(nanovg_bench_flatten) timer.restart();
4828   foreach (int j; 0..cache.npaths) {
4829     NVGpath* path = &cache.paths[j];
4830     NVGpoint* pts = &cache.points[path.first];
4831 
4832     // if the first and last points are the same, remove the last, mark as closed path
4833     NVGpoint* p0 = &pts[path.count-1];
4834     NVGpoint* p1 = &pts[0];
4835     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4836       --path.count;
4837       p0 = &pts[path.count-1];
4838       path.closed = true;
4839     }
4840 
4841     // enforce winding
4842     if (path.count > 2) {
4843       immutable float area = nvg__polyArea(pts, path.count);
4844       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4845       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4846     }
4847 
4848     foreach (immutable _; 0..path.count) {
4849       // calculate segment direction and length
4850       p0.dx = p1.x-p0.x;
4851       p0.dy = p1.y-p0.y;
4852       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4853       // update bounds
4854       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4855       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4856       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4857       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4858       // advance
4859       p0 = p1++;
4860     }
4861   }
4862   version(nanovg_bench_flatten) {{
4863     timer.stop();
4864     auto xb = timer.toBuffer(tmbuf[]);
4865     import core.stdc.stdio : printf;
4866     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4867   }}
4868 }
4869 
4870 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4871   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4872   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4873 }
4874 
4875 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4876   if (bevel) {
4877     *x0 = p1.x+p0.dy*w;
4878     *y0 = p1.y-p0.dx*w;
4879     *x1 = p1.x+p1.dy*w;
4880     *y1 = p1.y-p1.dx*w;
4881   } else {
4882     *x0 = p1.x+p1.dmx*w;
4883     *y0 = p1.y+p1.dmy*w;
4884     *x1 = p1.x+p1.dmx*w;
4885     *y1 = p1.y+p1.dmy*w;
4886   }
4887 }
4888 
4889 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4890   float dlx0 = p0.dy;
4891   float dly0 = -p0.dx;
4892   float dlx1 = p1.dy;
4893   float dly1 = -p1.dx;
4894   //NVG_NOTUSED(fringe);
4895 
4896   if (p1.flags&PointFlag.Left) {
4897     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4898     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4899     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4900     float a1 = nvg__atan2f(-dly1, -dlx1);
4901     if (a1 > a0) a1 -= NVG_PI*2;
4902 
4903     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4904     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4905 
4906     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4907     for (int i = 0; i < n; ++i) {
4908       float u = i/cast(float)(n-1);
4909       float a = a0+u*(a1-a0);
4910       float rx = p1.x+nvg__cosf(a)*rw;
4911       float ry = p1.y+nvg__sinf(a)*rw;
4912       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4913       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4914     }
4915 
4916     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4917     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4918 
4919   } else {
4920     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4921     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4922     immutable float a0 = nvg__atan2f(dly0, dlx0);
4923     float a1 = nvg__atan2f(dly1, dlx1);
4924     if (a1 < a0) a1 += NVG_PI*2;
4925 
4926     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4927     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4928 
4929     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4930     for (int i = 0; i < n; i++) {
4931       float u = i/cast(float)(n-1);
4932       float a = a0+u*(a1-a0);
4933       float lx = p1.x+nvg__cosf(a)*lw;
4934       float ly = p1.y+nvg__sinf(a)*lw;
4935       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4936       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4937     }
4938 
4939     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4940     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4941 
4942   }
4943   return dst;
4944 }
4945 
4946 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4947   float rx0, ry0, rx1, ry1;
4948   float lx0, ly0, lx1, ly1;
4949   float dlx0 = p0.dy;
4950   float dly0 = -p0.dx;
4951   float dlx1 = p1.dy;
4952   float dly1 = -p1.dx;
4953   //NVG_NOTUSED(fringe);
4954 
4955   if (p1.flags&PointFlag.Left) {
4956     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4957 
4958     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4959     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4960 
4961     if (p1.flags&PointFlag.Bevel) {
4962       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4963       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4964 
4965       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4966       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4967     } else {
4968       rx0 = p1.x-p1.dmx*rw;
4969       ry0 = p1.y-p1.dmy*rw;
4970 
4971       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4972       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4973 
4974       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4975       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4976 
4977       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4978       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4979     }
4980 
4981     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4982     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4983 
4984   } else {
4985     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4986 
4987     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4988     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4989 
4990     if (p1.flags&PointFlag.Bevel) {
4991       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4992       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4993 
4994       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4995       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4996     } else {
4997       lx0 = p1.x+p1.dmx*lw;
4998       ly0 = p1.y+p1.dmy*lw;
4999 
5000       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5001       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5002 
5003       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5004       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5005 
5006       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5007       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5008     }
5009 
5010     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5011     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5012   }
5013 
5014   return dst;
5015 }
5016 
5017 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5018   immutable float px = p.x-dx*d;
5019   immutable float py = p.y-dy*d;
5020   immutable float dlx = dy;
5021   immutable float dly = -dx;
5022   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
5023   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
5024   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5025   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5026   return dst;
5027 }
5028 
5029 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5030   immutable float px = p.x+dx*d;
5031   immutable float py = p.y+dy*d;
5032   immutable float dlx = dy;
5033   immutable float dly = -dx;
5034   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5035   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5036   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5037   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5038   return dst;
5039 }
5040 
5041 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5042   immutable float px = p.x;
5043   immutable float py = p.y;
5044   immutable float dlx = dy;
5045   immutable float dly = -dx;
5046   //NVG_NOTUSED(aa);
5047   immutable float ncpf = cast(float)(ncap-1);
5048   foreach (int i; 0..ncap) {
5049     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5050     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5051     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5052     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5053   }
5054   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5055   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5056   return dst;
5057 }
5058 
5059 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5060   immutable float px = p.x;
5061   immutable float py = p.y;
5062   immutable float dlx = dy;
5063   immutable float dly = -dx;
5064   //NVG_NOTUSED(aa);
5065   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5066   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5067   immutable float ncpf = cast(float)(ncap-1);
5068   foreach (int i; 0..ncap) {
5069     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5070     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5071     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5072     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5073   }
5074   return dst;
5075 }
5076 
5077 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5078   NVGpathCache* cache = ctx.cache;
5079   float iw = 0.0f;
5080 
5081   if (w > 0.0f) iw = 1.0f/w;
5082 
5083   // Calculate which joins needs extra vertices to append, and gather vertex count.
5084   foreach (int i; 0..cache.npaths) {
5085     NVGpath* path = &cache.paths[i];
5086     NVGpoint* pts = &cache.points[path.first];
5087     NVGpoint* p0 = &pts[path.count-1];
5088     NVGpoint* p1 = &pts[0];
5089     int nleft = 0;
5090 
5091     path.nbevel = 0;
5092 
5093     foreach (int j; 0..path.count) {
5094       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5095       immutable float dlx0 = p0.dy;
5096       immutable float dly0 = -p0.dx;
5097       immutable float dlx1 = p1.dy;
5098       immutable float dly1 = -p1.dx;
5099       // Calculate extrusions
5100       p1.dmx = (dlx0+dlx1)*0.5f;
5101       p1.dmy = (dly0+dly1)*0.5f;
5102       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5103       if (dmr2 > 0.000001f) {
5104         float scale = 1.0f/dmr2;
5105         if (scale > 600.0f) scale = 600.0f;
5106         p1.dmx *= scale;
5107         p1.dmy *= scale;
5108       }
5109 
5110       // Clear flags, but keep the corner.
5111       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5112 
5113       // Keep track of left turns.
5114       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5115       if (cross > 0.0f) {
5116         nleft++;
5117         p1.flags |= PointFlag.Left;
5118       }
5119 
5120       // Calculate if we should use bevel or miter for inner join.
5121       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5122       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5123 
5124       // Check to see if the corner needs to be beveled.
5125       if (p1.flags&PointFlag.Corner) {
5126         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5127           p1.flags |= PointFlag.Bevel;
5128         }
5129       }
5130 
5131       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5132 
5133       p0 = p1++;
5134     }
5135 
5136     path.convex = (nleft == path.count);
5137   }
5138 }
5139 
5140 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5141   NVGpathCache* cache = ctx.cache;
5142   immutable float aa = ctx.fringeWidth;
5143   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5144 
5145   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5146 
5147   // Calculate max vertex usage.
5148   int cverts = 0;
5149   foreach (int i; 0..cache.npaths) {
5150     NVGpath* path = &cache.paths[i];
5151     immutable bool loop = path.closed;
5152     if (lineJoin == NVGLineCap.Round) {
5153       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5154     } else {
5155       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5156     }
5157     if (!loop) {
5158       // space for caps
5159       if (lineCap == NVGLineCap.Round) {
5160         cverts += (ncap*2+2)*2;
5161       } else {
5162         cverts += (3+3)*2;
5163       }
5164     }
5165   }
5166 
5167   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5168   if (verts is null) return;
5169 
5170   foreach (int i; 0..cache.npaths) {
5171     NVGpath* path = &cache.paths[i];
5172     NVGpoint* pts = &cache.points[path.first];
5173     NVGpoint* p0;
5174     NVGpoint* p1;
5175     int s, e;
5176 
5177     path.fill = null;
5178     path.nfill = 0;
5179 
5180     // Calculate fringe or stroke
5181     immutable bool loop = path.closed;
5182     NVGVertex* dst = verts;
5183     path.stroke = dst;
5184 
5185     if (loop) {
5186       // Looping
5187       p0 = &pts[path.count-1];
5188       p1 = &pts[0];
5189       s = 0;
5190       e = path.count;
5191     } else {
5192       // Add cap
5193       p0 = &pts[0];
5194       p1 = &pts[1];
5195       s = 1;
5196       e = path.count-1;
5197     }
5198 
5199     if (!loop) {
5200       // Add cap
5201       float dx = p1.x-p0.x;
5202       float dy = p1.y-p0.y;
5203       nvg__normalize(&dx, &dy);
5204            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5205       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5206       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5207     }
5208 
5209     foreach (int j; s..e) {
5210       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5211         if (lineJoin == NVGLineCap.Round) {
5212           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5213         } else {
5214           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5215         }
5216       } else {
5217         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5218         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5219       }
5220       p0 = p1++;
5221     }
5222 
5223     if (loop) {
5224       // Loop it
5225       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5226       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5227     } else {
5228       // Add cap
5229       float dx = p1.x-p0.x;
5230       float dy = p1.y-p0.y;
5231       nvg__normalize(&dx, &dy);
5232            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5233       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5234       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5235     }
5236 
5237     path.nstroke = cast(int)(dst-verts);
5238 
5239     verts = dst;
5240   }
5241 }
5242 
5243 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5244   NVGpathCache* cache = ctx.cache;
5245   immutable float aa = ctx.fringeWidth;
5246   bool fringe = (w > 0.0f);
5247 
5248   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5249 
5250   // Calculate max vertex usage.
5251   int cverts = 0;
5252   foreach (int i; 0..cache.npaths) {
5253     NVGpath* path = &cache.paths[i];
5254     cverts += path.count+path.nbevel+1;
5255     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5256   }
5257 
5258   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5259   if (verts is null) return;
5260 
5261   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5262 
5263   foreach (int i; 0..cache.npaths) {
5264     NVGpath* path = &cache.paths[i];
5265     NVGpoint* pts = &cache.points[path.first];
5266 
5267     // Calculate shape vertices.
5268     immutable float woff = 0.5f*aa;
5269     NVGVertex* dst = verts;
5270     path.fill = dst;
5271 
5272     if (fringe) {
5273       // Looping
5274       NVGpoint* p0 = &pts[path.count-1];
5275       NVGpoint* p1 = &pts[0];
5276       foreach (int j; 0..path.count) {
5277         if (p1.flags&PointFlag.Bevel) {
5278           immutable float dlx0 = p0.dy;
5279           immutable float dly0 = -p0.dx;
5280           immutable float dlx1 = p1.dy;
5281           immutable float dly1 = -p1.dx;
5282           if (p1.flags&PointFlag.Left) {
5283             immutable float lx = p1.x+p1.dmx*woff;
5284             immutable float ly = p1.y+p1.dmy*woff;
5285             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5286           } else {
5287             immutable float lx0 = p1.x+dlx0*woff;
5288             immutable float ly0 = p1.y+dly0*woff;
5289             immutable float lx1 = p1.x+dlx1*woff;
5290             immutable float ly1 = p1.y+dly1*woff;
5291             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5292             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5293           }
5294         } else {
5295           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5296         }
5297         p0 = p1++;
5298       }
5299     } else {
5300       foreach (int j; 0..path.count) {
5301         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5302         ++dst;
5303       }
5304     }
5305 
5306     path.nfill = cast(int)(dst-verts);
5307     verts = dst;
5308 
5309     // Calculate fringe
5310     if (fringe) {
5311       float lw = w+woff;
5312       immutable float rw = w-woff;
5313       float lu = 0;
5314       immutable float ru = 1;
5315       dst = verts;
5316       path.stroke = dst;
5317 
5318       // Create only half a fringe for convex shapes so that
5319       // the shape can be rendered without stenciling.
5320       if (convex) {
5321         lw = woff; // This should generate the same vertex as fill inset above.
5322         lu = 0.5f; // Set outline fade at middle.
5323       }
5324 
5325       // Looping
5326       NVGpoint* p0 = &pts[path.count-1];
5327       NVGpoint* p1 = &pts[0];
5328 
5329       foreach (int j; 0..path.count) {
5330         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5331           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5332         } else {
5333           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5334           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5335         }
5336         p0 = p1++;
5337       }
5338 
5339       // Loop it
5340       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5341       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5342 
5343       path.nstroke = cast(int)(dst-verts);
5344       verts = dst;
5345     } else {
5346       path.stroke = null;
5347       path.nstroke = 0;
5348     }
5349   }
5350 }
5351 
5352 
5353 // ////////////////////////////////////////////////////////////////////////// //
5354 // Paths
5355 
5356 /// Clears the current path and sub-paths.
5357 /// Group: paths
5358 @scriptable
5359 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5360   ctx.ncommands = 0;
5361   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5362   nvg__clearPathCache(ctx);
5363 }
5364 
5365 public alias newPath = beginPath; /// Ditto.
5366 
5367 /// Starts new sub-path with specified point as first point.
5368 /// Group: paths
5369 @scriptable
5370 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5371   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5372 }
5373 
5374 /// Starts new sub-path with specified point as first point.
5375 /// Arguments: [x, y]*
5376 /// Group: paths
5377 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5378   enum ArgC = 2;
5379   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5380   if (args.length < ArgC) return;
5381   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5382 }
5383 
5384 /// Adds line segment from the last point in the path to the specified point.
5385 /// Group: paths
5386 @scriptable
5387 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5388   nvg__appendCommands(ctx, Command.LineTo, x, y);
5389 }
5390 
5391 /// Adds line segment from the last point in the path to the specified point.
5392 /// Arguments: [x, y]*
5393 /// Group: paths
5394 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5395   enum ArgC = 2;
5396   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5397   if (args.length < ArgC) return;
5398   foreach (immutable idx; 0..args.length/ArgC) {
5399     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5400   }
5401 }
5402 
5403 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5404 /// Group: paths
5405 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 {
5406   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5407 }
5408 
5409 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5410 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5411 /// Group: paths
5412 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5413   enum ArgC = 6;
5414   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5415   if (args.length < ArgC) return;
5416   foreach (immutable idx; 0..args.length/ArgC) {
5417     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5418   }
5419 }
5420 
5421 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5422 /// Group: paths
5423 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5424   immutable float x0 = ctx.commandx;
5425   immutable float y0 = ctx.commandy;
5426   nvg__appendCommands(ctx,
5427     Command.BezierTo,
5428     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5429     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5430     x, y,
5431   );
5432 }
5433 
5434 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5435 /// Arguments: [cx, cy, x, y]*
5436 /// Group: paths
5437 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5438   enum ArgC = 4;
5439   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5440   if (args.length < ArgC) return;
5441   const(float)* aptr = args.ptr;
5442   foreach (immutable idx; 0..args.length/ArgC) {
5443     immutable float x0 = ctx.commandx;
5444     immutable float y0 = ctx.commandy;
5445     immutable float cx = *aptr++;
5446     immutable float cy = *aptr++;
5447     immutable float x = *aptr++;
5448     immutable float y = *aptr++;
5449     nvg__appendCommands(ctx,
5450       Command.BezierTo,
5451       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5452       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5453       x, y,
5454     );
5455   }
5456 }
5457 
5458 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5459 /// Group: paths
5460 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5461   if (ctx.ncommands == 0) return;
5462 
5463   immutable float x0 = ctx.commandx;
5464   immutable float y0 = ctx.commandy;
5465 
5466   // handle degenerate cases
5467   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5468       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5469       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5470       radius < ctx.distTol)
5471   {
5472     ctx.lineTo(x1, y1);
5473     return;
5474   }
5475 
5476   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5477   float dx0 = x0-x1;
5478   float dy0 = y0-y1;
5479   float dx1 = x2-x1;
5480   float dy1 = y2-y1;
5481   nvg__normalize(&dx0, &dy0);
5482   nvg__normalize(&dx1, &dy1);
5483   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5484   immutable float d = radius/nvg__tanf(a/2.0f);
5485 
5486   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5487 
5488   if (d > 10000.0f) {
5489     ctx.lineTo(x1, y1);
5490     return;
5491   }
5492 
5493   float cx = void, cy = void, a0 = void, a1 = void;
5494   NVGWinding dir;
5495   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5496     cx = x1+dx0*d+dy0*radius;
5497     cy = y1+dy0*d+-dx0*radius;
5498     a0 = nvg__atan2f(dx0, -dy0);
5499     a1 = nvg__atan2f(-dx1, dy1);
5500     dir = NVGWinding.CW;
5501     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5502   } else {
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.CCW;
5508     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5509   }
5510 
5511   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5512 }
5513 
5514 
5515 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5516 /// Arguments: [x1, y1, x2, y2, radius]*
5517 /// Group: paths
5518 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5519   enum ArgC = 5;
5520   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5521   if (args.length < ArgC) return;
5522   if (ctx.ncommands == 0) return;
5523   const(float)* aptr = args.ptr;
5524   foreach (immutable idx; 0..args.length/ArgC) {
5525     immutable float x0 = ctx.commandx;
5526     immutable float y0 = ctx.commandy;
5527     immutable float x1 = *aptr++;
5528     immutable float y1 = *aptr++;
5529     immutable float x2 = *aptr++;
5530     immutable float y2 = *aptr++;
5531     immutable float radius = *aptr++;
5532     ctx.arcTo(x1, y1, x2, y2, radius);
5533   }
5534 }
5535 
5536 /// Closes current sub-path with a line segment.
5537 /// Group: paths
5538 @scriptable
5539 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5540   nvg__appendCommands(ctx, Command.Close);
5541 }
5542 
5543 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5544 /// Group: paths
5545 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5546   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5547 }
5548 
5549 /// Ditto.
5550 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5551   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5552 }
5553 
5554 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5555  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5556  * Angles are specified in radians.
5557  *
5558  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5559  *
5560  * Group: paths
5561  */
5562 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 {
5563   static assert(mode == "original" || mode == "move" || mode == "line");
5564 
5565   float[3+5*7+100] vals = void;
5566   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5567   static if (mode == "original") {
5568     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5569   } else static if (mode == "move") {
5570     enum move = Command.MoveTo;
5571   } else static if (mode == "line") {
5572     enum move = Command.LineTo;
5573   } else {
5574     static assert(0, "wtf?!");
5575   }
5576 
5577   // Clamp angles
5578   float da = a1-a0;
5579   if (dir == NVGWinding.CW) {
5580     if (nvg__absf(da) >= NVG_PI*2) {
5581       da = NVG_PI*2;
5582     } else {
5583       while (da < 0.0f) da += NVG_PI*2;
5584     }
5585   } else {
5586     if (nvg__absf(da) >= NVG_PI*2) {
5587       da = -NVG_PI*2;
5588     } else {
5589       while (da > 0.0f) da -= NVG_PI*2;
5590     }
5591   }
5592 
5593   // Split arc into max 90 degree segments.
5594   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5595   immutable float hda = (da/cast(float)ndivs)/2.0f;
5596   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5597 
5598   if (dir == NVGWinding.CCW) kappa = -kappa;
5599 
5600   int nvals = 0;
5601   float px = 0, py = 0, ptanx = 0, ptany = 0;
5602   foreach (int i; 0..ndivs+1) {
5603     immutable float a = a0+da*(i/cast(float)ndivs);
5604     immutable float dx = nvg__cosf(a);
5605     immutable float dy = nvg__sinf(a);
5606     immutable float x = cx+dx*r;
5607     immutable float y = cy+dy*r;
5608     immutable float tanx = -dy*r*kappa;
5609     immutable float tany = dx*r*kappa;
5610 
5611     if (i == 0) {
5612       if (vals.length-nvals < 3) {
5613         // flush
5614         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5615         nvals = 0;
5616       }
5617       vals.ptr[nvals++] = cast(float)move;
5618       vals.ptr[nvals++] = x;
5619       vals.ptr[nvals++] = y;
5620     } else {
5621       if (vals.length-nvals < 7) {
5622         // flush
5623         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5624         nvals = 0;
5625       }
5626       vals.ptr[nvals++] = Command.BezierTo;
5627       vals.ptr[nvals++] = px+ptanx;
5628       vals.ptr[nvals++] = py+ptany;
5629       vals.ptr[nvals++] = x-tanx;
5630       vals.ptr[nvals++] = y-tany;
5631       vals.ptr[nvals++] = x;
5632       vals.ptr[nvals++] = y;
5633     }
5634     px = x;
5635     py = y;
5636     ptanx = tanx;
5637     ptany = tany;
5638   }
5639 
5640   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5641 }
5642 
5643 
5644 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5645  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5646  * Angles are specified in radians.
5647  *
5648  * Arguments: [cx, cy, r, a0, a1]*
5649  *
5650  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5651  *
5652  * Group: paths
5653  */
5654 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5655   static assert(mode == "original" || mode == "move" || mode == "line");
5656   enum ArgC = 5;
5657   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5658   if (args.length < ArgC) return;
5659   const(float)* aptr = args.ptr;
5660   foreach (immutable idx; 0..args.length/ArgC) {
5661     immutable cx = *aptr++;
5662     immutable cy = *aptr++;
5663     immutable r = *aptr++;
5664     immutable a0 = *aptr++;
5665     immutable a1 = *aptr++;
5666     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5667   }
5668 }
5669 
5670 /// Creates new rectangle shaped sub-path.
5671 /// Group: paths
5672 @scriptable
5673 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5674   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5675     Command.MoveTo, x, y,
5676     Command.LineTo, x, y+h,
5677     Command.LineTo, x+w, y+h,
5678     Command.LineTo, x+w, y,
5679     Command.Close,
5680   );
5681 }
5682 
5683 /// Creates new rectangle shaped sub-path.
5684 /// Arguments: [x, y, w, h]*
5685 /// Group: paths
5686 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5687   enum ArgC = 4;
5688   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5689   if (args.length < ArgC) return;
5690   const(float)* aptr = args.ptr;
5691   foreach (immutable idx; 0..args.length/ArgC) {
5692     immutable x = *aptr++;
5693     immutable y = *aptr++;
5694     immutable w = *aptr++;
5695     immutable h = *aptr++;
5696     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5697       Command.MoveTo, x, y,
5698       Command.LineTo, x, y+h,
5699       Command.LineTo, x+w, y+h,
5700       Command.LineTo, x+w, y,
5701       Command.Close,
5702     );
5703   }
5704 }
5705 
5706 /// Creates new rounded rectangle shaped sub-path.
5707 /// Group: paths
5708 @scriptable
5709 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5710   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5711 }
5712 
5713 /// Creates new rounded rectangle shaped sub-path.
5714 /// Arguments: [x, y, w, h, radius]*
5715 /// Group: paths
5716 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5717   enum ArgC = 5;
5718   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5719   if (args.length < ArgC) return;
5720   const(float)* aptr = args.ptr;
5721   foreach (immutable idx; 0..args.length/ArgC) {
5722     immutable x = *aptr++;
5723     immutable y = *aptr++;
5724     immutable w = *aptr++;
5725     immutable h = *aptr++;
5726     immutable r = *aptr++;
5727     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5728   }
5729 }
5730 
5731 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5732 /// Group: paths
5733 @scriptable
5734 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 {
5735   if (rw < 0.1f || rh < 0.1f) {
5736     rect(ctx, x, y, w, h);
5737   } else {
5738     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5739       Command.MoveTo, x+rw, y,
5740       Command.LineTo, x+w-rw, y,
5741       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5742       Command.LineTo, x+w, y+h-rh,
5743       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5744       Command.LineTo, x+rw, y+h,
5745       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5746       Command.LineTo, x, y+rh,
5747       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5748       Command.Close,
5749     );
5750   }
5751 }
5752 
5753 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5754 /// Arguments: [x, y, w, h, rw, rh]*
5755 /// Group: paths
5756 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5757   enum ArgC = 6;
5758   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5759   if (args.length < ArgC) return;
5760   const(float)* aptr = args.ptr;
5761   foreach (immutable idx; 0..args.length/ArgC) {
5762     immutable x = *aptr++;
5763     immutable y = *aptr++;
5764     immutable w = *aptr++;
5765     immutable h = *aptr++;
5766     immutable rw = *aptr++;
5767     immutable rh = *aptr++;
5768     if (rw < 0.1f || rh < 0.1f) {
5769       rect(ctx, x, y, w, h);
5770     } else {
5771       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5772         Command.MoveTo, x+rw, y,
5773         Command.LineTo, x+w-rw, y,
5774         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5775         Command.LineTo, x+w, y+h-rh,
5776         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5777         Command.LineTo, x+rw, y+h,
5778         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5779         Command.LineTo, x, y+rh,
5780         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5781         Command.Close,
5782       );
5783     }
5784   }
5785 }
5786 
5787 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5788 /// Group: paths
5789 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 {
5790   if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5791     ctx.rect(x, y, w, h);
5792   } else {
5793     immutable float halfw = nvg__absf(w)*0.5f;
5794     immutable float halfh = nvg__absf(h)*0.5f;
5795     immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5796     immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5797     immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5798     immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5799     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5800       Command.MoveTo, x, y+ryTL,
5801       Command.LineTo, x, y+h-ryBL,
5802       Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5803       Command.LineTo, x+w-rxBR, y+h,
5804       Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5805       Command.LineTo, x+w, y+ryTR,
5806       Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5807       Command.LineTo, x+rxTL, y,
5808       Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5809       Command.Close,
5810     );
5811   }
5812 }
5813 
5814 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5815 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5816 /// Group: paths
5817 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5818   enum ArgC = 8;
5819   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5820   if (args.length < ArgC) return;
5821   const(float)* aptr = args.ptr;
5822   foreach (immutable idx; 0..args.length/ArgC) {
5823     immutable x = *aptr++;
5824     immutable y = *aptr++;
5825     immutable w = *aptr++;
5826     immutable h = *aptr++;
5827     immutable radTopLeft = *aptr++;
5828     immutable radTopRight = *aptr++;
5829     immutable radBottomRight = *aptr++;
5830     immutable radBottomLeft = *aptr++;
5831     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5832       ctx.rect(x, y, w, h);
5833     } else {
5834       immutable float halfw = nvg__absf(w)*0.5f;
5835       immutable float halfh = nvg__absf(h)*0.5f;
5836       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5837       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5838       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5839       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5840       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5841         Command.MoveTo, x, y+ryTL,
5842         Command.LineTo, x, y+h-ryBL,
5843         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5844         Command.LineTo, x+w-rxBR, y+h,
5845         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5846         Command.LineTo, x+w, y+ryTR,
5847         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5848         Command.LineTo, x+rxTL, y,
5849         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5850         Command.Close,
5851       );
5852     }
5853   }
5854 }
5855 
5856 /// Creates new ellipse shaped sub-path.
5857 /// Group: paths
5858 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5859   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5860     Command.MoveTo, cx-rx, cy,
5861     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5862     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5863     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5864     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5865     Command.Close,
5866   );
5867 }
5868 
5869 /// Creates new ellipse shaped sub-path.
5870 /// Arguments: [cx, cy, rx, ry]*
5871 /// Group: paths
5872 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5873   enum ArgC = 4;
5874   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5875   if (args.length < ArgC) return;
5876   const(float)* aptr = args.ptr;
5877   foreach (immutable idx; 0..args.length/ArgC) {
5878     immutable cx = *aptr++;
5879     immutable cy = *aptr++;
5880     immutable rx = *aptr++;
5881     immutable ry = *aptr++;
5882     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5883       Command.MoveTo, cx-rx, cy,
5884       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5885       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5886       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5887       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5888       Command.Close,
5889     );
5890   }
5891 }
5892 
5893 /// Creates new circle shaped sub-path.
5894 /// Group: paths
5895 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5896   ctx.ellipse(cx, cy, r, r);
5897 }
5898 
5899 /// Creates new circle shaped sub-path.
5900 /// Arguments: [cx, cy, r]*
5901 /// Group: paths
5902 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5903   enum ArgC = 3;
5904   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5905   if (args.length < ArgC) return;
5906   const(float)* aptr = args.ptr;
5907   foreach (immutable idx; 0..args.length/ArgC) {
5908     immutable cx = *aptr++;
5909     immutable cy = *aptr++;
5910     immutable r = *aptr++;
5911     ctx.ellipse(cx, cy, r, r);
5912   }
5913 }
5914 
5915 // Debug function to dump cached path data.
5916 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5917   import core.stdc.stdio : printf;
5918   const(NVGpath)* path;
5919   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5920   for (int i = 0; i < ctx.cache.npaths; ++i) {
5921     path = &ctx.cache.paths[i];
5922     printf("-Path %d\n", i);
5923     if (path.nfill) {
5924       printf("-fill: %d\n", path.nfill);
5925       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5926     }
5927     if (path.nstroke) {
5928       printf("-stroke: %d\n", path.nstroke);
5929       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5930     }
5931   }
5932 }
5933 
5934 // Flatten path, prepare it for fill operation.
5935 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5936   NVGpathCache* cache = ctx.cache;
5937   NVGstate* state = nvg__getState(ctx);
5938 
5939   nvg__flattenPaths!false(ctx);
5940 
5941   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5942     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5943   } else {
5944     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5945   }
5946 
5947   cache.evenOddMode = state.evenOddMode;
5948   cache.fringeWidth = ctx.fringeWidth;
5949   cache.fillReady = true;
5950   cache.strokeReady = false;
5951   cache.clipmode = NVGClipMode.None;
5952 }
5953 
5954 // Flatten path, prepare it for stroke operation.
5955 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5956   NVGstate* state = nvg__getState(ctx);
5957   NVGpathCache* cache = ctx.cache;
5958 
5959   nvg__flattenPaths!true(ctx);
5960 
5961   immutable float scale = nvg__getAverageScale(state.xform);
5962   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5963 
5964   if (strokeWidth < ctx.fringeWidth) {
5965     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5966     // Since coverage is area, scale by alpha*alpha.
5967     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5968     cache.strokeAlphaMul = alpha*alpha;
5969     strokeWidth = ctx.fringeWidth;
5970   } else {
5971     cache.strokeAlphaMul = 1.0f;
5972   }
5973   cache.strokeWidth = strokeWidth;
5974 
5975   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5976     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5977   } else {
5978     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5979   }
5980 
5981   cache.fringeWidth = ctx.fringeWidth;
5982   cache.fillReady = false;
5983   cache.strokeReady = true;
5984   cache.clipmode = NVGClipMode.None;
5985 }
5986 
5987 /// Fills the current path with current fill style.
5988 /// Group: paths
5989 @scriptable
5990 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5991   NVGstate* state = nvg__getState(ctx);
5992 
5993   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5994     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5995     ctx.currFillHitId = ctx.pathPickId;
5996   }
5997 
5998   nvg__prepareFill(ctx);
5999 
6000   // apply global alpha
6001   NVGPaint fillPaint = state.fill;
6002   fillPaint.innerColor.a *= state.alpha;
6003   fillPaint.middleColor.a *= state.alpha;
6004   fillPaint.outerColor.a *= state.alpha;
6005 
6006   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6007 
6008   if (ctx.recblockdraw) return;
6009 
6010   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);
6011 
6012   // count triangles
6013   foreach (int i; 0..ctx.cache.npaths) {
6014     NVGpath* path = &ctx.cache.paths[i];
6015     ctx.fillTriCount += path.nfill-2;
6016     ctx.fillTriCount += path.nstroke-2;
6017     ctx.drawCallCount += 2;
6018   }
6019 }
6020 
6021 /// Fills the current path with current stroke style.
6022 /// Group: paths
6023 @scriptable
6024 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
6025   NVGstate* state = nvg__getState(ctx);
6026 
6027   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6028     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6029     ctx.currStrokeHitId = ctx.pathPickId;
6030   }
6031 
6032   nvg__prepareStroke(ctx);
6033 
6034   NVGpathCache* cache = ctx.cache;
6035 
6036   NVGPaint strokePaint = state.stroke;
6037   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6038   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6039   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6040 
6041   // apply global alpha
6042   strokePaint.innerColor.a *= state.alpha;
6043   strokePaint.middleColor.a *= state.alpha;
6044   strokePaint.outerColor.a *= state.alpha;
6045 
6046   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6047 
6048   if (ctx.recblockdraw) return;
6049 
6050   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6051 
6052   // count triangles
6053   foreach (int i; 0..ctx.cache.npaths) {
6054     NVGpath* path = &ctx.cache.paths[i];
6055     ctx.strokeTriCount += path.nstroke-2;
6056     ++ctx.drawCallCount;
6057   }
6058 }
6059 
6060 /// Sets current path as clipping region.
6061 /// Group: clipping
6062 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6063   NVGstate* state = nvg__getState(ctx);
6064 
6065   if (aclipmode == NVGClipMode.None) return;
6066   if (ctx.recblockdraw) return; //???
6067 
6068   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6069 
6070   /*
6071   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6072     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6073     ctx.currFillHitId = ctx.pathPickId;
6074   }
6075   */
6076 
6077   nvg__prepareFill(ctx);
6078 
6079   // apply global alpha
6080   NVGPaint fillPaint = state.fill;
6081   fillPaint.innerColor.a *= state.alpha;
6082   fillPaint.middleColor.a *= state.alpha;
6083   fillPaint.outerColor.a *= state.alpha;
6084 
6085   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6086 
6087   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);
6088 
6089   // count triangles
6090   foreach (int i; 0..ctx.cache.npaths) {
6091     NVGpath* path = &ctx.cache.paths[i];
6092     ctx.fillTriCount += path.nfill-2;
6093     ctx.fillTriCount += path.nstroke-2;
6094     ctx.drawCallCount += 2;
6095   }
6096 }
6097 
6098 /// Sets current path as clipping region.
6099 /// Group: clipping
6100 public alias clipFill = clip;
6101 
6102 /// Sets current path' stroke as clipping region.
6103 /// Group: clipping
6104 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6105   NVGstate* state = nvg__getState(ctx);
6106 
6107   if (aclipmode == NVGClipMode.None) return;
6108   if (ctx.recblockdraw) return; //???
6109 
6110   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6111 
6112   /*
6113   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6114     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6115     ctx.currStrokeHitId = ctx.pathPickId;
6116   }
6117   */
6118 
6119   nvg__prepareStroke(ctx);
6120 
6121   NVGpathCache* cache = ctx.cache;
6122 
6123   NVGPaint strokePaint = state.stroke;
6124   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6125   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6126   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6127 
6128   // apply global alpha
6129   strokePaint.innerColor.a *= state.alpha;
6130   strokePaint.middleColor.a *= state.alpha;
6131   strokePaint.outerColor.a *= state.alpha;
6132 
6133   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6134 
6135   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6136 
6137   // count triangles
6138   foreach (int i; 0..ctx.cache.npaths) {
6139     NVGpath* path = &ctx.cache.paths[i];
6140     ctx.strokeTriCount += path.nstroke-2;
6141     ++ctx.drawCallCount;
6142   }
6143 }
6144 
6145 
6146 // ////////////////////////////////////////////////////////////////////////// //
6147 // Picking API
6148 
6149 // most of the code is by Michael Wynne <mike@mikesspace.net>
6150 // https://github.com/memononen/nanovg/pull/230
6151 // https://github.com/MikeWW/nanovg
6152 
6153 /// Pick type query. Used in [hitTest] and [hitTestAll].
6154 /// Group: picking_api
6155 public enum NVGPickKind : ubyte {
6156   Fill = 0x01, ///
6157   Stroke = 0x02, ///
6158   All = 0x03, ///
6159 }
6160 
6161 /// Marks the fill of the current path as pickable with the specified id.
6162 /// Note that you can create and mark path without rasterizing it.
6163 /// Group: picking_api
6164 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6165   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6166   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6167   nvg__pickSceneInsert(ps, pp);
6168 }
6169 
6170 public alias currFillPickId = currFillHitId; /// Ditto.
6171 
6172 /// Marks the stroke of the current path as pickable with the specified id.
6173 /// Note that you can create and mark path without rasterizing it.
6174 /// Group: picking_api
6175 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6176   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6177   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6178   nvg__pickSceneInsert(ps, pp);
6179 }
6180 
6181 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6182 
6183 // Marks the saved path set (fill) as pickable with the specified id.
6184 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6185 // Group: picking_api
6186 /+
6187 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6188   if (svp is null) return;
6189   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6190   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6191     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6192     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6193     nvg__pickSceneInsert(ps, pp);
6194   }
6195 }
6196 +/
6197 
6198 // Marks the saved path set (stroke) as pickable with the specified id.
6199 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6200 // Group: picking_api
6201 /+
6202 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6203   if (svp is null) return;
6204   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6205   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6206     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6207     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6208     nvg__pickSceneInsert(ps, pp);
6209   }
6210 }
6211 +/
6212 
6213 private template IsGoodHitTestDG(DG) {
6214   enum IsGoodHitTestDG =
6215     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6216     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6217 }
6218 
6219 private template IsGoodHitTestInternalDG(DG) {
6220   enum IsGoodHitTestInternalDG =
6221     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6222     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6223 }
6224 
6225 /// Call delegate [dg] for each path under the specified position (in no particular order).
6226 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6227 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6228 /// Group: picking_api
6229 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) {
6230   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6231 
6232   NVGpickScene* ps = ctx.pickScene;
6233   int levelwidth = 1<<(ps.nlevels-1);
6234   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6235   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6236   int npicked = 0;
6237 
6238   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6239   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6240   // checking, so path walking order is not guaranteed.
6241   static if (bestOrder) {
6242     int lastBestOrder = int.min;
6243   }
6244 
6245   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6246   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6247     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6248       //{ 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); }
6249       static if (bestOrder) {
6250         // reject earlier paths
6251         if (pp.order <= lastBestOrder) continue; // not interesting
6252       }
6253       immutable uint kpx = kind&pp.flags&3;
6254       if (kpx == 0) continue; // not interesting
6255       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6256       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6257       int hit = 0;
6258       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6259       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6260       if (!hit) continue;
6261       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6262       static if (bestOrder) lastBestOrder = pp.order;
6263       static if (IsGoodHitTestDG!DG) {
6264         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6265           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6266         } else {
6267           dg(pp.id, cast(int)pp.order);
6268         }
6269       } else {
6270         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6271           if (dg(pp)) return pp.id;
6272         } else {
6273           dg(pp);
6274         }
6275       }
6276     }
6277     cellx >>= 1;
6278     celly >>= 1;
6279     levelwidth >>= 1;
6280   }
6281 
6282   return -1;
6283 }
6284 
6285 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6286 /// Returns the slice of [ids].
6287 /// Group: picking_api
6288 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6289   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6290 
6291   int npicked = 0;
6292   NVGpickScene* ps = ctx.pickScene;
6293 
6294   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6295     if (npicked == ps.cpicked) {
6296       int cpicked = ps.cpicked+ps.cpicked;
6297       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6298       if (picked is null) return true; // abort
6299       ps.cpicked = cpicked;
6300       ps.picked = picked;
6301     }
6302     ps.picked[npicked] = pp;
6303     ++npicked;
6304     return false; // go on
6305   });
6306 
6307   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6308 
6309   assert(npicked >= 0);
6310   if (npicked > ids.length) npicked = cast(int)ids.length;
6311   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6312 
6313   return ids[0..npicked];
6314 }
6315 
6316 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6317 /// Group: picking_api
6318 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6319   if (ctx.pickScene is null) return -1;
6320 
6321   int bestOrder = int.min;
6322   int bestID = -1;
6323 
6324   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6325     if (pp.order > bestOrder) {
6326       bestOrder = pp.order;
6327       bestID = pp.id;
6328     }
6329   });
6330 
6331   return bestID;
6332 }
6333 
6334 /// Returns `true` if the path with the given id contains x,y.
6335 /// Group: picking_api
6336 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6337   if (ctx.pickScene is null || id == NVGNoPick) return false;
6338 
6339   bool res = false;
6340 
6341   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6342     if (pp.id == id) {
6343       res = true;
6344       return true; // stop
6345     }
6346     return false; // continue
6347   });
6348 
6349   return res;
6350 }
6351 
6352 /// Returns `true` if the given point is within the fill of the currently defined path.
6353 /// This operation can be done before rasterizing the current path.
6354 /// Group: picking_api
6355 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6356   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6357   int oldnpoints = ps.npoints;
6358   int oldnsegments = ps.nsegments;
6359   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6360   if (pp is null) return false; // oops
6361   scope(exit) {
6362     nvg__freePickPath(ps, pp);
6363     ps.npoints = oldnpoints;
6364     ps.nsegments = oldnsegments;
6365   }
6366   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6367 }
6368 
6369 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6370 
6371 /// Returns `true` if the given point is within the stroke of the currently defined path.
6372 /// This operation can be done before rasterizing the current path.
6373 /// Group: picking_api
6374 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6375   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6376   int oldnpoints = ps.npoints;
6377   int oldnsegments = ps.nsegments;
6378   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6379   if (pp is null) return false; // oops
6380   scope(exit) {
6381     nvg__freePickPath(ps, pp);
6382     ps.npoints = oldnpoints;
6383     ps.nsegments = oldnsegments;
6384   }
6385   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6386 }
6387 
6388 
6389 nothrow @trusted @nogc {
6390 extern(C) {
6391   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6392   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6393 
6394   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6395     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6396   }
6397 }
6398 
6399 enum NVGPickEPS = 0.0001f;
6400 
6401 // Segment flags
6402 enum NVGSegmentFlags {
6403   Corner = 1,
6404   Bevel = 2,
6405   InnerBevel = 4,
6406   Cap = 8,
6407   Endcap = 16,
6408 }
6409 
6410 // Path flags
6411 enum NVGPathFlags : ushort {
6412   Fill = NVGPickKind.Fill,
6413   Stroke = NVGPickKind.Stroke,
6414   Scissor = 0x80,
6415 }
6416 
6417 struct NVGsegment {
6418   int firstPoint; // Index into NVGpickScene.points
6419   short type; // NVG_LINETO or NVG_BEZIERTO
6420   short flags; // Flags relate to the corner between the prev segment and this one.
6421   float[4] bounds;
6422   float[2] startDir; // Direction at t == 0
6423   float[2] endDir; // Direction at t == 1
6424   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6425 }
6426 
6427 struct NVGpickSubPath {
6428   short winding; // TODO: Merge to flag field
6429   bool closed; // TODO: Merge to flag field
6430 
6431   int firstSegment; // Index into NVGpickScene.segments
6432   int nsegments;
6433 
6434   float[4] bounds;
6435 
6436   NVGpickSubPath* next;
6437 }
6438 
6439 struct NVGpickPath {
6440   int id;
6441   short flags;
6442   short order;
6443   float strokeWidth;
6444   float miterLimit;
6445   short lineCap;
6446   short lineJoin;
6447   bool evenOddMode;
6448 
6449   float[4] bounds;
6450   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6451 
6452   NVGpickSubPath* subPaths;
6453   NVGpickPath* next;
6454   NVGpickPath* cellnext;
6455 }
6456 
6457 struct NVGpickScene {
6458   int npaths;
6459 
6460   NVGpickPath* paths; // Linked list of paths
6461   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6462   NVGpickPath* freePaths; // Linked list of free paths
6463 
6464   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6465 
6466   int width;
6467   int height;
6468 
6469   // Points for all path sub paths.
6470   float* points;
6471   int npoints;
6472   int cpoints;
6473 
6474   // Segments for all path sub paths
6475   NVGsegment* segments;
6476   int nsegments;
6477   int csegments;
6478 
6479   // Implicit quadtree
6480   float xdim; // Width / (1 << nlevels)
6481   float ydim; // Height / (1 << nlevels)
6482   int ncells; // Total number of cells in all levels
6483   int nlevels;
6484   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6485 
6486   // Temp storage for picking
6487   int cpicked;
6488   NVGpickPath** picked;
6489 }
6490 
6491 
6492 // bounds utilities
6493 void nvg__initBounds (ref float[4] bounds) {
6494   bounds.ptr[0] = bounds.ptr[1] = float.max;
6495   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6496 }
6497 
6498 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6499   npoints *= 2;
6500   for (int i = 0; i < npoints; i += 2) {
6501     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6502     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6503     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6504     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6505   }
6506 }
6507 
6508 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) {
6509   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6510   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6511   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6512   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6513 }
6514 
6515 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) {
6516   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6517   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6518   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6519   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6520 
6521   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6522   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6523 }
6524 
6525 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) {
6526   pragma(inline, true);
6527   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6528 }
6529 
6530 // building paths & sub paths
6531 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6532   import core.stdc.string : memcpy;
6533   if (ps.npoints+n > ps.cpoints) {
6534     import core.stdc.stdlib : realloc;
6535     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6536     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6537     if (points is null) assert(0, "NanoVega: out of memory");
6538     ps.points = points;
6539     ps.cpoints = cpoints;
6540   }
6541   int i = ps.npoints;
6542   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6543   ps.npoints += n;
6544   return i;
6545 }
6546 
6547 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6548   NVGsegment* seg = null;
6549   if (ps.nsegments == ps.csegments) {
6550     int csegments = 1+ps.csegments+(ps.csegments<<1);
6551     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6552     if (segments is null) assert(0, "NanoVega: out of memory");
6553     ps.segments = segments;
6554     ps.csegments = csegments;
6555   }
6556 
6557   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6558 
6559   seg = &ps.segments[ps.nsegments];
6560   ++ps.nsegments;
6561   seg.firstPoint = firstPoint;
6562   seg.type = cast(short)type;
6563   seg.flags = flags;
6564   ++psp.nsegments;
6565 
6566   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6567   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6568 }
6569 
6570 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6571   const(float)* points = &ps.points[seg.firstPoint*2];
6572   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6573   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6574   switch (seg.type) {
6575     case Command.LineTo:
6576       d.ptr[0] = x1-x0;
6577       d.ptr[1] = y1-y0;
6578       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6579       break;
6580     case Command.BezierTo:
6581       immutable float x2 = points[2*2+0];
6582       immutable float y2 = points[2*2+1];
6583       immutable float x3 = points[3*2+0];
6584       immutable float y3 = points[3*2+1];
6585 
6586       immutable float omt = 1.0f-t;
6587       immutable float omt2 = omt*omt;
6588       immutable float t2 = t*t;
6589 
6590       d.ptr[0] =
6591         3.0f*omt2*(x1-x0)+
6592         6.0f*omt*t*(x2-x1)+
6593         3.0f*t2*(x3-x2);
6594 
6595       d.ptr[1] =
6596         3.0f*omt2*(y1-y0)+
6597         6.0f*omt*t*(y2-y1)+
6598         3.0f*t2*(y3-y2);
6599 
6600       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6601       break;
6602     default:
6603       break;
6604   }
6605 }
6606 
6607 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6608   if (psp.firstSegment == -1) return;
6609   NVGsegment* segments = &ps.segments[psp.firstSegment];
6610   for (int s = 0; s < psp.nsegments; ++s) {
6611     NVGsegment* seg = &segments[s];
6612     const(float)* points = &ps.points[seg.firstPoint*2];
6613     if (seg.type == Command.LineTo) {
6614       nvg__initBounds(seg.bounds);
6615       nvg__expandBounds(seg.bounds, points, 2);
6616     } else {
6617       nvg__bezierBounds(points, seg.bounds);
6618     }
6619   }
6620 }
6621 
6622 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6623   if (psp.firstSegment == -1) return;
6624   immutable bool closed = psp.closed;
6625   const(float)* points = ps.points;
6626   NVGsegment* seg = null;
6627   NVGsegment* segments = &ps.segments[psp.firstSegment];
6628   int nsegments = psp.nsegments;
6629   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6630 
6631   int ns = 0; // nsupports
6632   float[32] supportingPoints = void;
6633   int firstPoint, lastPoint;
6634 
6635   if (!closed) {
6636     segments[0].flags |= NVGSegmentFlags.Cap;
6637     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6638   }
6639 
6640   for (int s = 0; s < nsegments; ++s) {
6641     seg = &segments[s];
6642     nvg__initBounds(seg.bounds);
6643 
6644     firstPoint = seg.firstPoint*2;
6645     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6646 
6647     ns = 0;
6648 
6649     // First two supporting points are either side of the start point
6650     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6651     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6652 
6653     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6654     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6655 
6656     // Second two supporting points are either side of the end point
6657     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6658     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6659 
6660     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6661     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6662 
6663     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6664       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6665       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6666 
6667       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6668 
6669       if (M2 > 0.000001f) {
6670         float scale = 1.0f/M2;
6671         if (scale > 600.0f) scale = 600.0f;
6672         seg.miterDir.ptr[0] *= scale;
6673         seg.miterDir.ptr[1] *= scale;
6674       }
6675 
6676       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6677 
6678       // Add an additional support at the corner on the other line
6679       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6680       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6681 
6682       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6683         // Set a corner as beveled if the join type is bevel or mitered and
6684         // miterLimit is hit.
6685         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6686           seg.flags |= NVGSegmentFlags.Bevel;
6687         } else {
6688           // Corner is mitered - add miter point as a support
6689           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6690           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6691         }
6692       } else if (lineJoin == NVGLineCap.Round) {
6693         // ... and at the midpoint of the corner arc
6694         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6695         nvg__normalize(&vertexN[0], &vertexN[1]);
6696 
6697         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6698         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6699       }
6700     }
6701 
6702     if (seg.flags&NVGSegmentFlags.Cap) {
6703       switch (lineCap) {
6704         case NVGLineCap.Butt:
6705           // supports for butt already added
6706           break;
6707         case NVGLineCap.Square:
6708           // square cap supports are just the original two supports moved out along the direction
6709           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6710           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6711           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6712           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6713           break;
6714         case NVGLineCap.Round:
6715           // add one additional support for the round cap along the dir
6716           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6717           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6718           break;
6719         default:
6720           break;
6721       }
6722     }
6723 
6724     if (seg.flags&NVGSegmentFlags.Endcap) {
6725       // end supporting points, either side of line
6726       int end = 4;
6727       switch(lineCap) {
6728         case NVGLineCap.Butt:
6729           // supports for butt already added
6730           break;
6731         case NVGLineCap.Square:
6732           // square cap supports are just the original two supports moved out along the direction
6733           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6734           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6735           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6736           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6737           break;
6738         case NVGLineCap.Round:
6739           // add one additional support for the round cap along the dir
6740           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6741           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6742           break;
6743         default:
6744           break;
6745       }
6746     }
6747 
6748     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6749 
6750     prevseg = seg;
6751   }
6752 }
6753 
6754 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6755   NVGpickScene* ps = nvg__pickSceneGet(context);
6756   if (ps is null) return null;
6757 
6758   int i = 0;
6759 
6760   int ncommands = cast(int)acommands.length;
6761   const(float)* commands = acommands.ptr;
6762 
6763   NVGpickPath* pp = null;
6764   NVGpickSubPath* psp = null;
6765   float[2] start = void;
6766   int firstPoint;
6767 
6768   //bool hasHoles = false;
6769   NVGpickSubPath* prev = null;
6770 
6771   float[8] points = void;
6772   float[2] inflections = void;
6773   int ninflections = 0;
6774 
6775   NVGstate* state = nvg__getState(context);
6776   float[4] totalBounds = void;
6777   NVGsegment* segments = null;
6778   const(NVGsegment)* seg = null;
6779   NVGpickSubPath *curpsp;
6780 
6781   pp = nvg__allocPickPath(ps);
6782   if (pp is null) return null;
6783 
6784   pp.id = id;
6785 
6786   bool hasPoints = false;
6787 
6788   void closeIt () {
6789     if (psp is null || !hasPoints) return;
6790     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6791       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6792       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6793     }
6794     psp.closed = true;
6795   }
6796 
6797   while (i < ncommands) {
6798     int cmd = cast(int)commands[i++];
6799     switch (cmd) {
6800       case Command.MoveTo: // one coordinate pair
6801         const(float)* tfxy = commands+i;
6802         i += 2;
6803 
6804         // new starting point
6805         start.ptr[0..2] = tfxy[0..2];
6806 
6807         // start a new path for each sub path to handle sub paths that intersect other sub paths
6808         prev = psp;
6809         psp = nvg__allocPickSubPath(ps);
6810         if (psp is null) { psp = prev; break; }
6811         psp.firstSegment = -1;
6812         psp.winding = NVGSolidity.Solid;
6813         psp.next = prev;
6814 
6815         nvg__pickSceneAddPoints(ps, tfxy, 1);
6816         hasPoints = true;
6817         break;
6818       case Command.LineTo: // one coordinate pair
6819         const(float)* tfxy = commands+i;
6820         i += 2;
6821         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6822         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6823         hasPoints = true;
6824         break;
6825       case Command.BezierTo: // three coordinate pairs
6826         const(float)* tfxy = commands+i;
6827         i += 3*2;
6828 
6829         // Split the curve at it's dx==0 or dy==0 inflection points.
6830         // Thus:
6831         //    A horizontal line only ever interects the curves once.
6832         //  and
6833         //    Finding the closest point on any curve converges more reliably.
6834 
6835         // NOTE: We could just split on dy==0 here.
6836 
6837         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6838         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6839 
6840         ninflections = 0;
6841         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6842         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6843 
6844         if (ninflections) {
6845           float previnfl = 0;
6846           float[8] pointsA = void, pointsB = void;
6847 
6848           nvg__smallsort(inflections.ptr, ninflections);
6849 
6850           for (int infl = 0; infl < ninflections; ++infl) {
6851             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6852 
6853             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6854 
6855             previnfl = inflections.ptr[infl];
6856 
6857             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6858 
6859             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6860             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6861 
6862             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6863           }
6864 
6865           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6866           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6867         } else {
6868           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6869           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6870         }
6871         hasPoints = true;
6872         break;
6873       case Command.Close:
6874         closeIt();
6875         break;
6876       case Command.Winding:
6877         psp.winding = cast(short)cast(int)commands[i];
6878         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6879         i += 1;
6880         break;
6881       default:
6882         break;
6883     }
6884   }
6885 
6886   // force-close filled paths
6887   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6888 
6889   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6890   pp.subPaths = psp;
6891   pp.strokeWidth = state.strokeWidth*0.5f;
6892   pp.miterLimit = state.miterLimit;
6893   pp.lineCap = cast(short)state.lineCap;
6894   pp.lineJoin = cast(short)state.lineJoin;
6895   pp.evenOddMode = nvg__getState(context).evenOddMode;
6896 
6897   nvg__initBounds(totalBounds);
6898 
6899   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6900     if (forStroke) {
6901       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6902     } else {
6903       nvg__pickSubPathAddFillSupports(ps, curpsp);
6904     }
6905 
6906     if (curpsp.firstSegment == -1) continue;
6907     segments = &ps.segments[curpsp.firstSegment];
6908     nvg__initBounds(curpsp.bounds);
6909     for (int s = 0; s < curpsp.nsegments; ++s) {
6910       seg = &segments[s];
6911       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6912       nvg__unionBounds(curpsp.bounds, seg.bounds);
6913     }
6914 
6915     nvg__unionBounds(totalBounds, curpsp.bounds);
6916   }
6917 
6918   // Store the scissor rect if present.
6919   if (state.scissor.extent.ptr[0] != -1.0f) {
6920     // Use points storage to store the scissor data
6921     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6922     float* scissor = &ps.points[pp.scissor*2];
6923 
6924     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6925     scissor[0..6] = state.scissor.xform.mat[];
6926     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6927 
6928     pp.flags |= NVGPathFlags.Scissor;
6929   }
6930 
6931   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6932 
6933   return pp;
6934 }
6935 
6936 
6937 // Struct management
6938 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6939   NVGpickPath* pp = ps.freePaths;
6940   if (pp !is null) {
6941     ps.freePaths = pp.next;
6942   } else {
6943     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6944   }
6945   memset(pp, 0, NVGpickPath.sizeof);
6946   return pp;
6947 }
6948 
6949 // Put a pick path and any sub paths (back) to the free lists.
6950 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6951   // Add all sub paths to the sub path free list.
6952   // Finds the end of the path sub paths, links that to the current
6953   // sub path free list head and replaces the head ptr with the
6954   // head path sub path entry.
6955   NVGpickSubPath* psp = null;
6956   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6957 
6958   if (psp) {
6959     psp.next = ps.freeSubPaths;
6960     ps.freeSubPaths = pp.subPaths;
6961   }
6962   pp.subPaths = null;
6963 
6964   // Add the path to the path freelist
6965   pp.next = ps.freePaths;
6966   ps.freePaths = pp;
6967   if (pp.next is null) ps.lastPath = pp;
6968 }
6969 
6970 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6971   NVGpickSubPath* psp = ps.freeSubPaths;
6972   if (psp !is null) {
6973     ps.freeSubPaths = psp.next;
6974   } else {
6975     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6976     if (psp is null) return null;
6977   }
6978   memset(psp, 0, NVGpickSubPath.sizeof);
6979   return psp;
6980 }
6981 
6982 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6983   psp.next = ps.freeSubPaths;
6984   ps.freeSubPaths = psp;
6985 }
6986 
6987 NVGpickScene* nvg__allocPickScene () {
6988   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6989   if (ps is null) return null;
6990   memset(ps, 0, NVGpickScene.sizeof);
6991   ps.nlevels = 5;
6992   return ps;
6993 }
6994 
6995 void nvg__deletePickScene (NVGpickScene* ps) {
6996   NVGpickPath* pp;
6997   NVGpickSubPath* psp;
6998 
6999   // Add all paths (and thus sub paths) to the free list(s).
7000   while (ps.paths !is null) {
7001     pp = ps.paths.next;
7002     nvg__freePickPath(ps, ps.paths);
7003     ps.paths = pp;
7004   }
7005 
7006   // Delete all paths
7007   while (ps.freePaths !is null) {
7008     pp = ps.freePaths;
7009     ps.freePaths = pp.next;
7010     while (pp.subPaths !is null) {
7011       psp = pp.subPaths;
7012       pp.subPaths = psp.next;
7013       free(psp);
7014     }
7015     free(pp);
7016   }
7017 
7018   // Delete all sub paths
7019   while (ps.freeSubPaths !is null) {
7020     psp = ps.freeSubPaths.next;
7021     free(ps.freeSubPaths);
7022     ps.freeSubPaths = psp;
7023   }
7024 
7025   ps.npoints = 0;
7026   ps.nsegments = 0;
7027 
7028   if (ps.levels !is null) {
7029     free(ps.levels[0]);
7030     free(ps.levels);
7031   }
7032 
7033   if (ps.picked !is null) free(ps.picked);
7034   if (ps.points !is null) free(ps.points);
7035   if (ps.segments !is null) free(ps.segments);
7036 
7037   free(ps);
7038 }
7039 
7040 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7041   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7042   return ctx.pickScene;
7043 }
7044 
7045 
7046 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7047 // points is 4 points (8 floats)
7048 // lvl1 is 3 points (6 floats)
7049 // lvl2 is 2 points (4 floats)
7050 // lvl3 is 1 point (2 floats)
7051 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7052   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7053   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7054 
7055   // Level 1
7056   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7057   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7058 
7059   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7060   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7061 
7062   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7063   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7064 
7065   // Level 2
7066   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7067   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7068 
7069   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7070   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7071 
7072   // Level 3
7073   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7074   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7075 }
7076 
7077 // Calculates a point on a bezier at point t.
7078 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7079   immutable float omt = 1-t;
7080   immutable float omt3 = omt*omt*omt;
7081   immutable float omt2 = omt*omt;
7082   immutable float t3 = t*t*t;
7083   immutable float t2 = t*t;
7084 
7085   tpoint.ptr[0] =
7086     points[0]*omt3+
7087     points[2]*3.0f*omt2*t+
7088     points[4]*3.0f*omt*t2+
7089     points[6]*t3;
7090 
7091   tpoint.ptr[1] =
7092     points[1]*omt3+
7093     points[3]*3.0f*omt2*t+
7094     points[5]*3.0f*omt*t2+
7095     points[7]*t3;
7096 }
7097 
7098 // Splits a cubic bezier curve into two parts at point t.
7099 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7100   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7101   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7102 
7103   float[6] lvl1 = void;
7104   float[4] lvl2 = void;
7105   float[2] lvl3 = void;
7106 
7107   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7108 
7109   // First half
7110   pointsA[x0] = points[x0];
7111   pointsA[y0] = points[y0];
7112 
7113   pointsA[x1] = lvl1.ptr[x0];
7114   pointsA[y1] = lvl1.ptr[y0];
7115 
7116   pointsA[x2] = lvl2.ptr[x0];
7117   pointsA[y2] = lvl2.ptr[y0];
7118 
7119   pointsA[x3] = lvl3.ptr[x0];
7120   pointsA[y3] = lvl3.ptr[y0];
7121 
7122   // Second half
7123   pointsB[x0] = lvl3.ptr[x0];
7124   pointsB[y0] = lvl3.ptr[y0];
7125 
7126   pointsB[x1] = lvl2.ptr[x1];
7127   pointsB[y1] = lvl2.ptr[y1];
7128 
7129   pointsB[x2] = lvl1.ptr[x2];
7130   pointsB[y2] = lvl1.ptr[y2];
7131 
7132   pointsB[x3] = points[x3];
7133   pointsB[y3] = points[y3];
7134 }
7135 
7136 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7137 // Appends any found inflection points to the array inflections and increments *ninflections.
7138 // So finds the parameters where dx/dt or dy/dt is 0
7139 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7140   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7141   float[2] t = void;
7142   int nvalid = *ninflections;
7143 
7144   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7145   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7146   immutable float c = 3.0f*( v1-v0 );
7147 
7148   float d = b*b-4.0f*a*c;
7149   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7150     // Zero or one root
7151     t.ptr[0] = -b/2.0f*a;
7152     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7153       inflections[nvalid] = t.ptr[0];
7154       ++nvalid;
7155     }
7156   } else if (d > NVGPickEPS) {
7157     // zero, one or two roots
7158     d = nvg__sqrtf(d);
7159 
7160     t.ptr[0] = (-b+d)/(2.0f*a);
7161     t.ptr[1] = (-b-d)/(2.0f*a);
7162 
7163     for (int i = 0; i < 2; ++i) {
7164       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7165         inflections[nvalid] = t.ptr[i];
7166         ++nvalid;
7167       }
7168     }
7169   } else {
7170     // zero roots
7171   }
7172 
7173   *ninflections = nvalid;
7174 }
7175 
7176 // Sort a small number of floats in ascending order (0 < n < 6)
7177 void nvg__smallsort (float* values, int n) {
7178   bool bSwapped = true;
7179   for (int j = 0; j < n-1 && bSwapped; ++j) {
7180     bSwapped = false;
7181     for (int i = 0; i < n-1; ++i) {
7182       if (values[i] > values[i+1]) {
7183         auto tmp = values[i];
7184         values[i] = values[i+1];
7185         values[i+1] = tmp;
7186       }
7187     }
7188   }
7189 }
7190 
7191 // Calculates the bounding rect of a given cubic bezier curve.
7192 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7193   float[4] inflections = void;
7194   int ninflections = 0;
7195   float[2] tpoint = void;
7196 
7197   nvg__initBounds(bounds);
7198 
7199   // Include start and end points in bounds
7200   nvg__expandBounds(bounds, &points[0], 1);
7201   nvg__expandBounds(bounds, &points[6], 1);
7202 
7203   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7204 
7205   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7206   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7207 
7208   foreach (immutable int i; 0..ninflections) {
7209     nvg__bezierEval(points, inflections[i], tpoint);
7210     nvg__expandBounds(bounds, tpoint.ptr, 1);
7211   }
7212 }
7213 
7214 // Checks to see if a line originating from x,y along the +ve x axis
7215 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7216 // Returns `true` on intersection.
7217 // Horizontal lines are never hit.
7218 bool nvg__intersectLine (const(float)* points, float x, float y) {
7219   immutable float x1 = points[0];
7220   immutable float y1 = points[1];
7221   immutable float x2 = points[2];
7222   immutable float y2 = points[3];
7223   immutable float d = y2-y1;
7224   if (d > NVGPickEPS || d < -NVGPickEPS) {
7225     immutable float s = (x2-x1)/d;
7226     immutable float lineX = x1+(y-y1)*s;
7227     return (lineX > x);
7228   } else {
7229     return false;
7230   }
7231 }
7232 
7233 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7234 // It is assumed that the line originates from within the bounding box of
7235 // the bezier and that the curve has no dy=0 inflection points.
7236 // Returns the number of intersections found (which is either 1 or 0).
7237 int nvg__intersectBezier (const(float)* points, float x, float y) {
7238   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7239   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7240 
7241   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7242 
7243   // Initial t guess
7244   float t = void;
7245        if (y3 != y0) t = (y-y0)/(y3-y0);
7246   else if (x3 != x0) t = (x-x0)/(x3-x0);
7247   else t = 0.5f;
7248 
7249   // A few Newton iterations
7250   for (int iter = 0; iter < 6; ++iter) {
7251     immutable float omt = 1-t;
7252     immutable float omt2 = omt*omt;
7253     immutable float t2 = t*t;
7254     immutable float omt3 = omt2*omt;
7255     immutable float t3 = t2*t;
7256 
7257     immutable float ty = y0*omt3 +
7258       y1*3.0f*omt2*t +
7259       y2*3.0f*omt*t2 +
7260       y3*t3;
7261 
7262     // Newton iteration
7263     immutable float dty = 3.0f*omt2*(y1-y0) +
7264       6.0f*omt*t*(y2-y1) +
7265       3.0f*t2*(y3-y2);
7266 
7267     // dty will never == 0 since:
7268     //  Either omt, omt2 are zero OR t2 is zero
7269     //  y0 != y1 != y2 != y3 (checked above)
7270     t = t-(ty-y)/dty;
7271   }
7272 
7273   {
7274     immutable float omt = 1-t;
7275     immutable float omt2 = omt*omt;
7276     immutable float t2 = t*t;
7277     immutable float omt3 = omt2*omt;
7278     immutable float t3 = t2*t;
7279 
7280     immutable float tx =
7281       x0*omt3+
7282       x1*3.0f*omt2*t+
7283       x2*3.0f*omt*t2+
7284       x3*t3;
7285 
7286     return (tx > x ? 1 : 0);
7287   }
7288 }
7289 
7290 // Finds the closest point on a line to a given point
7291 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7292   immutable float x1 = points[0];
7293   immutable float y1 = points[1];
7294   immutable float x2 = points[2];
7295   immutable float y2 = points[3];
7296   immutable float pqx = x2-x1;
7297   immutable float pqz = y2-y1;
7298   immutable float dx = x-x1;
7299   immutable float dz = y-y1;
7300   immutable float d = pqx*pqx+pqz*pqz;
7301   float t = pqx*dx+pqz*dz;
7302   if (d > 0) t /= d;
7303   if (t < 0) t = 0; else if (t > 1) t = 1;
7304   closest[0] = x1+t*pqx;
7305   closest[1] = y1+t*pqz;
7306   *ot = t;
7307 }
7308 
7309 // Finds the closest point on a curve for a given point (x,y).
7310 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7311 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7312   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7313   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7314 
7315   // This assumes that the curve has no dy=0 inflection points.
7316 
7317   // Initial t guess
7318   float t = 0.5f;
7319 
7320   // A few Newton iterations
7321   for (int iter = 0; iter < 6; ++iter) {
7322     immutable float omt = 1-t;
7323     immutable float omt2 = omt*omt;
7324     immutable float t2 = t*t;
7325     immutable float omt3 = omt2*omt;
7326     immutable float t3 = t2*t;
7327 
7328     immutable float ty =
7329       y0*omt3+
7330       y1*3.0f*omt2*t+
7331       y2*3.0f*omt*t2+
7332       y3*t3;
7333 
7334     immutable float tx =
7335       x0*omt3+
7336       x1*3.0f*omt2*t+
7337       x2*3.0f*omt*t2+
7338       x3*t3;
7339 
7340     // Newton iteration
7341     immutable float dty =
7342       3.0f*omt2*(y1-y0)+
7343       6.0f*omt*t*(y2-y1)+
7344       3.0f*t2*(y3-y2);
7345 
7346     immutable float ddty =
7347       6.0f*omt*(y2-2.0f*y1+y0)+
7348       6.0f*t*(y3-2.0f*y2+y1);
7349 
7350     immutable float dtx =
7351       3.0f*omt2*(x1-x0)+
7352       6.0f*omt*t*(x2-x1)+
7353       3.0f*t2*(x3-x2);
7354 
7355     immutable float ddtx =
7356       6.0f*omt*(x2-2.0f*x1+x0)+
7357       6.0f*t*(x3-2.0f*x2+x1);
7358 
7359     immutable float errorx = tx-x;
7360     immutable float errory = ty-y;
7361 
7362     immutable float n = errorx*dtx+errory*dty;
7363     if (n == 0) break;
7364 
7365     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7366     if (d != 0) t = t-n/d; else break;
7367   }
7368 
7369   t = nvg__max(0, nvg__min(1.0, t));
7370   *ot = t;
7371   {
7372     immutable float omt = 1-t;
7373     immutable float omt2 = omt*omt;
7374     immutable float t2 = t*t;
7375     immutable float omt3 = omt2*omt;
7376     immutable float t3 = t2*t;
7377 
7378     immutable float ty =
7379       y0*omt3+
7380       y1*3.0f*omt2*t+
7381       y2*3.0f*omt*t2+
7382       y3*t3;
7383 
7384     immutable float tx =
7385       x0*omt3+
7386       x1*3.0f*omt2*t+
7387       x2*3.0f*omt*t2+
7388       x3*t3;
7389 
7390     closest[0] = tx;
7391     closest[1] = ty;
7392   }
7393 }
7394 
7395 // Returns:
7396 //  1  If (x,y) is contained by the stroke of the path
7397 //  0  If (x,y) is not contained by the path.
7398 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7399   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7400   if (psp.firstSegment == -1) return 0;
7401 
7402   float[2] closest = void;
7403   float[2] d = void;
7404   float t = void;
7405 
7406   // trace a line from x,y out along the positive x axis and count the number of intersections
7407   int nsegments = psp.nsegments;
7408   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7409   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7410   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7411 
7412   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7413     if (nvg__pointInBounds(x, y, seg.bounds)) {
7414       // Line potentially hits stroke.
7415       switch (seg.type) {
7416         case Command.LineTo:
7417           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7418           break;
7419         case Command.BezierTo:
7420           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7421           break;
7422         default:
7423           continue;
7424       }
7425 
7426       d.ptr[0] = x-closest.ptr[0];
7427       d.ptr[1] = y-closest.ptr[1];
7428 
7429       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7430           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7431           (lineJoin == NVGLineCap.Round))
7432       {
7433         // Closest point is in the middle of the line/curve, at a rounded join/cap
7434         // or at a smooth join
7435         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7436         if (distSqd < strokeWidthSqd) return 1;
7437       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7438                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7439         switch (lineCap) {
7440           case NVGLineCap.Butt:
7441             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7442             immutable float dirD = (t < NVGPickEPS ?
7443               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7444                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7445             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7446             break;
7447           case NVGLineCap.Square:
7448             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7449             break;
7450           case NVGLineCap.Round:
7451             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7452             if (distSqd < strokeWidthSqd) return 1;
7453             break;
7454           default:
7455             break;
7456         }
7457       } else if (seg.flags&NVGSegmentFlags.Corner) {
7458         // Closest point is at a corner
7459         const(NVGsegment)* seg0, seg1;
7460 
7461         if (t < NVGPickEPS) {
7462           seg0 = prevseg;
7463           seg1 = seg;
7464         } else {
7465           seg0 = seg;
7466           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7467         }
7468 
7469         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7470           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7471           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7472           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7473         } else {
7474           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7475           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7476           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7477         }
7478       }
7479     }
7480   }
7481 
7482   return 0;
7483 }
7484 
7485 // Returns:
7486 //   1  If (x,y) is contained by the path and the path is solid.
7487 //  -1  If (x,y) is contained by the path and the path is a hole.
7488 //   0  If (x,y) is not contained by the path.
7489 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7490   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7491   if (psp.firstSegment == -1) return 0;
7492 
7493   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7494   int nsegments = psp.nsegments;
7495   int nintersections = 0;
7496 
7497   // trace a line from x,y out along the positive x axis and count the number of intersections
7498   for (int s = 0; s < nsegments; ++s, ++seg) {
7499     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7500         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7501         seg.bounds.ptr[2] > x)
7502     {
7503       // Line hits the box.
7504       switch (seg.type) {
7505         case Command.LineTo:
7506           if (seg.bounds.ptr[0] > x) {
7507             // line originates outside the box
7508             ++nintersections;
7509           } else {
7510             // line originates inside the box
7511             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7512           }
7513           break;
7514         case Command.BezierTo:
7515           if (seg.bounds.ptr[0] > x) {
7516             // line originates outside the box
7517             ++nintersections;
7518           } else {
7519             // line originates inside the box
7520             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7521           }
7522           break;
7523         default:
7524           break;
7525       }
7526     }
7527   }
7528 
7529   if (evenOddMode) {
7530     return nintersections;
7531   } else {
7532     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7533   }
7534 }
7535 
7536 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7537   int pickCount = 0;
7538   const(NVGpickSubPath)* psp = pp.subPaths;
7539   while (psp !is null) {
7540     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7541     psp = psp.next;
7542   }
7543   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7544 }
7545 
7546 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7547   const(NVGpickSubPath)* psp = pp.subPaths;
7548   while (psp !is null) {
7549     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7550     psp = psp.next;
7551   }
7552   return false;
7553 }
7554 
7555 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7556   if (nvg__pointInBounds(x, y, pp.bounds)) {
7557     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7558     if (pp.flags&NVGPathFlags.Scissor) {
7559       const(float)* scissor = &ps.points[pp.scissor*2];
7560       // untransform scissor translation
7561       float stx = void, sty = void;
7562       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7563       immutable float rx = x-stx;
7564       immutable float ry = y-sty;
7565       //{ 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]); }
7566       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7567           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7568       {
7569         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7570         return false;
7571       }
7572     }
7573     return true;
7574   }
7575   return false;
7576 }
7577 
7578 int nvg__countBitsUsed (uint v) pure {
7579   pragma(inline, true);
7580   import core.bitop : bsr;
7581   return (v != 0 ? bsr(v)+1 : 0);
7582 }
7583 
7584 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7585   if (ps is null || pp is null) return;
7586 
7587   int[4] cellbounds;
7588   int base = ps.nlevels-1;
7589   int level;
7590   int levelwidth;
7591   int levelshift;
7592   int levelx;
7593   int levely;
7594   NVGpickPath** cell = null;
7595 
7596   // Bit tricks for inserting into an implicit quadtree.
7597 
7598   // Calc bounds of path in cells at the lowest level
7599   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7600   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7601   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7602   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7603 
7604   // Find which bits differ between the min/max x/y coords
7605   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7606   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7607 
7608   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7609   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7610   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7611   if (level < 0) level = 0;
7612   //{ 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]); }
7613   //level = 0;
7614 
7615   // Find the correct cell in the chosen level, clamping to the edges.
7616   levelwidth = 1<<level;
7617   levelshift = (ps.nlevels-level)-1;
7618   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7619   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7620 
7621   // Insert the path into the linked list at that cell.
7622   cell = &ps.levels[level][levely*levelwidth+levelx];
7623 
7624   pp.cellnext = *cell;
7625   *cell = pp;
7626 
7627   if (ps.paths is null) ps.lastPath = pp;
7628   pp.next = ps.paths;
7629   ps.paths = pp;
7630 
7631   // Store the order (depth) of the path for picking ops.
7632   pp.order = cast(short)ps.npaths;
7633   ++ps.npaths;
7634 }
7635 
7636 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7637   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7638 
7639   //NVG_PICK_DEBUG_NEWFRAME();
7640 
7641   // Return all paths & sub paths from last frame to the free list
7642   while (ps.paths !is null) {
7643     NVGpickPath* pp = ps.paths.next;
7644     nvg__freePickPath(ps, ps.paths);
7645     ps.paths = pp;
7646   }
7647 
7648   ps.paths = null;
7649   ps.npaths = 0;
7650 
7651   // Store the screen metrics for the quadtree
7652   ps.width = width;
7653   ps.height = height;
7654 
7655   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7656   ps.xdim = cast(float)width/lowestSubDiv;
7657   ps.ydim = cast(float)height/lowestSubDiv;
7658 
7659   // Allocate the quadtree if required.
7660   if (ps.levels is null) {
7661     int ncells = 1;
7662 
7663     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7664     for (int l = 0; l < ps.nlevels; ++l) {
7665       int leveldim = 1<<l;
7666       ncells += leveldim*leveldim;
7667     }
7668 
7669     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7670 
7671     int cell = 1;
7672     for (int l = 1; l < ps.nlevels; ++l) {
7673       ps.levels[l] = &ps.levels[0][cell];
7674       int leveldim = 1<<l;
7675       cell += leveldim*leveldim;
7676     }
7677 
7678     ps.ncells = ncells;
7679   }
7680   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7681 
7682   // Allocate temporary storage for nvgHitTestAll results if required.
7683   if (ps.picked is null) {
7684     ps.cpicked = 16;
7685     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7686   }
7687 
7688   ps.npoints = 0;
7689   ps.nsegments = 0;
7690 }
7691 } // nothrow @trusted @nogc
7692 
7693 
7694 /// Return outline of the current path. Returned outline is not flattened.
7695 /// Group: paths
7696 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7697   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7698 
7699   auto res = NVGPathOutline.createNew();
7700 
7701   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7702   int ncommands = cast(int)acommands.length;
7703   const(float)* commands = acommands.ptr;
7704 
7705   float cx = 0, cy = 0;
7706   float[2] start = void;
7707   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7708   float[8] bcp = void; // bezier curve points; used to calculate bounds
7709 
7710   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7711     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7712     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7713     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7714     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7715   }
7716 
7717   bool hasPoints = false;
7718 
7719   void closeIt () nothrow @trusted @nogc {
7720     if (!hasPoints) return;
7721     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7722       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7723       res.ds.putArgs(start[]);
7724       cx = start.ptr[0];
7725       cy = start.ptr[1];
7726       addToBounds(cx, cy);
7727     }
7728   }
7729 
7730   int i = 0;
7731   while (i < ncommands) {
7732     int cmd = cast(int)commands[i++];
7733     switch (cmd) {
7734       case Command.MoveTo: // one coordinate pair
7735         const(float)* tfxy = commands+i;
7736         i += 2;
7737         // add command
7738         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7739         res.ds.putArgs(tfxy[0..2]);
7740         // new starting point
7741         start.ptr[0..2] = tfxy[0..2];
7742         cx = tfxy[0];
7743         cy = tfxy[0];
7744         addToBounds(cx, cy);
7745         hasPoints = true;
7746         break;
7747       case Command.LineTo: // one coordinate pair
7748         const(float)* tfxy = commands+i;
7749         i += 2;
7750         // add command
7751         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7752         res.ds.putArgs(tfxy[0..2]);
7753         cx = tfxy[0];
7754         cy = tfxy[0];
7755         addToBounds(cx, cy);
7756         hasPoints = true;
7757         break;
7758       case Command.BezierTo: // three coordinate pairs
7759         const(float)* tfxy = commands+i;
7760         i += 3*2;
7761         // add command
7762         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7763         res.ds.putArgs(tfxy[0..6]);
7764         // bounds
7765         bcp.ptr[0] = cx;
7766         bcp.ptr[1] = cy;
7767         bcp.ptr[2..8] = tfxy[0..6];
7768         nvg__bezierBounds(bcp.ptr, totalBounds);
7769         cx = tfxy[4];
7770         cy = tfxy[5];
7771         hasPoints = true;
7772         break;
7773       case Command.Close:
7774         closeIt();
7775         hasPoints = false;
7776         break;
7777       case Command.Winding:
7778         //psp.winding = cast(short)cast(int)commands[i];
7779         i += 1;
7780         break;
7781       default:
7782         break;
7783     }
7784   }
7785 
7786   res.ds.bounds[] = totalBounds[];
7787   return res;
7788 }
7789 
7790 
7791 // ////////////////////////////////////////////////////////////////////////// //
7792 // Text
7793 
7794 /** Creates font by loading it from the disk from specified file name.
7795  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7796  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7797  *
7798  * On POSIX systems it is possible to use fontconfig font names too.
7799  * `:noaa` in font path is still allowed, but it must be the last option.
7800  *
7801  * Group: text_api
7802  */
7803 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7804   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7805 }
7806 
7807 /** Creates font by loading it from the specified memory chunk.
7808  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7809  * Won't free data on error.
7810  *
7811  * Group: text_api
7812  */
7813 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7814   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7815 }
7816 
7817 /// Add fonts from another context.
7818 /// This is more effective than reloading fonts, 'cause font data will be shared.
7819 /// Group: text_api
7820 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7821   if (ctx is null || source is null) return;
7822   ctx.fs.addFontsFrom(source.fs);
7823 }
7824 
7825 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7826 /// Group: text_api
7827 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7828   pragma(inline, true);
7829   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7830 }
7831 
7832 /// Sets the font size of current text style.
7833 /// Group: text_api
7834 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7835   pragma(inline, true);
7836   nvg__getState(ctx).fontSize = size;
7837 }
7838 
7839 /// Gets the font size of current text style.
7840 /// Group: text_api
7841 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7842   pragma(inline, true);
7843   return nvg__getState(ctx).fontSize;
7844 }
7845 
7846 /// Sets the blur of current text style.
7847 /// Group: text_api
7848 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7849   pragma(inline, true);
7850   nvg__getState(ctx).fontBlur = blur;
7851 }
7852 
7853 /// Gets the blur of current text style.
7854 /// Group: text_api
7855 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7856   pragma(inline, true);
7857   return nvg__getState(ctx).fontBlur;
7858 }
7859 
7860 /// Sets the letter spacing of current text style.
7861 /// Group: text_api
7862 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7863   pragma(inline, true);
7864   nvg__getState(ctx).letterSpacing = spacing;
7865 }
7866 
7867 /// Gets the letter spacing of current text style.
7868 /// Group: text_api
7869 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7870   pragma(inline, true);
7871   return nvg__getState(ctx).letterSpacing;
7872 }
7873 
7874 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7875 /// Group: text_api
7876 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7877   pragma(inline, true);
7878   nvg__getState(ctx).lineHeight = lineHeight;
7879 }
7880 
7881 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7882 /// Group: text_api
7883 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7884   pragma(inline, true);
7885   return nvg__getState(ctx).lineHeight;
7886 }
7887 
7888 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7889 /// Group: text_api
7890 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7891   pragma(inline, true);
7892   nvg__getState(ctx).textAlign = talign;
7893 }
7894 
7895 /// Ditto.
7896 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7897   pragma(inline, true);
7898   nvg__getState(ctx).textAlign.horizontal = h;
7899 }
7900 
7901 /// Ditto.
7902 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7903   pragma(inline, true);
7904   nvg__getState(ctx).textAlign.vertical = v;
7905 }
7906 
7907 /// Ditto.
7908 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7909   pragma(inline, true);
7910   nvg__getState(ctx).textAlign.reset(h, v);
7911 }
7912 
7913 /// Ditto.
7914 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7915   pragma(inline, true);
7916   nvg__getState(ctx).textAlign.reset(h, v);
7917 }
7918 
7919 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7920 /// Group: text_api
7921 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7922   pragma(inline, true);
7923   return nvg__getState(ctx).textAlign;
7924 }
7925 
7926 /// Sets the font face based on specified id of current text style.
7927 /// Group: text_api
7928 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7929   pragma(inline, true);
7930   nvg__getState(ctx).fontId = font;
7931 }
7932 
7933 /// Gets the font face based on specified id of current text style.
7934 /// Group: text_api
7935 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7936   pragma(inline, true);
7937   return nvg__getState(ctx).fontId;
7938 }
7939 
7940 /** Sets the font face based on specified name of current text style.
7941  *
7942  * The underlying implementation is using O(1) data structure to lookup
7943  * font names, so you probably should use this function instead of [fontFaceId]
7944  * to make your code more robust and less error-prone.
7945  *
7946  * Group: text_api
7947  */
7948 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7949   pragma(inline, true);
7950   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7951 }
7952 
7953 static if (is(typeof(&fons__nvg__toPath))) {
7954   public enum NanoVegaHasCharToPath = true; ///
7955 } else {
7956   public enum NanoVegaHasCharToPath = false; ///
7957 }
7958 
7959 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7960 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7961 /// Returns `false` if there is no such glyph, or current font is not scalable.
7962 /// Group: text_api
7963 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7964   NVGstate* state = nvg__getState(ctx);
7965   ctx.fs.fontId = state.fontId;
7966   return ctx.fs.toPath(ctx, dch, bounds);
7967 }
7968 
7969 static if (is(typeof(&fons__nvg__bounds))) {
7970   public enum NanoVegaHasCharPathBounds = true; ///
7971 } else {
7972   public enum NanoVegaHasCharPathBounds = false; ///
7973 }
7974 
7975 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7976 /// The glyph is not scaled in any way.
7977 /// Returns `false` if there is no such glyph, or current font is not scalable.
7978 /// Group: text_api
7979 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7980   NVGstate* state = nvg__getState(ctx);
7981   ctx.fs.fontId = state.fontId;
7982   return ctx.fs.getPathBounds(dch, bounds);
7983 }
7984 
7985 /** [charOutline] will return [NVGPathOutline].
7986 
7987  some usage samples:
7988 
7989  ---
7990     float[4] bounds = void;
7991 
7992     nvg.scale(0.5, 0.5);
7993     nvg.translate(500, 800);
7994     nvg.evenOddFill;
7995 
7996     nvg.newPath();
7997     nvg.charToPath('&', bounds[]);
7998     conwriteln(bounds[]);
7999     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
8000     nvg.strokeColor(NVGColor("#0f0"));
8001     nvg.strokeWidth = 3;
8002     nvg.fill();
8003     nvg.stroke();
8004     // glyph bounds
8005     nvg.newPath();
8006     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8007     nvg.strokeColor(NVGColor("#00f"));
8008     nvg.stroke();
8009 
8010     nvg.newPath();
8011     nvg.charToPath('g', bounds[]);
8012     conwriteln(bounds[]);
8013     nvg.fill();
8014     nvg.strokeColor(NVGColor("#0f0"));
8015     nvg.stroke();
8016     // glyph bounds
8017     nvg.newPath();
8018     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8019     nvg.strokeColor(NVGColor("#00f"));
8020     nvg.stroke();
8021 
8022     nvg.newPath();
8023     nvg.moveTo(0, 0);
8024     nvg.lineTo(600, 0);
8025     nvg.strokeColor(NVGColor("#0ff"));
8026     nvg.stroke();
8027 
8028     if (auto ol = nvg.charOutline('Q')) {
8029       scope(exit) ol.kill();
8030       nvg.newPath();
8031       conwriteln("==== length: ", ol.length, " ====");
8032       foreach (const ref cmd; ol.commands) {
8033         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8034         assert(cmd.valid);
8035         final switch (cmd.code) {
8036           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8037           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8038           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8039           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;
8040         }
8041       }
8042       nvg.strokeColor(NVGColor("#f00"));
8043       nvg.stroke();
8044     }
8045  ---
8046 
8047  Group: text_api
8048  */
8049 public struct NVGPathOutline {
8050 private nothrow @trusted @nogc:
8051   struct DataStore {
8052     uint rc; // refcount
8053     ubyte* data;
8054     uint used;
8055     uint size;
8056     uint ccount; // number of commands
8057     float[4] bounds = 0; /// outline bounds
8058   nothrow @trusted @nogc:
8059     void putBytes (const(void)[] b) {
8060       if (b.length == 0) return;
8061       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8062       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8063       if (used+cast(uint)b.length > size) {
8064         import core.stdc.stdlib : realloc;
8065         uint newsz = size;
8066         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8067         assert(used+cast(uint)b.length <= newsz);
8068         data = cast(ubyte*)realloc(data, newsz);
8069         if (data is null) assert(0, "NanoVega: out of memory");
8070         size = newsz;
8071       }
8072       import core.stdc.string : memcpy;
8073       memcpy(data+used, b.ptr, b.length);
8074       used += cast(uint)b.length;
8075     }
8076     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8077     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8078   }
8079 
8080   static void incRef (DataStore* ds) {
8081     pragma(inline, true);
8082     if (ds !is null) {
8083       ++ds.rc;
8084       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8085     }
8086   }
8087 
8088   static void decRef (DataStore* ds) {
8089     version(aliced) pragma(inline, true);
8090     if (ds !is null) {
8091       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8092       if (--ds.rc == 0) {
8093         import core.stdc.stdlib : free;
8094         import core.stdc.string : memset;
8095         if (ds.data !is null) free(ds.data);
8096         memset(ds, 0, DataStore.sizeof); // just in case
8097         free(ds);
8098         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8099       }
8100     }
8101   }
8102 
8103 private:
8104   static NVGPathOutline createNew () {
8105     import core.stdc.stdlib : malloc;
8106     import core.stdc.string : memset;
8107     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8108     if (ds is null) assert(0, "NanoVega: out of memory");
8109     memset(ds, 0, DataStore.sizeof);
8110     ds.rc = 1;
8111     NVGPathOutline res;
8112     res.dsaddr = cast(usize)ds;
8113     return res;
8114   }
8115 
8116 private:
8117   usize dsaddr; // fool GC
8118 
8119   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8120 
8121 public:
8122   /// commands
8123   static struct Command {
8124     ///
8125     enum Kind : ubyte {
8126       MoveTo, ///
8127       LineTo, ///
8128       QuadTo, ///
8129       BezierTo, ///
8130       End, /// no more commands (this command is not `valid`!)
8131 
8132     }
8133     Kind code; ///
8134     const(float)[] args; ///
8135     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8136 
8137     static uint arglen (Kind code) pure nothrow @safe @nogc {
8138       pragma(inline, true);
8139       return
8140         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8141         code == Kind.QuadTo ? 4 :
8142         code == Kind.BezierTo ? 6 :
8143         0;
8144     }
8145 
8146     /// perform NanoVega command with stored data.
8147     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8148       if (ctx is null) return;
8149       final switch (code) {
8150         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8151         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8152         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8153         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8154         case Kind.End: break;
8155       }
8156     }
8157 
8158     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8159     void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8160       if (ctx is null || !valid) return;
8161       float[6] pts = void;
8162       pts[0..args.length] = args[];
8163       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8164       final switch (code) {
8165         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8166         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8167         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8168         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8169         case Kind.End: break;
8170       }
8171     }
8172   }
8173 
8174 public:
8175   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8176   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8177     auto res = createNew();
8178     res.ds.putCommand(Command.Kind.MoveTo);
8179     res.ds.putArgs(x0, y0);
8180     res.ds.putCommand(Command.Kind.QuadTo);
8181     res.ds.putArgs(cx, cy, x, y);
8182     return res;
8183   }
8184 
8185   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8186   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) {
8187     auto res = createNew();
8188     res.ds.putCommand(Command.Kind.MoveTo);
8189     res.ds.putArgs(x1, y1);
8190     res.ds.putCommand(Command.Kind.BezierTo);
8191     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8192     return res;
8193   }
8194 
8195 public:
8196   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8197   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8198 
8199   void opAssign() (in auto ref NVGPathOutline a) {
8200     incRef(cast(DataStore*)a.dsaddr);
8201     decRef(cast(DataStore*)dsaddr);
8202     dsaddr = a.dsaddr;
8203   }
8204 
8205   /// Clear storage.
8206   void clear () {
8207     pragma(inline, true);
8208     decRef(ds);
8209     dsaddr = 0;
8210   }
8211 
8212   /// Is this outline empty?
8213   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8214 
8215   /// Returns number of commands in outline.
8216   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8217 
8218   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8219   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8220 
8221   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8222   NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8223 
8224   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8225   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8226     import core.stdc.string : memset;
8227 
8228     NVGPathOutline res;
8229     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8230 
8231     // check if we need to flatten the path
8232     if (tfm is null) {
8233       bool dowork = false;
8234       foreach (const ref cs; commands) {
8235         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8236           dowork = true;
8237           break;
8238         }
8239       }
8240       if (!dowork) { res = this; return res; } // nothing to do
8241     }
8242 
8243     NVGcontextinternal ctx;
8244     memset(&ctx, 0, ctx.sizeof);
8245     ctx.cache = nvg__allocPathCache();
8246     scope(exit) {
8247       import core.stdc.stdlib : free;
8248       nvg__deletePathCache(ctx.cache);
8249     }
8250 
8251     ctx.tessTol = 0.25f;
8252     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8253     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8254     ctx.distTol = 0.01f;
8255     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8256 
8257     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8258 
8259     // has some curves or transformations, convert path
8260     res = createNew();
8261     float[8] args = void;
8262 
8263     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8264 
8265     float lastX = float.max, lastY = float.max;
8266     bool lastWasMove = false;
8267 
8268     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8269       if (tfm !is null) tfm.point(x, y);
8270       bool isMove = (cmd == Command.Kind.MoveTo);
8271       if (isMove) {
8272         // moveto
8273         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8274       } else {
8275         // lineto
8276         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8277       }
8278       lastWasMove = isMove;
8279       lastX = x;
8280       lastY = y;
8281       res.ds.putCommand(cmd);
8282       res.ds.putArgs(x, y);
8283       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8284       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8285       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8286       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8287     }
8288 
8289     // sorry for this pasta
8290     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 {
8291       ctx.cache.npoints = 0;
8292       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8293         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8294       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8295         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8296       } else {
8297         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8298       }
8299       // add generated points
8300       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8301     }
8302 
8303     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8304       flattenBezier(
8305         x0, y0,
8306         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8307         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8308         x, y,
8309         0,
8310       );
8311     }
8312 
8313     float cx = 0, cy = 0;
8314     foreach (const ref cs; commands) {
8315       switch (cs.code) {
8316         case Command.Kind.LineTo:
8317         case Command.Kind.MoveTo:
8318           addPoint(cs.args[0], cs.args[1], cs.code);
8319           cx = cs.args[0];
8320           cy = cs.args[1];
8321           break;
8322         case Command.Kind.QuadTo:
8323           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8324           cx = cs.args[2];
8325           cy = cs.args[3];
8326           break;
8327         case Command.Kind.BezierTo:
8328           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8329           cx = cs.args[4];
8330           cy = cs.args[5];
8331           break;
8332         default:
8333           break;
8334       }
8335     }
8336 
8337     return res;
8338   }
8339 
8340   /// Returns forward range with all glyph commands.
8341   auto commands () const nothrow @trusted @nogc {
8342     static struct Range {
8343     private nothrow @trusted @nogc:
8344       usize dsaddr;
8345       uint cpos; // current position in data
8346       uint cleft; // number of commands left
8347       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8348     public:
8349       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8350       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8351       void opAssign() (in auto ref Range a) {
8352         incRef(cast(DataStore*)a.dsaddr);
8353         decRef(cast(DataStore*)dsaddr);
8354         dsaddr = a.dsaddr;
8355         cpos = a.cpos;
8356         cleft = a.cleft;
8357       }
8358       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8359       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8360       @property int length () const pure { pragma(inline, true); return cleft; }
8361       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8362       @property Command front () const {
8363         Command res = void;
8364         if (cleft > 0) {
8365           res.code = cast(Command.Kind)data[cpos];
8366           switch (res.code) {
8367             case Command.Kind.MoveTo:
8368             case Command.Kind.LineTo:
8369               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8370               break;
8371             case Command.Kind.QuadTo:
8372               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8373               break;
8374             case Command.Kind.BezierTo:
8375               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8376               break;
8377             default:
8378               res.code = Command.Kind.End;
8379               res.args = null;
8380               break;
8381           }
8382         } else {
8383           res.code = Command.Kind.End;
8384           res.args = null;
8385         }
8386         return res;
8387       }
8388       void popFront () {
8389         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8390         --cleft;
8391         switch (data[cpos]) {
8392           case Command.Kind.MoveTo:
8393           case Command.Kind.LineTo:
8394             cpos += 1+1*2*cast(uint)float.sizeof;
8395             break;
8396           case Command.Kind.QuadTo:
8397             cpos += 1+2*2*cast(uint)float.sizeof;
8398             break;
8399           case Command.Kind.BezierTo:
8400             cpos += 1+3*2*cast(uint)float.sizeof;
8401             break;
8402           default:
8403             cleft = 0;
8404             break;
8405         }
8406       }
8407     }
8408     if (dsaddr) {
8409       incRef(cast(DataStore*)dsaddr); // range anchors it
8410       return Range(dsaddr, 0, ds.ccount);
8411     } else {
8412       return Range.init;
8413     }
8414   }
8415 }
8416 
8417 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8418 
8419 /// Destroy glyph outiline and free allocated memory.
8420 /// Group: text_api
8421 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8422   pragma(inline, true);
8423   ol.clear();
8424 }
8425 
8426 static if (is(typeof(&fons__nvg__toOutline))) {
8427   public enum NanoVegaHasCharOutline = true; ///
8428 } else {
8429   public enum NanoVegaHasCharOutline = false; ///
8430 }
8431 
8432 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8433 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8434 /// Returns `null` if there is no such glyph, or current font is not scalable.
8435 /// Group: text_api
8436 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8437   import core.stdc.stdlib : malloc;
8438   import core.stdc.string : memcpy;
8439   NVGstate* state = nvg__getState(ctx);
8440   ctx.fs.fontId = state.fontId;
8441   auto oline = NVGPathOutline.createNew();
8442   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8443   return oline;
8444 }
8445 
8446 
8447 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8448   pragma(inline, true);
8449   return (cast(int)(a/d+0.5f))*d;
8450 }
8451 
8452 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8453   pragma(inline, true);
8454   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8455 }
8456 
8457 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8458   int[4] dirty = void;
8459   if (ctx.fs.validateTexture(dirty.ptr)) {
8460     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8461     // Update texture
8462     if (fontImage.valid) {
8463       int iw, ih;
8464       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8465       int x = dirty[0];
8466       int y = dirty[1];
8467       int w = dirty[2]-dirty[0];
8468       int h = dirty[3]-dirty[1];
8469       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8470     }
8471   }
8472 }
8473 
8474 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8475   int iw, ih;
8476   nvg__flushTextTexture(ctx);
8477   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8478   // if next fontImage already have a texture
8479   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8480     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8481   } else {
8482     // calculate the new font image size and create it
8483     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8484     if (iw > ih) ih *= 2; else iw *= 2;
8485     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8486     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8487     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8488       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8489       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8490     }
8491   }
8492   ++ctx.fontImageIdx;
8493   ctx.fs.resetAtlas(iw, ih);
8494   return true;
8495 }
8496 
8497 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8498   NVGstate* state = nvg__getState(ctx);
8499   NVGPaint paint = state.fill;
8500 
8501   // Render triangles.
8502   paint.image = ctx.fontImages[ctx.fontImageIdx];
8503 
8504   // Apply global alpha
8505   paint.innerColor.a *= state.alpha;
8506   paint.middleColor.a *= state.alpha;
8507   paint.outerColor.a *= state.alpha;
8508 
8509   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8510 
8511   ++ctx.drawCallCount;
8512   ctx.textTriCount += nverts/3;
8513 }
8514 
8515 /// Draws text string at specified location. Returns next x position.
8516 /// Group: text_api
8517 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8518   NVGstate* state = nvg__getState(ctx);
8519   FONSTextIter!T iter, prevIter;
8520   FONSQuad q;
8521   NVGVertex* verts;
8522   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8523   float invscale = 1.0f/scale;
8524   int cverts = 0;
8525   int nverts = 0;
8526 
8527   if (state.fontId == FONS_INVALID) return x;
8528   if (str.length == 0) return x;
8529 
8530   ctx.fs.size = state.fontSize*scale;
8531   ctx.fs.spacing = state.letterSpacing*scale;
8532   ctx.fs.blur = state.fontBlur*scale;
8533   ctx.fs.textAlign = state.textAlign;
8534   ctx.fs.fontId = state.fontId;
8535 
8536   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8537   verts = nvg__allocTempVerts(ctx, cverts);
8538   if (verts is null) return x;
8539 
8540   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8541   prevIter = iter;
8542   while (iter.next(q)) {
8543     float[4*2] c = void;
8544     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8545       if (nverts != 0) {
8546         // TODO: add back-end bit to do this just once per frame
8547         nvg__flushTextTexture(ctx);
8548         nvg__renderText(ctx, verts, nverts);
8549         nverts = 0;
8550       }
8551       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8552       iter = prevIter;
8553       iter.next(q); // try again
8554       if (iter.prevGlyphIndex < 0) {
8555         // still can not find glyph, try replacement
8556         iter = prevIter;
8557         if (!iter.getDummyChar(q)) break;
8558       }
8559     }
8560     prevIter = iter;
8561     // transform corners
8562     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8563     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8564     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8565     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8566     // create triangles
8567     if (nverts+6 <= cverts) {
8568       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8569       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8570       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8571       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8572       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8573       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8574     }
8575   }
8576 
8577   // TODO: add back-end bit to do this just once per frame
8578   if (nverts > 0) {
8579     nvg__flushTextTexture(ctx);
8580     nvg__renderText(ctx, verts, nverts);
8581   }
8582 
8583   return iter.nextx/scale;
8584 }
8585 
8586 /** Draws multi-line text string at specified location wrapped at the specified width.
8587  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8588  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8589  *
8590  * Group: text_api
8591  */
8592 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8593   NVGstate* state = nvg__getState(ctx);
8594   if (state.fontId == FONS_INVALID) return;
8595 
8596   NVGTextRow!T[2] rows;
8597   auto oldAlign = state.textAlign;
8598   scope(exit) state.textAlign = oldAlign;
8599   auto halign = state.textAlign.horizontal;
8600   float lineh = 0;
8601 
8602   ctx.textMetrics(null, null, &lineh);
8603   state.textAlign.horizontal = NVGTextAlign.H.Left;
8604   for (;;) {
8605     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8606     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8607     if (rres.length == 0) break;
8608     foreach (ref row; rres) {
8609       final switch (halign) {
8610         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8611         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8612         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8613       }
8614       y += lineh*state.lineHeight;
8615     }
8616     str = rres[$-1].rest;
8617   }
8618 }
8619 
8620 private template isGoodPositionDelegate(DG) {
8621   private DG dg;
8622   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8623              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8624     enum isGoodPositionDelegate = true;
8625   else
8626     enum isGoodPositionDelegate = false;
8627 }
8628 
8629 /** Calculates the glyph x positions of the specified text.
8630  * Measured values are returned in local coordinate space.
8631  *
8632  * Group: text_api
8633  */
8634 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8635 if (isAnyCharType!T)
8636 {
8637   if (str.length == 0 || positions.length == 0) return positions[0..0];
8638   usize posnum;
8639   auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) {
8640     positions.ptr[posnum++] = pos;
8641     return (posnum < positions.length);
8642   });
8643   return positions[0..len];
8644 }
8645 
8646 /// Ditto.
8647 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8648 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8649 {
8650   import std.traits : ReturnType;
8651   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8652 
8653   NVGstate* state = nvg__getState(ctx);
8654   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8655   float invscale = 1.0f/scale;
8656   FONSTextIter!T iter, prevIter;
8657   FONSQuad q;
8658   int npos = 0;
8659 
8660   if (str.length == 0) return 0;
8661 
8662   ctx.fs.size = state.fontSize*scale;
8663   ctx.fs.spacing = state.letterSpacing*scale;
8664   ctx.fs.blur = state.fontBlur*scale;
8665   ctx.fs.textAlign = state.textAlign;
8666   ctx.fs.fontId = state.fontId;
8667 
8668   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8669   prevIter = iter;
8670   while (iter.next(q)) {
8671     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8672       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8673       iter = prevIter;
8674       iter.next(q); // try again
8675       if (iter.prevGlyphIndex < 0) {
8676         // still can not find glyph, try replacement
8677         iter = prevIter;
8678         if (!iter.getDummyChar(q)) break;
8679       }
8680     }
8681     prevIter = iter;
8682     NVGGlyphPosition position = void; //WARNING!
8683     position.strpos = cast(usize)(iter.stringp-str.ptr);
8684     position.x = iter.x*invscale;
8685     position.minx = nvg__min(iter.x, q.x0)*invscale;
8686     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8687     ++npos;
8688     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8689   }
8690 
8691   return npos;
8692 }
8693 
8694 private template isGoodRowDelegate(CT, DG) {
8695   private DG dg;
8696   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8697              is(typeof({ NVGTextRow!CT row; dg(row); })))
8698     enum isGoodRowDelegate = true;
8699   else
8700     enum isGoodRowDelegate = false;
8701 }
8702 
8703 /** Breaks the specified text into lines.
8704  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8705  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8706  *
8707  * Group: text_api
8708  */
8709 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8710 if (isAnyCharType!T)
8711 {
8712   if (rows.length == 0) return rows;
8713   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8714   int nrow = 0;
8715   auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) {
8716     rows[nrow++] = row;
8717     return (nrow < rows.length);
8718   });
8719   return rows[0..count];
8720 }
8721 
8722 /** Breaks the specified text into lines.
8723  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8724  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8725  * Returns number of rows.
8726  *
8727  * Group: text_api
8728  */
8729 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8730 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8731 {
8732   import std.traits : ReturnType;
8733   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8734 
8735   enum NVGcodepointType : int {
8736     Space,
8737     NewLine,
8738     Char,
8739   }
8740 
8741   NVGstate* state = nvg__getState(ctx);
8742   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8743   float invscale = 1.0f/scale;
8744   FONSTextIter!T iter, prevIter;
8745   FONSQuad q;
8746   int nrows = 0;
8747   float rowStartX = 0;
8748   float rowWidth = 0;
8749   float rowMinX = 0;
8750   float rowMaxX = 0;
8751   int rowStart = 0;
8752   int rowEnd = 0;
8753   int wordStart = 0;
8754   float wordStartX = 0;
8755   float wordMinX = 0;
8756   int breakEnd = 0;
8757   float breakWidth = 0;
8758   float breakMaxX = 0;
8759   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8760   uint pcodepoint = 0;
8761 
8762   if (state.fontId == FONS_INVALID) return 0;
8763   if (str.length == 0 || dg is null) return 0;
8764 
8765   ctx.fs.size = state.fontSize*scale;
8766   ctx.fs.spacing = state.letterSpacing*scale;
8767   ctx.fs.blur = state.fontBlur*scale;
8768   ctx.fs.textAlign = state.textAlign;
8769   ctx.fs.fontId = state.fontId;
8770 
8771   breakRowWidth *= scale;
8772 
8773   enum Phase {
8774     Normal, // searching for breaking point
8775     SkipBlanks, // skip leading blanks
8776   }
8777   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8778 
8779   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8780   prevIter = iter;
8781   while (iter.next(q)) {
8782     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8783       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8784       iter = prevIter;
8785       iter.next(q); // try again
8786       if (iter.prevGlyphIndex < 0) {
8787         // still can not find glyph, try replacement
8788         iter = prevIter;
8789         if (!iter.getDummyChar(q)) break;
8790       }
8791     }
8792     prevIter = iter;
8793     switch (iter.codepoint) {
8794       case 9: // \t
8795       case 11: // \v
8796       case 12: // \f
8797       case 32: // space
8798       case 0x00a0: // NBSP
8799         type = NVGcodepointType.Space;
8800         break;
8801       case 10: // \n
8802         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8803         break;
8804       case 13: // \r
8805         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8806         break;
8807       case 0x0085: // NEL
8808       case 0x2028: // Line Separator
8809       case 0x2029: // Paragraph Separator
8810         type = NVGcodepointType.NewLine;
8811         break;
8812       default:
8813         type = NVGcodepointType.Char;
8814         break;
8815     }
8816     if (phase == Phase.SkipBlanks) {
8817       // fix row start
8818       rowStart = cast(int)(iter.stringp-str.ptr);
8819       rowEnd = rowStart;
8820       rowStartX = iter.x;
8821       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8822       rowMinX = q.x0-rowStartX;
8823       rowMaxX = q.x1-rowStartX;
8824       wordStart = rowStart;
8825       wordStartX = iter.x;
8826       wordMinX = q.x0-rowStartX;
8827       breakEnd = rowStart;
8828       breakWidth = 0.0;
8829       breakMaxX = 0.0;
8830       if (type == NVGcodepointType.Space) continue;
8831       phase = Phase.Normal;
8832     }
8833 
8834     if (type == NVGcodepointType.NewLine) {
8835       // always handle new lines
8836       NVGTextRow!T row;
8837       row.string = str;
8838       row.start = rowStart;
8839       row.end = rowEnd;
8840       row.width = rowWidth*invscale;
8841       row.minx = rowMinX*invscale;
8842       row.maxx = rowMaxX*invscale;
8843       ++nrows;
8844       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8845       phase = Phase.SkipBlanks;
8846     } else {
8847       float nextWidth = iter.nextx-rowStartX;
8848       // track last non-white space character
8849       if (type == NVGcodepointType.Char) {
8850         rowEnd = cast(int)(iter.nextp-str.ptr);
8851         rowWidth = iter.nextx-rowStartX;
8852         rowMaxX = q.x1-rowStartX;
8853       }
8854       // track last end of a word
8855       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8856         breakEnd = cast(int)(iter.stringp-str.ptr);
8857         breakWidth = rowWidth;
8858         breakMaxX = rowMaxX;
8859       }
8860       // track last beginning of a word
8861       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8862         wordStart = cast(int)(iter.stringp-str.ptr);
8863         wordStartX = iter.x;
8864         wordMinX = q.x0-rowStartX;
8865       }
8866       // break to new line when a character is beyond break width
8867       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8868         // the run length is too long, need to break to new line
8869         NVGTextRow!T row;
8870         row.string = str;
8871         if (breakEnd == rowStart) {
8872           // the current word is longer than the row length, just break it from here
8873           row.start = rowStart;
8874           row.end = cast(int)(iter.stringp-str.ptr);
8875           row.width = rowWidth*invscale;
8876           row.minx = rowMinX*invscale;
8877           row.maxx = rowMaxX*invscale;
8878           ++nrows;
8879           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8880           rowStartX = iter.x;
8881           rowStart = cast(int)(iter.stringp-str.ptr);
8882           rowEnd = cast(int)(iter.nextp-str.ptr);
8883           rowWidth = iter.nextx-rowStartX;
8884           rowMinX = q.x0-rowStartX;
8885           rowMaxX = q.x1-rowStartX;
8886           wordStart = rowStart;
8887           wordStartX = iter.x;
8888           wordMinX = q.x0-rowStartX;
8889         } else {
8890           // break the line from the end of the last word, and start new line from the beginning of the new
8891           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8892           row.start = rowStart;
8893           row.end = breakEnd;
8894           row.width = breakWidth*invscale;
8895           row.minx = rowMinX*invscale;
8896           row.maxx = breakMaxX*invscale;
8897           ++nrows;
8898           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8899           rowStartX = wordStartX;
8900           rowStart = wordStart;
8901           rowEnd = cast(int)(iter.nextp-str.ptr);
8902           rowWidth = iter.nextx-rowStartX;
8903           rowMinX = wordMinX;
8904           rowMaxX = q.x1-rowStartX;
8905           // no change to the word start
8906         }
8907         // set null break point
8908         breakEnd = rowStart;
8909         breakWidth = 0.0;
8910         breakMaxX = 0.0;
8911       }
8912     }
8913 
8914     pcodepoint = iter.codepoint;
8915     ptype = type;
8916   }
8917 
8918   // break the line from the end of the last word, and start new line from the beginning of the new
8919   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8920     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8921     NVGTextRow!T row;
8922     row.string = str;
8923     row.start = rowStart;
8924     row.end = cast(int)str.length;
8925     row.width = rowWidth*invscale;
8926     row.minx = rowMinX*invscale;
8927     row.maxx = rowMaxX*invscale;
8928     ++nrows;
8929     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8930   }
8931 
8932   return nrows;
8933 }
8934 
8935 /** Returns iterator which you can use to calculate text bounds and advancement.
8936  * This is usable when you need to do some text layouting with wrapping, to avoid
8937  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8938  * algorithm. Note that you can copy the returned struct to save iterator state.
8939  *
8940  * You can check if iterator is valid with [valid] property, put new chars with
8941  * [put] method, get current advance with [advance] property, and current
8942  * bounds with `getBounds(ref float[4] bounds)` method.
8943  *
8944  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8945  *
8946  * Group: text_api
8947  */
8948 public struct TextBoundsIterator {
8949 private:
8950   NVGContext ctx;
8951   FONSTextBoundsIterator fsiter; // fontstash iterator
8952   float scale, invscale, xscaled, yscaled;
8953   // font settings
8954   float fsSize, fsSpacing, fsBlur;
8955   int fsFontId;
8956   NVGTextAlign fsAlign;
8957 
8958 public:
8959   /// Setups iteration. Takes current font parameters from the given NanoVega context.
8960   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8961 
8962   /// Resets iteration. Takes current font parameters from the given NanoVega context.
8963   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8964     fsiter = fsiter.init;
8965     this = this.init;
8966     if (actx is null) return;
8967     NVGstate* state = nvg__getState(actx);
8968     if (state is null) return;
8969     if (state.fontId == FONS_INVALID) { ctx = null; return; }
8970 
8971     ctx = actx;
8972     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8973     invscale = 1.0f/scale;
8974 
8975     fsSize = state.fontSize*scale;
8976     fsSpacing = state.letterSpacing*scale;
8977     fsBlur = state.fontBlur*scale;
8978     fsAlign = state.textAlign;
8979     fsFontId = state.fontId;
8980     restoreFont();
8981 
8982     xscaled = ax*scale;
8983     yscaled = ay*scale;
8984     fsiter.reset(ctx.fs, xscaled, yscaled);
8985   }
8986 
8987   /// Restart iteration. Will not restore font.
8988   void restart () nothrow @trusted @nogc {
8989     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8990   }
8991 
8992   /// Restore font settings for the context.
8993   void restoreFont () nothrow @trusted @nogc {
8994     if (ctx !is null) {
8995       ctx.fs.size = fsSize;
8996       ctx.fs.spacing = fsSpacing;
8997       ctx.fs.blur = fsBlur;
8998       ctx.fs.textAlign = fsAlign;
8999       ctx.fs.fontId = fsFontId;
9000     }
9001   }
9002 
9003   /// Is this iterator valid?
9004   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
9005 
9006   /// Add chars.
9007   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
9008 
9009   /// Returns current advance
9010   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
9011 
9012   /// Returns current text bounds.
9013   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
9014     if (ctx !is null) {
9015       fsiter.getBounds(bounds);
9016       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
9017       bounds[0] *= invscale;
9018       bounds[1] *= invscale;
9019       bounds[2] *= invscale;
9020       bounds[3] *= invscale;
9021     } else {
9022       bounds[] = 0;
9023     }
9024   }
9025 
9026   /// Returns current horizontal text bounds.
9027   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
9028     if (ctx !is null) {
9029       fsiter.getHBounds(xmin, xmax);
9030       xmin *= invscale;
9031       xmax *= invscale;
9032     }
9033   }
9034 
9035   /// Returns current vertical text bounds.
9036   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9037     if (ctx !is null) {
9038       //fsiter.getVBounds(ymin, ymax);
9039       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9040       ymin *= invscale;
9041       ymax *= invscale;
9042     }
9043   }
9044 }
9045 
9046 /// Returns font line height (without line spacing), measured in local coordinate space.
9047 /// Group: text_api
9048 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9049   float res = void;
9050   ctx.textMetrics(null, null, &res);
9051   return res;
9052 }
9053 
9054 /// Returns font ascender (positive), measured in local coordinate space.
9055 /// Group: text_api
9056 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9057   float res = void;
9058   ctx.textMetrics(&res, null, null);
9059   return res;
9060 }
9061 
9062 /// Returns font descender (negative), measured in local coordinate space.
9063 /// Group: text_api
9064 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9065   float res = void;
9066   ctx.textMetrics(null, &res, null);
9067   return res;
9068 }
9069 
9070 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9071  * Measured values are returned in local coordinate space.
9072  *
9073  * Group: text_api
9074  */
9075 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9076   float[4] bnd = void;
9077   ctx.textBounds(0, 0, str, bnd[]);
9078   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9079     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9080     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9081   } else {
9082     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9083     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9084   }
9085 }
9086 
9087 /** Measures the specified text string. Returns horizontal size of the measured text.
9088  * Measured values are returned in local coordinate space.
9089  *
9090  * Group: text_api
9091  */
9092 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9093   float w = void;
9094   ctx.textExtents(str, &w, null);
9095   return w;
9096 }
9097 
9098 /** Measures the specified text string. Parameter bounds should be a float[4],
9099  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9100  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9101  * Measured values are returned in local coordinate space.
9102  *
9103  * Group: text_api
9104  */
9105 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9106 if (isAnyCharType!T)
9107 {
9108   NVGstate* state = nvg__getState(ctx);
9109 
9110   if (state.fontId == FONS_INVALID) {
9111     bounds[] = 0;
9112     return 0;
9113   }
9114 
9115   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9116   ctx.fs.size = state.fontSize*scale;
9117   ctx.fs.spacing = state.letterSpacing*scale;
9118   ctx.fs.blur = state.fontBlur*scale;
9119   ctx.fs.textAlign = state.textAlign;
9120   ctx.fs.fontId = state.fontId;
9121 
9122   float[4] b = void;
9123   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9124   immutable float invscale = 1.0f/scale;
9125   if (bounds.length) {
9126     // use line bounds for height
9127     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9128     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9129     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9130     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9131     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9132   }
9133   return width*invscale;
9134 }
9135 
9136 /// Ditto.
9137 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9138   NVGstate* state = nvg__getState(ctx);
9139   NVGTextRow!T[2] rows;
9140   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9141   float invscale = 1.0f/scale;
9142   float lineh = 0, rminy = 0, rmaxy = 0;
9143   float minx, miny, maxx, maxy;
9144 
9145   if (state.fontId == FONS_INVALID) {
9146     bounds[] = 0;
9147     return;
9148   }
9149 
9150   auto oldAlign = state.textAlign;
9151   scope(exit) state.textAlign = oldAlign;
9152   auto halign = state.textAlign.horizontal;
9153 
9154   ctx.textMetrics(null, null, &lineh);
9155   state.textAlign.horizontal = NVGTextAlign.H.Left;
9156 
9157   minx = maxx = x;
9158   miny = maxy = y;
9159 
9160   ctx.fs.size = state.fontSize*scale;
9161   ctx.fs.spacing = state.letterSpacing*scale;
9162   ctx.fs.blur = state.fontBlur*scale;
9163   ctx.fs.textAlign = state.textAlign;
9164   ctx.fs.fontId = state.fontId;
9165   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9166   rminy *= invscale;
9167   rmaxy *= invscale;
9168 
9169   for (;;) {
9170     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9171     if (rres.length == 0) break;
9172     foreach (ref row; rres) {
9173       float rminx, rmaxx, dx = 0;
9174       // horizontal bounds
9175       final switch (halign) {
9176         case NVGTextAlign.H.Left: dx = 0; break;
9177         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9178         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9179       }
9180       rminx = x+row.minx+dx;
9181       rmaxx = x+row.maxx+dx;
9182       minx = nvg__min(minx, rminx);
9183       maxx = nvg__max(maxx, rmaxx);
9184       // vertical bounds
9185       miny = nvg__min(miny, y+rminy);
9186       maxy = nvg__max(maxy, y+rmaxy);
9187       y += lineh*state.lineHeight;
9188     }
9189     str = rres[$-1].rest;
9190   }
9191 
9192   if (bounds.length) {
9193     if (bounds.length > 0) bounds.ptr[0] = minx;
9194     if (bounds.length > 1) bounds.ptr[1] = miny;
9195     if (bounds.length > 2) bounds.ptr[2] = maxx;
9196     if (bounds.length > 3) bounds.ptr[3] = maxy;
9197   }
9198 }
9199 
9200 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9201 /// Group: text_api
9202 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9203   NVGstate* state = nvg__getState(ctx);
9204 
9205   if (state.fontId == FONS_INVALID) {
9206     if (ascender !is null) *ascender *= 0;
9207     if (descender !is null) *descender *= 0;
9208     if (lineh !is null) *lineh *= 0;
9209     return;
9210   }
9211 
9212   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9213   immutable float invscale = 1.0f/scale;
9214 
9215   ctx.fs.size = state.fontSize*scale;
9216   ctx.fs.spacing = state.letterSpacing*scale;
9217   ctx.fs.blur = state.fontBlur*scale;
9218   ctx.fs.textAlign = state.textAlign;
9219   ctx.fs.fontId = state.fontId;
9220 
9221   ctx.fs.getVertMetrics(ascender, descender, lineh);
9222   if (ascender !is null) *ascender *= invscale;
9223   if (descender !is null) *descender *= invscale;
9224   if (lineh !is null) *lineh *= invscale;
9225 }
9226 
9227 
9228 // ////////////////////////////////////////////////////////////////////////// //
9229 // fontstash
9230 // ////////////////////////////////////////////////////////////////////////// //
9231 import core.stdc.stdlib : malloc, realloc, free;
9232 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9233 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9234 
9235 public:
9236 // welcome to version hell!
9237 version(nanovg_force_stb_ttf) {
9238 } else {
9239   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9240 }
9241 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9242 //version(nanovg_ignore_mono);
9243 
9244 version(nanovg_force_stb_ttf) {
9245   private enum NanoVegaForceFreeType = false;
9246 } else {
9247   version (nanovg_builtin_freetype_bindings) {
9248     version(Posix) {
9249       private enum NanoVegaForceFreeType = true;
9250     } else {
9251       private enum NanoVegaForceFreeType = false;
9252     }
9253   } else {
9254     version(Posix) {
9255       private enum NanoVegaForceFreeType = true;
9256     } else {
9257       private enum NanoVegaForceFreeType = false;
9258     }
9259   }
9260 }
9261 
9262 version(nanovg_use_freetype_ii) {
9263   enum NanoVegaIsUsingSTBTTF = false;
9264   //pragma(msg, "iv.freetype: forced");
9265 } else {
9266   static if (NanoVegaForceFreeType) {
9267     enum NanoVegaIsUsingSTBTTF = false;
9268   } else {
9269     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9270       import iv.stb.ttf;
9271       enum NanoVegaIsUsingSTBTTF = true;
9272       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9273     } else static if (__traits(compiles, { import arsd.ttf; })) {
9274       import arsd.ttf;
9275       enum NanoVegaIsUsingSTBTTF = true;
9276       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9277     } else static if (__traits(compiles, { import stb_truetype; })) {
9278       import stb_truetype;
9279       enum NanoVegaIsUsingSTBTTF = true;
9280       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9281     } else static if (__traits(compiles, { import iv.freetype; })) {
9282       version (nanovg_builtin_freetype_bindings) {
9283         enum NanoVegaIsUsingSTBTTF = false;
9284         version = nanovg_builtin_freetype_bindings;
9285       } else {
9286         import iv.freetype;
9287         enum NanoVegaIsUsingSTBTTF = false;
9288       }
9289       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9290     } else {
9291       static assert(0, "no stb_ttf/iv.freetype found!");
9292     }
9293   }
9294 }
9295 
9296 
9297 // ////////////////////////////////////////////////////////////////////////// //
9298 //version = nanovg_ft_mono;
9299 
9300 /// Invald font id.
9301 /// Group: font_stash
9302 public enum FONS_INVALID = -1;
9303 
9304 public enum FONSBitmapFlag : uint {
9305   Required = 0,
9306   Optional = 1,
9307 }
9308 
9309 public enum FONSError : int {
9310   NoError = 0,
9311   AtlasFull = 1, // Font atlas is full.
9312   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.
9313   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9314   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9315 }
9316 
9317 /// Initial parameters for new FontStash.
9318 /// Group: font_stash
9319 public struct FONSParams {
9320   enum Flag : uint {
9321     ZeroTopLeft    = 0U, // default
9322     ZeroBottomLeft = 1U,
9323   }
9324   int width, height;
9325   Flag flags = Flag.ZeroTopLeft;
9326   void* userPtr;
9327   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9328   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9329   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9330   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9331   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9332 }
9333 
9334 //TODO: document this
9335 public struct FONSQuad {
9336   float x0=0, y0=0, s0=0, t0=0;
9337   float x1=0, y1=0, s1=0, t1=0;
9338 }
9339 
9340 //TODO: document this
9341 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9342   alias CharType = CT;
9343   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9344   uint codepoint;
9345   short isize, iblur;
9346   FONSContext stash;
9347   FONSfont* font;
9348   int prevGlyphIndex;
9349   const(CT)* s; // string
9350   const(CT)* n; // next
9351   const(CT)* e; // end
9352   FONSBitmapFlag bitmapOption;
9353   static if (is(CT == char)) {
9354     uint utf8state;
9355   }
9356 
9357   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9358   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9359 
9360   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9361   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9362   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9363 
9364   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9365     import core.stdc.string : memset;
9366 
9367     memset(&this, 0, this.sizeof);
9368     if (astash is null) return false;
9369 
9370     FONSstate* state = astash.getState;
9371 
9372     if (state.font < 0 || state.font >= astash.nfonts) return false;
9373     font = astash.fonts[state.font];
9374     if (font is null || font.fdata is null) return false;
9375 
9376     isize = cast(short)(state.size*10.0f);
9377     iblur = cast(short)state.blur;
9378     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9379 
9380     // align horizontally
9381     if (state.talign.left) {
9382       // empty
9383     } else if (state.talign.right) {
9384       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9385       ax -= width;
9386     } else if (state.talign.center) {
9387       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9388       ax -= width*0.5f;
9389     }
9390 
9391     // align vertically
9392     ay += astash.getVertAlign(font, state.talign, isize);
9393 
9394     x = nextx = ax;
9395     y = nexty = ay;
9396     spacing = state.spacing;
9397 
9398     if (astr.ptr is null) {
9399            static if (is(CharType == char)) astr = "";
9400       else static if (is(CharType == wchar)) astr = ""w;
9401       else static if (is(CharType == dchar)) astr = ""d;
9402       else static assert(0, "wtf?!");
9403     }
9404     s = astr.ptr;
9405     n = astr.ptr;
9406     e = astr.ptr+astr.length;
9407 
9408     codepoint = 0;
9409     prevGlyphIndex = -1;
9410     bitmapOption = abitmapOption;
9411     stash = astash;
9412 
9413     return true;
9414   }
9415 
9416   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9417     if (stash is null || font is null) return false;
9418     // get glyph and quad
9419     x = nextx;
9420     y = nexty;
9421     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9422     if (glyph !is null) {
9423       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9424       prevGlyphIndex = glyph.index;
9425       return true;
9426     } else {
9427       prevGlyphIndex = -1;
9428       return false;
9429     }
9430   }
9431 
9432   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9433     if (stash is null || font is null) return false;
9434     FONSglyph* glyph = null;
9435     static if (is(CharType == char)) {
9436       const(char)* str = this.n;
9437       this.s = this.n;
9438       if (str is this.e) return false;
9439       const(char)* e = this.e;
9440       for (; str !is e; ++str) {
9441         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9442         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9443         if (utf8state) continue;
9444         ++str; // 'cause we'll break anyway
9445         // get glyph and quad
9446         x = nextx;
9447         y = nexty;
9448         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9449         if (glyph !is null) {
9450           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9451           prevGlyphIndex = glyph.index;
9452         } else {
9453           prevGlyphIndex = -1;
9454         }
9455         break;
9456       }
9457       this.n = str;
9458     } else {
9459       const(CharType)* str = this.n;
9460       this.s = this.n;
9461       if (str is this.e) return false;
9462       codepoint = cast(uint)(*str++);
9463       if (codepoint > dchar.max) codepoint = 0xFFFD;
9464       // get glyph and quad
9465       x = nextx;
9466       y = nexty;
9467       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9468       if (glyph !is null) {
9469         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9470         prevGlyphIndex = glyph.index;
9471       } else {
9472         prevGlyphIndex = -1;
9473       }
9474       this.n = str;
9475     }
9476     return true;
9477   }
9478 }
9479 
9480 
9481 // ////////////////////////////////////////////////////////////////////////// //
9482 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9483 
9484 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9485 version(nanovg_builtin_freetype_bindings) {
9486 pragma(lib, "freetype");
9487 private extern(C) nothrow @trusted @nogc {
9488 private import core.stdc.config : c_long, c_ulong;
9489 alias FT_Pos = c_long;
9490 // config/ftconfig.h
9491 alias FT_Int16 = short;
9492 alias FT_UInt16 = ushort;
9493 alias FT_Int32 = int;
9494 alias FT_UInt32 = uint;
9495 alias FT_Fast = int;
9496 alias FT_UFast = uint;
9497 alias FT_Int64 = long;
9498 alias FT_Uint64 = ulong;
9499 // fttypes.h
9500 alias FT_Bool = ubyte;
9501 alias FT_FWord = short;
9502 alias FT_UFWord = ushort;
9503 alias FT_Char = char;
9504 alias FT_Byte = ubyte;
9505 alias FT_Bytes = FT_Byte*;
9506 alias FT_Tag = FT_UInt32;
9507 alias FT_String = char;
9508 alias FT_Short = short;
9509 alias FT_UShort = ushort;
9510 alias FT_Int = int;
9511 alias FT_UInt = uint;
9512 alias FT_Long = c_long;
9513 alias FT_ULong = c_ulong;
9514 alias FT_F2Dot14 = short;
9515 alias FT_F26Dot6 = c_long;
9516 alias FT_Fixed = c_long;
9517 alias FT_Error = int;
9518 alias FT_Pointer = void*;
9519 alias FT_Offset = usize;
9520 alias FT_PtrDist = ptrdiff_t;
9521 
9522 struct FT_UnitVector {
9523   FT_F2Dot14 x;
9524   FT_F2Dot14 y;
9525 }
9526 
9527 struct FT_Matrix {
9528   FT_Fixed xx, xy;
9529   FT_Fixed yx, yy;
9530 }
9531 
9532 struct FT_Data {
9533   const(FT_Byte)* pointer;
9534   FT_Int length;
9535 }
9536 alias FT_Face = FT_FaceRec*;
9537 struct FT_FaceRec {
9538   FT_Long num_faces;
9539   FT_Long face_index;
9540   FT_Long face_flags;
9541   FT_Long style_flags;
9542   FT_Long num_glyphs;
9543   FT_String* family_name;
9544   FT_String* style_name;
9545   FT_Int num_fixed_sizes;
9546   FT_Bitmap_Size* available_sizes;
9547   FT_Int num_charmaps;
9548   FT_CharMap* charmaps;
9549   FT_Generic generic;
9550   FT_BBox bbox;
9551   FT_UShort units_per_EM;
9552   FT_Short ascender;
9553   FT_Short descender;
9554   FT_Short height;
9555   FT_Short max_advance_width;
9556   FT_Short max_advance_height;
9557   FT_Short underline_position;
9558   FT_Short underline_thickness;
9559   FT_GlyphSlot glyph;
9560   FT_Size size;
9561   FT_CharMap charmap;
9562   FT_Driver driver;
9563   FT_Memory memory;
9564   FT_Stream stream;
9565   FT_ListRec sizes_list;
9566   FT_Generic autohint;
9567   void* extensions;
9568   FT_Face_Internal internal;
9569 }
9570 struct FT_Bitmap_Size {
9571   FT_Short height;
9572   FT_Short width;
9573   FT_Pos size;
9574   FT_Pos x_ppem;
9575   FT_Pos y_ppem;
9576 }
9577 alias FT_CharMap = FT_CharMapRec*;
9578 struct FT_CharMapRec {
9579   FT_Face face;
9580   FT_Encoding encoding;
9581   FT_UShort platform_id;
9582   FT_UShort encoding_id;
9583 }
9584 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9585 struct FT_Generic {
9586   void* data;
9587   FT_Generic_Finalizer finalizer;
9588 }
9589 struct FT_Vector {
9590   FT_Pos x;
9591   FT_Pos y;
9592 }
9593 struct FT_BBox {
9594   FT_Pos xMin, yMin;
9595   FT_Pos xMax, yMax;
9596 }
9597 alias FT_Pixel_Mode = int;
9598 enum {
9599   FT_PIXEL_MODE_NONE = 0,
9600   FT_PIXEL_MODE_MONO,
9601   FT_PIXEL_MODE_GRAY,
9602   FT_PIXEL_MODE_GRAY2,
9603   FT_PIXEL_MODE_GRAY4,
9604   FT_PIXEL_MODE_LCD,
9605   FT_PIXEL_MODE_LCD_V,
9606   FT_PIXEL_MODE_MAX
9607 }
9608 struct FT_Bitmap {
9609   uint rows;
9610   uint width;
9611   int pitch;
9612   ubyte* buffer;
9613   ushort num_grays;
9614   ubyte pixel_mode;
9615   ubyte palette_mode;
9616   void* palette;
9617 }
9618 struct FT_Outline {
9619   short n_contours;
9620   short n_points;
9621   FT_Vector* points;
9622   byte* tags;
9623   short* contours;
9624   int flags;
9625 }
9626 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9627 struct FT_GlyphSlotRec {
9628   FT_Library library;
9629   FT_Face face;
9630   FT_GlyphSlot next;
9631   FT_UInt reserved;
9632   FT_Generic generic;
9633   FT_Glyph_Metrics metrics;
9634   FT_Fixed linearHoriAdvance;
9635   FT_Fixed linearVertAdvance;
9636   FT_Vector advance;
9637   FT_Glyph_Format format;
9638   FT_Bitmap bitmap;
9639   FT_Int bitmap_left;
9640   FT_Int bitmap_top;
9641   FT_Outline outline;
9642   FT_UInt num_subglyphs;
9643   FT_SubGlyph subglyphs;
9644   void* control_data;
9645   c_long control_len;
9646   FT_Pos lsb_delta;
9647   FT_Pos rsb_delta;
9648   void* other;
9649   FT_Slot_Internal internal;
9650 }
9651 alias FT_Size = FT_SizeRec*;
9652 struct FT_SizeRec {
9653   FT_Face face;
9654   FT_Generic generic;
9655   FT_Size_Metrics metrics;
9656   FT_Size_Internal internal;
9657 }
9658 alias FT_Encoding = FT_Tag;
9659 alias FT_Face_Internal = void*;
9660 alias FT_Driver = void*;
9661 alias FT_Memory = void*;
9662 alias FT_Stream = void*;
9663 alias FT_Library = void*;
9664 alias FT_SubGlyph = void*;
9665 alias FT_Slot_Internal = void*;
9666 alias FT_Size_Internal = void*;
9667 alias FT_ListNode = FT_ListNodeRec*;
9668 alias FT_List = FT_ListRec*;
9669 struct FT_ListNodeRec {
9670   FT_ListNode prev;
9671   FT_ListNode next;
9672   void* data;
9673 }
9674 struct FT_ListRec {
9675   FT_ListNode head;
9676   FT_ListNode tail;
9677 }
9678 struct FT_Glyph_Metrics {
9679   FT_Pos width;
9680   FT_Pos height;
9681   FT_Pos horiBearingX;
9682   FT_Pos horiBearingY;
9683   FT_Pos horiAdvance;
9684   FT_Pos vertBearingX;
9685   FT_Pos vertBearingY;
9686   FT_Pos vertAdvance;
9687 }
9688 alias FT_Glyph_Format = FT_Tag;
9689 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9690   pragma(inline, true);
9691   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9692 }
9693 enum : FT_Tag {
9694   FT_GLYPH_FORMAT_NONE = 0,
9695   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9696   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9697   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9698   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9699 }
9700 struct FT_Size_Metrics {
9701   FT_UShort x_ppem;
9702   FT_UShort y_ppem;
9703 
9704   FT_Fixed x_scale;
9705   FT_Fixed y_scale;
9706 
9707   FT_Pos ascender;
9708   FT_Pos descender;
9709   FT_Pos height;
9710   FT_Pos max_advance;
9711 }
9712 enum FT_LOAD_DEFAULT = 0x0U;
9713 enum FT_LOAD_NO_SCALE = 1U<<0;
9714 enum FT_LOAD_NO_HINTING = 1U<<1;
9715 enum FT_LOAD_RENDER = 1U<<2;
9716 enum FT_LOAD_NO_BITMAP = 1U<<3;
9717 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9718 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9719 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9720 enum FT_LOAD_PEDANTIC = 1U<<7;
9721 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9722 enum FT_LOAD_NO_RECURSE = 1U<<10;
9723 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9724 enum FT_LOAD_MONOCHROME = 1U<<12;
9725 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9726 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9727 enum FT_LOAD_COLOR = 1U<<20;
9728 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9729 enum FT_FACE_FLAG_KERNING = 1U<<6;
9730 alias FT_Kerning_Mode = int;
9731 enum /*FT_Kerning_Mode*/ {
9732   FT_KERNING_DEFAULT = 0,
9733   FT_KERNING_UNFITTED,
9734   FT_KERNING_UNSCALED
9735 }
9736 extern(C) nothrow @nogc {
9737   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9738   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9739   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9740   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9741 }
9742 struct FT_Outline_Funcs {
9743   FT_Outline_MoveToFunc move_to;
9744   FT_Outline_LineToFunc line_to;
9745   FT_Outline_ConicToFunc conic_to;
9746   FT_Outline_CubicToFunc cubic_to;
9747   int shift;
9748   FT_Pos delta;
9749 }
9750 
9751 FT_Error FT_Init_FreeType (FT_Library*);
9752 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9753 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9754 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9755 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9756 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9757 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9758 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9759 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9760 }
9761 } else version(bindbc) {
9762   import bindbc.freetype;
9763   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9764   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9765   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9766 } else {
9767   import iv.freetype;
9768 }
9769 
9770 struct FONSttFontImpl {
9771   FT_Face font;
9772   bool mono; // no aa?
9773 }
9774 
9775 __gshared FT_Library ftLibrary;
9776 
9777 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9778   FT_Error ftError;
9779   //FONS_NOTUSED(context);
9780   ftError = FT_Init_FreeType(&ftLibrary);
9781   return (ftError == 0);
9782 }
9783 
9784 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9785   font.mono = v;
9786 }
9787 
9788 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9789   return font.mono;
9790 }
9791 
9792 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9793   FT_Error ftError;
9794   //font.font.userdata = stash;
9795   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9796   return ftError == 0;
9797 }
9798 
9799 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9800   *ascent = font.font.ascender;
9801   *descent = font.font.descender;
9802   *lineGap = font.font.height-(*ascent - *descent);
9803 }
9804 
9805 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9806   return size/(font.font.ascender-font.font.descender);
9807 }
9808 
9809 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9810   return FT_Get_Char_Index(font.font, codepoint);
9811 }
9812 
9813 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 {
9814   FT_Error ftError;
9815   FT_GlyphSlot ftGlyph;
9816   //version(nanovg_ignore_mono) enum exflags = 0;
9817   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9818   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9819   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)));
9820   if (ftError) return 0;
9821   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9822   if (ftError) return 0;
9823   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9824   if (ftError) return 0;
9825   ftGlyph = font.font.glyph;
9826   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9827   *x0 = ftGlyph.bitmap_left;
9828   *x1 = *x0+ftGlyph.bitmap.width;
9829   *y0 = -ftGlyph.bitmap_top;
9830   *y1 = *y0+ftGlyph.bitmap.rows;
9831   return 1;
9832 }
9833 
9834 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9835   FT_GlyphSlot ftGlyph = font.font.glyph;
9836   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9837   //version(nanovg_ignore_mono) enum RenderAA = true;
9838   //else version(nanovg_ft_mono) enum RenderAA = false;
9839   //else enum RenderAA = true;
9840   if (font.mono) {
9841     auto src = ftGlyph.bitmap.buffer;
9842     auto dst = output;
9843     auto spt = ftGlyph.bitmap.pitch;
9844     if (spt < 0) spt = -spt;
9845     foreach (int y; 0..ftGlyph.bitmap.rows) {
9846       ubyte count = 0, b = 0;
9847       auto s = src;
9848       auto d = dst;
9849       foreach (int x; 0..ftGlyph.bitmap.width) {
9850         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9851         *d++ = (b&0x80 ? 255 : 0);
9852       }
9853       src += spt;
9854       dst += outStride;
9855     }
9856   } else {
9857     auto src = ftGlyph.bitmap.buffer;
9858     auto dst = output;
9859     auto spt = ftGlyph.bitmap.pitch;
9860     if (spt < 0) spt = -spt;
9861     foreach (int y; 0..ftGlyph.bitmap.rows) {
9862       import core.stdc.string : memcpy;
9863       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9864       memcpy(dst, src, ftGlyph.bitmap.width);
9865       src += spt;
9866       dst += outStride;
9867     }
9868   }
9869 }
9870 
9871 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9872   FT_Vector ftKerning;
9873   version(none) {
9874     // fitted kerning
9875     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9876     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9877     return cast(int)ftKerning.x; // round up and convert to integer
9878   } else {
9879     // unfitted kerning
9880     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9881     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9882     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;
9883     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9884     version(none) {
9885       if (ftKerning.x) {
9886         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9887         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9888       }
9889     }
9890     version(none) {
9891       FT_Vector kk;
9892       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9893       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9894       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9895       //assert(ftKerning.x == kadvfrac);
9896       if (ftKerning.x || kadvfrac) {
9897         { 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); }
9898       }
9899       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9900       return kadvfrac/64.0f;
9901     }
9902     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9903     return ftKerning.x/64.0f;
9904   }
9905 }
9906 
9907 extern(C) nothrow @trusted @nogc {
9908   static struct OutlinerData {
9909     @disable this (this);
9910     void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9911     NVGContext vg;
9912     NVGPathOutline.DataStore* ol;
9913     FT_BBox outlineBBox;
9914   nothrow @trusted @nogc:
9915     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9916     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9917   }
9918 
9919   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9920     auto odata = cast(OutlinerData*)user;
9921     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9922     if (odata.ol !is null) {
9923       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9924       odata.ol.putArgs(odata.transx(to.x));
9925       odata.ol.putArgs(odata.transy(to.y));
9926     }
9927     return 0;
9928   }
9929 
9930   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9931     auto odata = cast(OutlinerData*)user;
9932     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9933     if (odata.ol !is null) {
9934       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9935       odata.ol.putArgs(odata.transx(to.x));
9936       odata.ol.putArgs(odata.transy(to.y));
9937     }
9938     return 0;
9939   }
9940 
9941   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9942     auto odata = cast(OutlinerData*)user;
9943     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9944     if (odata.ol !is null) {
9945       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9946       odata.ol.putArgs(odata.transx(c1.x));
9947       odata.ol.putArgs(odata.transy(c1.y));
9948       odata.ol.putArgs(odata.transx(to.x));
9949       odata.ol.putArgs(odata.transy(to.y));
9950     }
9951     return 0;
9952   }
9953 
9954   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9955     auto odata = cast(OutlinerData*)user;
9956     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));
9957     if (odata.ol !is null) {
9958       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9959       odata.ol.putArgs(odata.transx(c1.x));
9960       odata.ol.putArgs(odata.transy(c1.y));
9961       odata.ol.putArgs(odata.transx(c2.x));
9962       odata.ol.putArgs(odata.transy(c2.y));
9963       odata.ol.putArgs(odata.transx(to.x));
9964       odata.ol.putArgs(odata.transy(to.y));
9965     }
9966     return 0;
9967   }
9968 }
9969 
9970 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9971   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9972 
9973   FT_Outline_Funcs funcs;
9974   funcs.move_to = &fons__nvg__moveto_cb;
9975   funcs.line_to = &fons__nvg__lineto_cb;
9976   funcs.conic_to = &fons__nvg__quadto_cb;
9977   funcs.cubic_to = &fons__nvg__cubicto_cb;
9978 
9979   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9980   if (err) { bounds[] = 0; return false; }
9981   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9982 
9983   FT_Outline outline = font.font.glyph.outline;
9984 
9985   OutlinerData odata;
9986   odata.vg = vg;
9987   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9988 
9989   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9990   if (err) { bounds[] = 0; return false; }
9991   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9992   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
9993   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
9994   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
9995   return true;
9996 }
9997 
9998 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
9999   FT_Outline_Funcs funcs;
10000   funcs.move_to = &fons__nvg__moveto_cb;
10001   funcs.line_to = &fons__nvg__lineto_cb;
10002   funcs.conic_to = &fons__nvg__quadto_cb;
10003   funcs.cubic_to = &fons__nvg__cubicto_cb;
10004 
10005   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10006   if (err) return false;
10007   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
10008 
10009   FT_Outline outline = font.font.glyph.outline;
10010 
10011   OutlinerData odata;
10012   odata.ol = ol;
10013   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10014 
10015   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10016   if (err) return false;
10017   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
10018   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
10019   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
10020   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
10021   return true;
10022 }
10023 
10024 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10025   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10026 
10027   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10028   if (err) return false;
10029   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10030 
10031   FT_Outline outline = font.font.glyph.outline;
10032   FT_BBox outlineBBox;
10033   FT_Outline_Get_CBox(&outline, &outlineBBox);
10034   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10035   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10036   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10037   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10038   return true;
10039 }
10040 
10041 
10042 } else {
10043 // ////////////////////////////////////////////////////////////////////////// //
10044 // sorry
10045 import std.traits : isFunctionPointer, isDelegate;
10046 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10047   import std.traits;
10048   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10049   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10050 }
10051 
10052 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10053   try {
10054     return assumeNoThrowNoGC(t)();
10055   } catch (Exception e) {
10056     assert(0, "OOPS!");
10057   }
10058 }
10059 
10060 struct FONSttFontImpl {
10061   stbtt_fontinfo font;
10062   bool mono; // no aa?
10063 }
10064 
10065 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10066   return 1;
10067 }
10068 
10069 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10070   font.mono = v;
10071 }
10072 
10073 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10074   return font.mono;
10075 }
10076 
10077 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10078   int stbError;
10079   font.font.userdata = context;
10080   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10081   return stbError;
10082 }
10083 
10084 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10085   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10086 }
10087 
10088 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10089   float res = void;
10090   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10091   return res;
10092 }
10093 
10094 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10095   int res;
10096   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10097   return res;
10098 }
10099 
10100 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 {
10101   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10102   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10103   return 1;
10104 }
10105 
10106 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10107   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10108 }
10109 
10110 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10111   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10112   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10113   float res = void;
10114   forceNoThrowNoGC({
10115     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10116     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10117   });
10118   /*
10119   if (res != 0) {
10120     { 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)); }
10121   }
10122   */
10123   //k8: dunno if this is right; i guess it isn't but...
10124   return res;
10125 }
10126 
10127 // old arsd.ttf sux! ;-)
10128 static if (is(typeof(STBTT_vcubic))) {
10129 
10130 static struct OutlinerData {
10131   @disable this (this);
10132   void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
10133   NVGPathOutline.DataStore* ol;
10134 nothrow @trusted @nogc:
10135   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10136   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10137 }
10138 
10139 
10140 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10141   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10142 
10143   bool okflag = false;
10144 
10145   forceNoThrowNoGC({
10146     int x0, y0, x1, y1;
10147     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10148       bounds[] = 0;
10149       return;
10150     }
10151 
10152     if (bounds.length > 0) bounds.ptr[0] = x0;
10153     if (bounds.length > 1) bounds.ptr[1] = -y1;
10154     if (bounds.length > 2) bounds.ptr[2] = x1;
10155     if (bounds.length > 3) bounds.ptr[3] = -y0;
10156 
10157     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10158 
10159     stbtt_vertex* verts = null;
10160     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10161     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10162     if (vcount < 1) return;
10163 
10164     foreach (const ref vt; verts[0..vcount]) {
10165       switch (vt.type) {
10166         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10167         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10168         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10169         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10170         default:
10171       }
10172     }
10173 
10174     okflag = true;
10175   });
10176 
10177   return okflag;
10178 }
10179 
10180 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10181   bool okflag = false;
10182 
10183   forceNoThrowNoGC({
10184     int x0, y0, x1, y1;
10185 
10186     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10187       ol.bounds[] = 0;
10188       return;
10189     }
10190 
10191     ol.bounds.ptr[0] = x0;
10192     ol.bounds.ptr[1] = -y1;
10193     ol.bounds.ptr[2] = x1;
10194     ol.bounds.ptr[3] = -y0;
10195 
10196     stbtt_vertex* verts = null;
10197     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10198     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10199     if (vcount < 1) return;
10200 
10201     OutlinerData odata;
10202     odata.ol = ol;
10203 
10204     foreach (const ref vt; verts[0..vcount]) {
10205       switch (vt.type) {
10206         case STBTT_vmove:
10207           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10208           odata.ol.putArgs(odata.transx(vt.x));
10209           odata.ol.putArgs(odata.transy(vt.y));
10210           break;
10211         case STBTT_vline:
10212           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10213           odata.ol.putArgs(odata.transx(vt.x));
10214           odata.ol.putArgs(odata.transy(vt.y));
10215           break;
10216         case STBTT_vcurve:
10217           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10218           odata.ol.putArgs(odata.transx(vt.x));
10219           odata.ol.putArgs(odata.transy(vt.y));
10220           odata.ol.putArgs(odata.transx(vt.cx));
10221           odata.ol.putArgs(odata.transy(vt.cy));
10222           break;
10223         case STBTT_vcubic:
10224           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
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           odata.ol.putArgs(odata.transx(vt.cx1));
10230           odata.ol.putArgs(odata.transy(vt.cy1));
10231           break;
10232         default:
10233       }
10234     }
10235 
10236     okflag = true;
10237   });
10238 
10239   return okflag;
10240 }
10241 
10242 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10243   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10244 
10245   bool okflag = false;
10246 
10247   forceNoThrowNoGC({
10248     int x0, y0, x1, y1;
10249     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10250       if (bounds.length > 0) bounds.ptr[0] = x0;
10251       if (bounds.length > 1) bounds.ptr[1] = -y1;
10252       if (bounds.length > 2) bounds.ptr[2] = x1;
10253       if (bounds.length > 3) bounds.ptr[3] = -y0;
10254       okflag = true;
10255     } else {
10256       bounds[] = 0;
10257     }
10258   });
10259 
10260   return okflag;
10261 }
10262 
10263 } // check for old stb_ttf
10264 
10265 
10266 } // version
10267 
10268 
10269 // ////////////////////////////////////////////////////////////////////////// //
10270 private:
10271 enum FONS_SCRATCH_BUF_SIZE = 64000;
10272 enum FONS_HASH_LUT_SIZE = 256;
10273 enum FONS_INIT_FONTS = 4;
10274 enum FONS_INIT_GLYPHS = 256;
10275 enum FONS_INIT_ATLAS_NODES = 256;
10276 enum FONS_VERTEX_COUNT = 1024;
10277 enum FONS_MAX_STATES = 20;
10278 enum FONS_MAX_FALLBACKS = 20;
10279 
10280 
10281 struct FONSglyph {
10282   uint codepoint;
10283   int index;
10284   int next;
10285   short size, blur;
10286   short x0, y0, x1, y1;
10287   short xadv, xoff, yoff;
10288 }
10289 
10290 // refcounted
10291 struct FONSfontData {
10292   ubyte* data;
10293   int dataSize;
10294   bool freeData;
10295   int rc;
10296 
10297   @disable this (this); // no copies
10298   void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); }
10299 }
10300 
10301 // won't set rc to 1
10302 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10303   import core.stdc.stdlib : malloc;
10304   assert(adata !is null);
10305   assert(asize > 0);
10306   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10307   if (res is null) assert(0, "FONS: out of memory");
10308   res.data = adata;
10309   res.dataSize = asize;
10310   res.freeData = afree;
10311   res.rc = 0;
10312   return res;
10313 }
10314 
10315 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10316   pragma(inline, true);
10317   if (fd !is null) ++fd.rc;
10318 }
10319 
10320 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10321   if (fd !is null) {
10322     if (--fd.rc == 0) {
10323       import core.stdc.stdlib : free;
10324       if (fd.freeData && fd.data !is null) {
10325         free(fd.data);
10326         fd.data = null;
10327       }
10328       free(fd);
10329       fd = null;
10330     }
10331   }
10332 }
10333 
10334 // as creating and destroying fonts is a rare operation, malloc some data
10335 struct FONSfont {
10336   FONSttFontImpl font;
10337   char* name; // malloced, strz, always lowercase
10338   uint namelen;
10339   uint namehash;
10340   char* path; // malloced, strz
10341   FONSfontData* fdata;
10342   float ascender;
10343   float descender;
10344   float lineh;
10345   FONSglyph* glyphs;
10346   int cglyphs;
10347   int nglyphs;
10348   int[FONS_HASH_LUT_SIZE] lut;
10349   int[FONS_MAX_FALLBACKS] fallbacks;
10350   int nfallbacks;
10351 
10352   @disable this (this);
10353   void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); }
10354 
10355   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10356     uint hash = 5381;
10357     foreach (ubyte b; cast(const(ubyte)[])s) {
10358       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10359       hash = ((hash<<5)+hash)+b;
10360     }
10361     return hash;
10362   }
10363 
10364   // except glyphs
10365   void freeMemory () nothrow @trusted @nogc {
10366     import core.stdc.stdlib : free;
10367     if (name !is null) { free(name); name = null; }
10368     namelen = namehash = 0;
10369     if (path !is null) { free(path); path = null; }
10370     fdata.decref();
10371   }
10372 
10373   // this also calcs name hash
10374   void setName (const(char)[] aname) nothrow @trusted @nogc {
10375     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10376     import core.stdc.stdlib : realloc;
10377     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10378     namelen = cast(uint)aname.length;
10379     name = cast(char*)realloc(name, namelen+1);
10380     if (name is null) assert(0, "FONS: out of memory");
10381     if (aname.length) name[0..aname.length] = aname[];
10382     name[namelen] = 0;
10383     // lowercase it
10384     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10385     namehash = djbhash(name[0..namelen]);
10386     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10387   }
10388 
10389   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10390     import core.stdc.stdlib : realloc;
10391     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10392     path = cast(char*)realloc(path, apath.length+1);
10393     if (path is null) assert(0, "FONS: out of memory");
10394     if (apath.length) path[0..apath.length] = apath[];
10395     path[apath.length] = 0;
10396   }
10397 
10398   // this won't check hash
10399   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10400     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10401     if (namelen != aname.length) return false;
10402     const(char)* ns = name;
10403     // name part
10404     foreach (char ch; aname) {
10405       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10406       if (ch != *ns++) return false;
10407     }
10408     // done (length was checked earlier)
10409     return true;
10410   }
10411 
10412   void clear () nothrow @trusted @nogc {
10413     import core.stdc.stdlib : free;
10414     import core.stdc.string : memset;
10415     if (glyphs !is null) free(glyphs);
10416     freeMemory();
10417     memset(&this, 0, this.sizeof);
10418   }
10419 
10420   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10421     if (nglyphs+1 > cglyphs) {
10422       import core.stdc.stdlib : realloc;
10423       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10424       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10425       if (glyphs is null) assert(0, "FontStash: out of memory");
10426     }
10427     ++nglyphs;
10428     return &glyphs[nglyphs-1];
10429   }
10430 }
10431 
10432 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10433   if (font !is null) {
10434     import core.stdc.stdlib : free;
10435     font.clear();
10436     free(font);
10437     font = null;
10438   }
10439 }
10440 
10441 
10442 // ////////////////////////////////////////////////////////////////////////// //
10443 struct FONSstate {
10444   int font;
10445   NVGTextAlign talign;
10446   float size = 0;
10447   float blur = 0;
10448   float spacing = 0;
10449 }
10450 
10451 
10452 // ////////////////////////////////////////////////////////////////////////// //
10453 // atlas based on Skyline Bin Packer by Jukka Jylänki
10454 alias FONSAtlas = FONSatlasInternal*;
10455 
10456 struct FONSatlasInternal {
10457   static struct Node {
10458     short x, y, width;
10459   }
10460 
10461   int width, height;
10462   Node* nodes;
10463   int nnodes;
10464   int cnodes;
10465 
10466   @disable this (this);
10467   void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10468 
10469 nothrow @trusted @nogc:
10470   static FONSAtlas create (int w, int h, int nnodes) {
10471     import core.stdc.stdlib : malloc;
10472     import core.stdc.string : memset;
10473 
10474     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10475     if (atlas is null) assert(0, "FontStash: out of memory");
10476     memset(atlas, 0, FONSatlasInternal.sizeof);
10477 
10478     atlas.width = w;
10479     atlas.height = h;
10480 
10481     // allocate space for skyline nodes
10482     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10483     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10484     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10485     atlas.nnodes = 0;
10486     atlas.cnodes = nnodes;
10487 
10488     // init root node
10489     atlas.nodes[0].x = 0;
10490     atlas.nodes[0].y = 0;
10491     atlas.nodes[0].width = cast(short)w;
10492     ++atlas.nnodes;
10493 
10494     return atlas;
10495   }
10496 
10497   void clear () {
10498     import core.stdc.stdlib : free;
10499     import core.stdc.string : memset;
10500 
10501     if (nodes !is null) free(nodes);
10502     memset(&this, 0, this.sizeof);
10503   }
10504 
10505   void insertNode (int idx, int x, int y, int w) {
10506     if (nnodes+1 > cnodes) {
10507       import core.stdc.stdlib : realloc;
10508       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10509       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10510       if (nodes is null) assert(0, "FontStash: out of memory");
10511     }
10512     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10513     nodes[idx].x = cast(short)x;
10514     nodes[idx].y = cast(short)y;
10515     nodes[idx].width = cast(short)w;
10516     ++nnodes;
10517   }
10518 
10519   void removeNode (int idx) {
10520     if (nnodes == 0) return;
10521     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10522     --nnodes;
10523   }
10524 
10525   // insert node for empty space
10526   void expand (int w, int h) {
10527     if (w > width) insertNode(nnodes, width, 0, w-width);
10528     width = w;
10529     height = h;
10530   }
10531 
10532   void reset (int w, int h) {
10533     width = w;
10534     height = h;
10535     nnodes = 0;
10536     // init root node
10537     nodes[0].x = 0;
10538     nodes[0].y = 0;
10539     nodes[0].width = cast(short)w;
10540     ++nnodes;
10541   }
10542 
10543   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10544     insertNode(idx, x, y+h, w);
10545 
10546     // delete skyline segments that fall under the shadow of the new segment
10547     for (int i = idx+1; i < nnodes; ++i) {
10548       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10549         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10550         nodes[i].x += cast(short)shrink;
10551         nodes[i].width -= cast(short)shrink;
10552         if (nodes[i].width <= 0) {
10553           removeNode(i);
10554           --i;
10555         } else {
10556           break;
10557         }
10558       } else {
10559         break;
10560       }
10561     }
10562 
10563     // Merge same height skyline segments that are next to each other
10564     for (int i = 0; i < nnodes-1; ++i) {
10565       if (nodes[i].y == nodes[i+1].y) {
10566         nodes[i].width += nodes[i+1].width;
10567         removeNode(i+1);
10568         --i;
10569       }
10570     }
10571   }
10572 
10573   // checks if there is enough space at the location of skyline span 'i',
10574   // and return the max height of all skyline spans under that at that location,
10575   // (think tetris block being dropped at that position); or -1 if no space found
10576   int rectFits (int i, int w, int h) {
10577     int x = nodes[i].x;
10578     int y = nodes[i].y;
10579     if (x+w > width) return -1;
10580     int spaceLeft = w;
10581     while (spaceLeft > 0) {
10582       if (i == nnodes) return -1;
10583       y = nvg__max(y, nodes[i].y);
10584       if (y+h > height) return -1;
10585       spaceLeft -= nodes[i].width;
10586       ++i;
10587     }
10588     return y;
10589   }
10590 
10591   bool addRect (int rw, int rh, int* rx, int* ry) {
10592     int besth = height, bestw = width, besti = -1;
10593     int bestx = -1, besty = -1;
10594 
10595     // Bottom left fit heuristic.
10596     for (int i = 0; i < nnodes; ++i) {
10597       int y = rectFits(i, rw, rh);
10598       if (y != -1) {
10599         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10600           besti = i;
10601           bestw = nodes[i].width;
10602           besth = y+rh;
10603           bestx = nodes[i].x;
10604           besty = y;
10605         }
10606       }
10607     }
10608 
10609     if (besti == -1) return false;
10610 
10611     // perform the actual packing
10612     addSkylineLevel(besti, bestx, besty, rw, rh);
10613 
10614     *rx = bestx;
10615     *ry = besty;
10616 
10617     return true;
10618   }
10619 }
10620 
10621 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10622   if (atlas !is null) {
10623     import core.stdc.stdlib : free;
10624     atlas.clear();
10625     free(atlas);
10626     atlas = null;
10627   }
10628 }
10629 
10630 
10631 // ////////////////////////////////////////////////////////////////////////// //
10632 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10633 /// Group: font_stash
10634 public struct FONScontextInternal {
10635 private:
10636   FONSParams params;
10637   float itw, ith;
10638   ubyte* texData;
10639   int[4] dirtyRect;
10640   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10641   int cfonts; // allocated
10642   int nfonts; // used (so we can track hash table stats)
10643   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10644   int hused, hsize;// used items and total items in [hashidx]
10645   FONSAtlas atlas;
10646   ubyte* scratch;
10647   int nscratch;
10648   FONSstate[FONS_MAX_STATES] states;
10649   int nstates;
10650 
10651   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10652 
10653   @disable this (this);
10654   void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10655 
10656 private:
10657   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10658     if (s0.length != s1.length) return false;
10659     const(char)* sp0 = s0.ptr;
10660     const(char)* sp1 = s1.ptr;
10661     foreach (immutable _; 0..s0.length) {
10662       char c0 = *sp0++;
10663       char c1 = *sp1++;
10664       if (c0 != c1) {
10665         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10666         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10667         if (c0 != c1) return false;
10668       }
10669     }
10670     return true;
10671   }
10672 
10673   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc {
10674     pragma(inline, true);
10675     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10676   }
10677 
10678   // simple linear probing; returns [FONS_INVALID] if not found
10679   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10680     if (nfonts == 0) return FONS_INVALID;
10681     auto nhash = FONSfont.djbhash(name);
10682     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10683     auto res = nhash%hsize;
10684     // hash will never be 100% full, so this loop is safe
10685     for (;;) {
10686       int idx = hashidx[res];
10687       if (idx == -1) break;
10688       auto font = fonts[idx];
10689       if (font is null) assert(0, "FONS internal error");
10690       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10691       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10692       res = (res+1)%hsize;
10693     }
10694     return FONS_INVALID;
10695   }
10696 
10697   // should be called $(B before) freeing `fonts[fidx]`
10698   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10699     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10700     if (fonts[fidx] is null) assert(0, "FONS internal error");
10701     if (hused != nfonts) assert(0, "FONS internal error");
10702     auto nhash = fonts[fidx].namehash;
10703     auto res = nhash%hsize;
10704     // hash will never be 100% full, so this loop is safe
10705     for (;;) {
10706       int idx = hashidx[res];
10707       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10708       if (idx == fidx) {
10709         // i found her! copy rest here
10710         int nidx = (res+1)%hsize;
10711         for (;;) {
10712           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10713           res = nidx;
10714           nidx = (nidx+1)%hsize;
10715         }
10716         return;
10717       }
10718       res = (res+1)%hsize;
10719     }
10720   }
10721 
10722   // add font with the given index to hash
10723   // prerequisite: font should not exists in hash
10724   void addIndexToHash (int idx) nothrow @trusted @nogc {
10725     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10726     if (fonts[idx] is null) assert(0, "FONS internal error");
10727     import core.stdc.stdlib : realloc;
10728     auto nhash = fonts[idx].namehash;
10729     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10730     // allocate new hash table if there was none
10731     if (hsize == 0) {
10732       enum InitSize = 256;
10733       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10734       if (newlist is null) assert(0, "FONS: out of memory");
10735       newlist[0..InitSize] = -1;
10736       hsize = InitSize;
10737       hused = 0;
10738       hashidx = newlist;
10739     }
10740     int res = cast(int)(nhash%hsize);
10741     // need to rehash? we want our hash table 50% full at max
10742     if (hashidx[res] != -1 && hused >= hsize/2) {
10743       uint nsz = hsize*2;
10744       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10745       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10746       if (newlist is null) assert(0, "FONS: out of memory");
10747       newlist[0..nsz] = -1;
10748       hused = 0;
10749       // rehash
10750       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10751         if (ff is null) continue;
10752         // find slot for this font (guaranteed to have one)
10753         uint newslot = ff.namehash%nsz;
10754         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10755         newlist[newslot] = cast(int)fidx;
10756         ++hused;
10757       }
10758       hsize = nsz;
10759       hashidx = newlist;
10760       // we added everything, including [idx], so nothing more to do here
10761     } else {
10762       // find slot (guaranteed to have one)
10763       while (hashidx[res] != -1) res = (res+1)%hsize;
10764       // i found her!
10765       hashidx[res] = idx;
10766       ++hused;
10767     }
10768   }
10769 
10770   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10771     int gx, gy;
10772     ubyte* dst;
10773 
10774     if (!atlas.addRect(w, h, &gx, &gy)) return;
10775 
10776     // Rasterize
10777     dst = &texData[gx+gy*params.width];
10778     foreach (int y; 0..h) {
10779       foreach (int x; 0..w) {
10780         dst[x] = 0xff;
10781       }
10782       dst += params.width;
10783     }
10784 
10785     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10786     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10787     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10788     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10789   }
10790 
10791   // returns fid, not hash slot
10792   int allocFontAt (int atidx) nothrow @trusted @nogc {
10793     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10794 
10795     if (atidx < 0) {
10796       if (nfonts >= cfonts) {
10797         import core.stdc.stdlib : realloc;
10798         import core.stdc.string : memset;
10799         assert(nfonts == cfonts);
10800         int newsz = cfonts+64;
10801         if (newsz > 65535) assert(0, "FONS: too many fonts");
10802         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10803         if (newlist is null) assert(0, "FONS: out of memory");
10804         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10805         fonts = newlist;
10806         cfonts = newsz;
10807       }
10808       assert(nfonts < cfonts);
10809     }
10810 
10811     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10812     if (font is null) assert(0, "FONS: out of memory");
10813     memset(font, 0, FONSfont.sizeof);
10814 
10815     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10816     if (font.glyphs is null) assert(0, "FONS: out of memory");
10817     font.cglyphs = FONS_INIT_GLYPHS;
10818     font.nglyphs = 0;
10819 
10820     if (atidx < 0) {
10821       fonts[nfonts] = font;
10822       return nfonts++;
10823     } else {
10824       fonts[atidx] = font;
10825       return atidx;
10826     }
10827   }
10828 
10829   // 0: ooops
10830   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10831     if (renderfont !is null) *renderfont = font;
10832     if (font is null || font.fdata is null) return 0;
10833     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10834     // try to find the glyph in fallback fonts
10835     if (g == 0) {
10836       foreach (immutable i; 0..font.nfallbacks) {
10837         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10838         if (fallbackFont !is null) {
10839           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10840           if (fallbackIndex != 0) {
10841             if (renderfont !is null) *renderfont = fallbackFont;
10842             return g;
10843           }
10844         }
10845       }
10846       // no char, try to find replacement one
10847       if (dch != 0xFFFD) {
10848         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10849         if (g == 0) {
10850           foreach (immutable i; 0..font.nfallbacks) {
10851             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10852             if (fallbackFont !is null) {
10853               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10854               if (fallbackIndex != 0) {
10855                 if (renderfont !is null) *renderfont = fallbackFont;
10856                 return g;
10857               }
10858             }
10859           }
10860         }
10861       }
10862     }
10863     return g;
10864   }
10865 
10866   void clear () nothrow @trusted @nogc {
10867     import core.stdc.stdlib : free;
10868 
10869     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10870     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10871 
10872     if (atlas !is null) atlas.kill();
10873     if (fonts !is null) free(fonts);
10874     if (texData !is null) free(texData);
10875     if (scratch !is null) free(scratch);
10876     if (hashidx !is null) free(hashidx);
10877   }
10878 
10879   // add font from another fontstash
10880   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10881     if (font is null || font.fdata is null) return FONS_INVALID;
10882     font.fdata.incref();
10883     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10884     if (res == FONS_INVALID) font.fdata.decref(); // oops
10885     return res;
10886   }
10887 
10888   // fdata refcount must be already increased; it won't be changed
10889   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10890     int i, ascent, descent, fh, lineGap;
10891 
10892     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10893     if (name.length > 32767) return FONS_INVALID;
10894     if (fdata is null) return FONS_INVALID;
10895 
10896     // find a font with the given name
10897     int newidx;
10898     FONSfont* oldfont = null;
10899     int oldidx = findNameInHash(name);
10900     if (oldidx != FONS_INVALID) {
10901       // replacement font
10902       oldfont = fonts[oldidx];
10903       newidx = oldidx;
10904     } else {
10905       // new font, allocate new bucket
10906       newidx = -1;
10907     }
10908 
10909     newidx = allocFontAt(newidx);
10910     FONSfont* font = fonts[newidx];
10911     font.setName(name);
10912     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10913     font.fdata = fdata; // set the font data (don't change reference count)
10914     fons__tt_setMono(&this, &font.font, !defAA);
10915 
10916     // init font
10917     nscratch = 0;
10918     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10919       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10920       font.fdata = null;
10921       font.kill();
10922       if (oldidx != FONS_INVALID) {
10923         assert(oldidx == newidx);
10924         fonts[oldidx] = oldfont;
10925       } else {
10926         assert(newidx == nfonts-1);
10927         fonts[newidx] = null;
10928         --nfonts;
10929       }
10930       return FONS_INVALID;
10931     } else {
10932       // free old font data, if any
10933       if (oldfont !is null) oldfont.kill();
10934     }
10935 
10936     // add font to name hash
10937     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10938 
10939     // store normalized line height
10940     // the real line height is got by multiplying the lineh by font size
10941     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10942     fh = ascent-descent;
10943     font.ascender = cast(float)ascent/cast(float)fh;
10944     font.descender = cast(float)descent/cast(float)fh;
10945     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10946 
10947     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10948     return newidx;
10949   }
10950 
10951   // isize: size*10
10952   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10953     if (params.isZeroTopLeft) {
10954       final switch (talign.vertical) {
10955         case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10956         case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10957         case NVGTextAlign.V.Baseline: return 0.0f;
10958         case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10959       }
10960     } else {
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     }
10968     assert(0);
10969   }
10970 
10971 public:
10972   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10973    *
10974    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10975    * FontStash only to measure text), you can simply pass `FONSParams.init`).
10976    */
10977   static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc {
10978     import core.stdc.string : memcpy;
10979 
10980     FONSContext stash = null;
10981 
10982     // allocate memory for the font stash
10983     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10984     if (stash is null) goto error;
10985     memset(stash, 0, FONScontextInternal.sizeof);
10986 
10987     memcpy(&stash.params, &params, params.sizeof);
10988     if (stash.params.width < 1) stash.params.width = 32;
10989     if (stash.params.height < 1) stash.params.height = 32;
10990 
10991     // allocate scratch buffer
10992     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
10993     if (stash.scratch is null) goto error;
10994 
10995     // initialize implementation library
10996     if (!fons__tt_init(stash)) goto error;
10997 
10998     if (stash.params.renderCreate !is null) {
10999       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
11000     }
11001 
11002     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
11003     if (stash.atlas is null) goto error;
11004 
11005     // don't allocate space for fonts: hash manager will do that for us later
11006     //stash.cfonts = 0;
11007     //stash.nfonts = 0;
11008 
11009     // create texture for the cache
11010     stash.itw = 1.0f/stash.params.width;
11011     stash.ith = 1.0f/stash.params.height;
11012     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
11013     if (stash.texData is null) goto error;
11014     memset(stash.texData, 0, stash.params.width*stash.params.height);
11015 
11016     stash.dirtyRect.ptr[0] = stash.params.width;
11017     stash.dirtyRect.ptr[1] = stash.params.height;
11018     stash.dirtyRect.ptr[2] = 0;
11019     stash.dirtyRect.ptr[3] = 0;
11020 
11021     // add white rect at 0, 0 for debug drawing
11022     stash.addWhiteRect(2, 2);
11023 
11024     stash.pushState();
11025     stash.clearState();
11026 
11027     return stash;
11028 
11029   error:
11030     stash.kill();
11031     return null;
11032   }
11033 
11034 public:
11035   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11036   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11037     FONSfont* baseFont = fonts[base];
11038     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11039       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11040       return true;
11041     }
11042     return false;
11043   }
11044 
11045   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11046   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11047 
11048   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11049   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11050 
11051   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11052   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11053 
11054   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11055   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11056 
11057   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11058   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11059 
11060   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11061 
11062   /// Check if FontStash has a font with the given name loaded.
11063   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11064 
11065   /// Get AA for the current font, or for the specified font.
11066   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11067     FONSstate* state = getState;
11068     if (font < 0) font = state.font;
11069     if (font < 0 || font >= nfonts) return false;
11070     FONSfont* f = fonts[font];
11071     return (f !is null ? !f.font.mono : false);
11072   }
11073 
11074   /// Push current state. Returns `false` if state stack overflowed.
11075   bool pushState () nothrow @trusted @nogc {
11076     if (nstates >= FONS_MAX_STATES) {
11077       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11078       return false;
11079     }
11080     if (nstates > 0) {
11081       import core.stdc.string : memcpy;
11082       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11083     }
11084     ++nstates;
11085     return true;
11086   }
11087 
11088   /// Pop current state. Returns `false` if state stack underflowed.
11089   bool popState () nothrow @trusted @nogc {
11090     if (nstates <= 1) {
11091       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11092       return false;
11093     }
11094     --nstates;
11095     return true;
11096   }
11097 
11098   /// Clear current state (i.e. set it to some sane defaults).
11099   void clearState () nothrow @trusted @nogc {
11100     FONSstate* state = getState;
11101     state.size = 12.0f;
11102     state.font = 0;
11103     state.blur = 0;
11104     state.spacing = 0;
11105     state.talign.reset;
11106   }
11107 
11108   private enum NoAlias = ":noaa";
11109 
11110   /** Add font to FontStash.
11111    *
11112    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11113    * with same name and path several times, FontStash will load it only once. Also, you can
11114    * load new disk font for any existing logical font.
11115    *
11116    * Params:
11117    *   name = logical font name, that will be used to select this font later.
11118    *   path = path to disk file with your font.
11119    *   defAA = should FontStash use antialiased font rasterizer?
11120    *
11121    * Returns:
11122    *   font id or [FONS_INVALID].
11123    */
11124   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11125     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11126     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11127 
11128     // if font path ends with ":noaa", turn off antialiasing
11129     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11130       path = path[0..$-NoAlias.length];
11131       if (path.length == 0) return FONS_INVALID;
11132       defAA = false;
11133     }
11134 
11135     // if font name ends with ":noaa", turn off antialiasing
11136     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11137       name = name[0..$-NoAlias.length];
11138       defAA = false;
11139     }
11140 
11141     // find a font with the given name
11142     int fidx = findNameInHash(name);
11143     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11144 
11145     int loadFontFile (const(char)[] path) {
11146       // check if existing font (if any) has the same path
11147       if (fidx >= 0) {
11148         import core.stdc.string : strlen;
11149         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11150         version(Posix) {
11151           //{ 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); }
11152           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11153             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11154             // i found her!
11155             return fidx;
11156           }
11157         } else {
11158           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11159             // i found her!
11160             return fidx;
11161           }
11162         }
11163       }
11164       version(Windows) {
11165         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11166         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11167       }
11168       // either no such font, or different path
11169       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11170       int xres = FONS_INVALID;
11171       try {
11172         import core.stdc.stdlib : free, malloc;
11173         static if (NanoVegaHasIVVFS) {
11174           auto fl = VFile(path);
11175           auto dataSize = fl.size;
11176           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11177           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11178           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11179           scope(failure) free(data); // oops
11180           fl.rawReadExact(data[0..cast(uint)dataSize]);
11181           fl.close();
11182         } else {
11183           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11184           import std.internal.cstring : tempCString;
11185           auto fl = fopen(path.tempCString, "rb");
11186           if (fl is null) return FONS_INVALID;
11187           scope(exit) fclose(fl);
11188           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11189           auto dataSize = ftell(fl);
11190           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11191           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11192           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11193           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11194           scope(failure) free(data); // oops
11195           ubyte* dptr = data;
11196           auto left = cast(uint)dataSize;
11197           while (left > 0) {
11198             auto rd = fread(dptr, 1, left, fl);
11199             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11200             dptr += rd;
11201             left -= rd;
11202           }
11203         }
11204         scope(failure) free(data); // oops
11205         // create font data
11206         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11207         fdata.incref();
11208         xres = addFontWithData(name, fdata, defAA);
11209         if (xres == FONS_INVALID) {
11210           fdata.decref(); // this will free [data] and [fdata]
11211         } else {
11212           // remember path
11213           fonts[xres].setPath(path);
11214         }
11215       } catch (Exception e) {
11216         // oops; sorry
11217       }
11218       return xres;
11219     }
11220 
11221     // first try direct path
11222     auto res = loadFontFile(path);
11223     // if loading failed, try fontconfig (if fontconfig is available)
11224     static if (NanoVegaHasFontConfig) {
11225       if (res == FONS_INVALID && fontconfigAvailable) {
11226         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11227         bool ok = true;
11228         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11229         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11230         if (ok) {
11231           import std.internal.cstring : tempCString;
11232           FcPattern* pat = FcNameParse(path.tempCString);
11233           if (pat !is null) {
11234             scope(exit) FcPatternDestroy(pat);
11235             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11236               FcDefaultSubstitute(pat);
11237               // find the font
11238               FcResult result;
11239               FcPattern* font = FcFontMatch(null, pat, &result);
11240               if (font !is null) {
11241                 scope(exit) FcPatternDestroy(font);
11242                 char* file = null;
11243                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11244                   if (file !is null && file[0]) {
11245                     import core.stdc.string : strlen;
11246                     res = loadFontFile(file[0..strlen(file)]);
11247                   }
11248                 }
11249               }
11250             }
11251           }
11252         }
11253       }
11254     }
11255     return res;
11256   }
11257 
11258   /** Add font to FontStash, using data from memory.
11259    *
11260    * And already loaded font to FontStash. You can replace existing logical fonts.
11261    * But note that you can't remove logical font by passing "empty" data.
11262    *
11263    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11264    *
11265    * Params:
11266    *   name = logical font name, that will be used to select this font later.
11267    *   data = font data.
11268    *   dataSize = font data size.
11269    *   freeData = should FontStash take ownership of the font data?
11270    *   defAA = should FontStash use antialiased font rasterizer?
11271    *
11272    * Returns:
11273    *   font id or [FONS_INVALID].
11274    */
11275   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11276     if (data is null || dataSize < 16) return FONS_INVALID;
11277     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11278     fdata.incref();
11279     auto res = addFontWithData(name, fdata, defAA);
11280     if (res == FONS_INVALID) {
11281       // we promised to not free data on error
11282       fdata.freeData = false;
11283       fdata.decref(); // this will free [fdata]
11284     }
11285     return res;
11286   }
11287 
11288   /** Add fonts from another FontStash.
11289    *
11290    * This is more effective (and faster) than reloading fonts, because internally font data
11291    * is reference counted.
11292    */
11293   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11294     if (source is null) return;
11295     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11296       if (font !is null) {
11297         auto newidx = addCookedFont(font);
11298         FONSfont* newfont = fonts[newidx];
11299         assert(newfont !is null);
11300         assert(newfont.path is null);
11301         // copy path
11302         if (font.path !is null && font.path[0]) {
11303           import core.stdc.stdlib : malloc;
11304           import core.stdc.string : strcpy, strlen;
11305           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11306           if (newfont.path is null) assert(0, "FONS: out of memory");
11307           strcpy(newfont.path, font.path);
11308         }
11309       }
11310     }
11311   }
11312 
11313   /// Returns logical font name corresponding to the given font id, or `null`.
11314   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11315   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11316     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11317     return fonts[idx].name[0..fonts[idx].namelen];
11318   }
11319 
11320   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11321   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11322     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11323     // remove ":noaa" suffix
11324     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11325       name = name[0..$-NoAlias.length];
11326     }
11327     if (name.length == 0) return FONS_INVALID;
11328     return findNameInHash(name);
11329   }
11330 
11331   /** Measures the specified text string. Parameter bounds should be a float[4],
11332    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11333    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11334    */
11335   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11336     FONSstate* state = getState;
11337     uint codepoint;
11338     uint utf8state = 0;
11339     FONSQuad q;
11340     FONSglyph* glyph = null;
11341     int prevGlyphIndex = -1;
11342     short isize = cast(short)(state.size*10.0f);
11343     short iblur = cast(short)state.blur;
11344     FONSfont* font;
11345 
11346     if (state.font < 0 || state.font >= nfonts) return 0;
11347     font = fonts[state.font];
11348     if (font is null || font.fdata is null) return 0;
11349 
11350     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11351 
11352     // Align vertically.
11353     y += getVertAlign(font, state.talign, isize);
11354 
11355     float minx = x, maxx = x;
11356     float miny = y, maxy = y;
11357     float startx = x;
11358 
11359     foreach (T ch; str) {
11360       static if (T.sizeof == 1) {
11361         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11362         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11363         if (utf8state) continue;
11364       } else {
11365         static if (T.sizeof == 4) {
11366           if (ch > dchar.max) ch = 0xFFFD;
11367         }
11368         codepoint = cast(uint)ch;
11369       }
11370       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11371       if (glyph !is null) {
11372         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11373         if (q.x0 < minx) minx = q.x0;
11374         if (q.x1 > maxx) maxx = q.x1;
11375         if (params.isZeroTopLeft) {
11376           if (q.y0 < miny) miny = q.y0;
11377           if (q.y1 > maxy) maxy = q.y1;
11378         } else {
11379           if (q.y1 < miny) miny = q.y1;
11380           if (q.y0 > maxy) maxy = q.y0;
11381         }
11382         prevGlyphIndex = glyph.index;
11383       } else {
11384         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11385         prevGlyphIndex = -1;
11386       }
11387     }
11388 
11389     float advance = x-startx;
11390     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11391 
11392     // Align horizontally
11393     if (state.talign.left) {
11394       // empty
11395     } else if (state.talign.right) {
11396       minx -= advance;
11397       maxx -= advance;
11398     } else if (state.talign.center) {
11399       minx -= advance*0.5f;
11400       maxx -= advance*0.5f;
11401     }
11402 
11403     if (bounds.length) {
11404       if (bounds.length > 0) bounds.ptr[0] = minx;
11405       if (bounds.length > 1) bounds.ptr[1] = miny;
11406       if (bounds.length > 2) bounds.ptr[2] = maxx;
11407       if (bounds.length > 3) bounds.ptr[3] = maxy;
11408     }
11409 
11410     return advance;
11411   }
11412 
11413   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11414   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11415     FONSstate* state = getState;
11416     if (state.font < 0 || state.font >= nfonts) {
11417       if (ascender !is null) *ascender = 0;
11418       if (descender !is null) *descender = 0;
11419       if (lineh !is null) *lineh = 0;
11420     } else {
11421       FONSfont* font = fonts[state.font];
11422       if (font is null || font.fdata is null) {
11423         if (ascender !is null) *ascender = 0;
11424         if (descender !is null) *descender = 0;
11425         if (lineh !is null) *lineh = 0;
11426       } else {
11427         short isize = cast(short)(state.size*10.0f);
11428         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11429         if (descender !is null) *descender = font.descender*isize/10.0f;
11430         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11431       }
11432     }
11433   }
11434 
11435   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11436   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11437     FONSfont* font;
11438     FONSstate* state = getState;
11439     short isize;
11440 
11441     if (minyp !is null) *minyp = 0;
11442     if (maxyp !is null) *maxyp = 0;
11443 
11444     if (state.font < 0 || state.font >= nfonts) return;
11445     font = fonts[state.font];
11446     isize = cast(short)(state.size*10.0f);
11447     if (font is null || font.fdata is null) return;
11448 
11449     y += getVertAlign(font, state.talign, isize);
11450 
11451     if (params.isZeroTopLeft) {
11452       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11453       immutable float maxy = miny+font.lineh*isize/10.0f;
11454       if (minyp !is null) *minyp = miny;
11455       if (maxyp !is null) *maxyp = maxy;
11456     } else {
11457       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11458       immutable float miny = maxy-font.lineh*isize/10.0f;
11459       if (minyp !is null) *minyp = miny;
11460       if (maxyp !is null) *maxyp = maxy;
11461     }
11462   }
11463 
11464   /// Returns font line height.
11465   float fontHeight () nothrow @trusted @nogc {
11466     float res = void;
11467     getVertMetrics(null, null, &res);
11468     return res;
11469   }
11470 
11471   /// Returns font ascender (positive).
11472   float fontAscender () nothrow @trusted @nogc {
11473     float res = void;
11474     getVertMetrics(&res, null, null);
11475     return res;
11476   }
11477 
11478   /// Returns font descender (negative).
11479   float fontDescender () nothrow @trusted @nogc {
11480     float res = void;
11481     getVertMetrics(null, &res, null);
11482     return res;
11483   }
11484 
11485   //TODO: document this
11486   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11487     if (width !is null) *width = params.width;
11488     if (height !is null) *height = params.height;
11489     return texData;
11490   }
11491 
11492   //TODO: document this
11493   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11494     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11495       dirty[0] = dirtyRect.ptr[0];
11496       dirty[1] = dirtyRect.ptr[1];
11497       dirty[2] = dirtyRect.ptr[2];
11498       dirty[3] = dirtyRect.ptr[3];
11499       // reset dirty rect
11500       dirtyRect.ptr[0] = params.width;
11501       dirtyRect.ptr[1] = params.height;
11502       dirtyRect.ptr[2] = 0;
11503       dirtyRect.ptr[3] = 0;
11504       return true;
11505     }
11506     return false;
11507   }
11508 
11509   //TODO: document this
11510   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11511     handleError = callback;
11512   }
11513 
11514   //TODO: document this
11515   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11516     if (width !is null) *width = params.width;
11517     if (height !is null) *height = params.height;
11518   }
11519 
11520   //TODO: document this
11521   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11522     import core.stdc.stdlib : free;
11523     import core.stdc.string : memcpy, memset;
11524 
11525     int maxy = 0;
11526     ubyte* data = null;
11527 
11528     width = nvg__max(width, params.width);
11529     height = nvg__max(height, params.height);
11530 
11531     if (width == params.width && height == params.height) return true;
11532 
11533     // Flush pending glyphs.
11534     flush();
11535 
11536     // Create new texture
11537     if (params.renderResize !is null) {
11538       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11539     }
11540     // Copy old texture data over.
11541     data = cast(ubyte*)malloc(width*height);
11542     if (data is null) return 0;
11543     foreach (immutable int i; 0..params.height) {
11544       ubyte* dst = &data[i*width];
11545       ubyte* src = &texData[i*params.width];
11546       memcpy(dst, src, params.width);
11547       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11548     }
11549     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11550 
11551     free(texData);
11552     texData = data;
11553 
11554     // Increase atlas size
11555     atlas.expand(width, height);
11556 
11557     // Add existing data as dirty.
11558     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11559     dirtyRect.ptr[0] = 0;
11560     dirtyRect.ptr[1] = 0;
11561     dirtyRect.ptr[2] = params.width;
11562     dirtyRect.ptr[3] = maxy;
11563 
11564     params.width = width;
11565     params.height = height;
11566     itw = 1.0f/params.width;
11567     ith = 1.0f/params.height;
11568 
11569     return true;
11570   }
11571 
11572   //TODO: document this
11573   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11574     import core.stdc.stdlib : realloc;
11575     import core.stdc.string : memcpy, memset;
11576 
11577     // flush pending glyphs
11578     flush();
11579 
11580     // create new texture
11581     if (params.renderResize !is null) {
11582       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11583     }
11584 
11585     // reset atlas
11586     atlas.reset(width, height);
11587 
11588     // clear texture data
11589     texData = cast(ubyte*)realloc(texData, width*height);
11590     if (texData is null) assert(0, "FONS: out of memory");
11591     memset(texData, 0, width*height);
11592 
11593     // reset dirty rect
11594     dirtyRect.ptr[0] = width;
11595     dirtyRect.ptr[1] = height;
11596     dirtyRect.ptr[2] = 0;
11597     dirtyRect.ptr[3] = 0;
11598 
11599     // Reset cached glyphs
11600     foreach (FONSfont* font; fonts[0..nfonts]) {
11601       if (font !is null) {
11602         font.nglyphs = 0;
11603         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11604       }
11605     }
11606 
11607     params.width = width;
11608     params.height = height;
11609     itw = 1.0f/params.width;
11610     ith = 1.0f/params.height;
11611 
11612     // Add white rect at 0, 0 for debug drawing.
11613     addWhiteRect(2, 2);
11614 
11615     return true;
11616   }
11617 
11618   //TODO: document this
11619   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11620     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11621     static if (is(typeof(&fons__nvg__bounds))) {
11622       FONSstate* state = getState;
11623       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11624       FONSfont* font;
11625       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11626       if (g == 0) { bounds[] = 0; return false; }
11627       assert(font !is null);
11628       return fons__nvg__bounds(&font.font, g, bounds);
11629     } else {
11630       bounds[] = 0;
11631       return false;
11632     }
11633   }
11634 
11635   //TODO: document this
11636   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11637     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11638     static if (is(typeof(&fons__nvg__toPath))) {
11639       if (vg is null) { bounds[] = 0; return false; }
11640       FONSstate* state = getState;
11641       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11642       FONSfont* font;
11643       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11644       if (g == 0) { bounds[] = 0; return false; }
11645       assert(font !is null);
11646       return fons__nvg__toPath(vg, &font.font, g, bounds);
11647     } else {
11648       bounds[] = 0;
11649       return false;
11650     }
11651   }
11652 
11653   //TODO: document this
11654   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11655     if (ol is null) return false;
11656     static if (is(typeof(&fons__nvg__toOutline))) {
11657       FONSstate* state = getState;
11658       if (state.font < 0 || state.font >= nfonts) return false;
11659       FONSfont* font;
11660       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11661       if (g == 0) return false;
11662       assert(font !is null);
11663       return fons__nvg__toOutline(&font.font, g, ol);
11664     } else {
11665       return false;
11666     }
11667   }
11668 
11669   //TODO: document this
11670   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11671     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11672       pragma(inline, true);
11673       a += ~(a<<15);
11674       a ^=  (a>>10);
11675       a +=  (a<<3);
11676       a ^=  (a>>6);
11677       a += ~(a<<11);
11678       a ^=  (a>>16);
11679       return a;
11680     }
11681 
11682     // based on Exponential blur, Jani Huhtanen, 2006
11683     enum APREC = 16;
11684     enum ZPREC = 7;
11685 
11686     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11687       foreach (immutable int y; 0..h) {
11688         int z = 0; // force zero border
11689         foreach (int x; 1..w) {
11690           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11691           dst[x] = cast(ubyte)(z>>ZPREC);
11692         }
11693         dst[w-1] = 0; // force zero border
11694         z = 0;
11695         for (int x = w-2; x >= 0; --x) {
11696           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11697           dst[x] = cast(ubyte)(z>>ZPREC);
11698         }
11699         dst[0] = 0; // force zero border
11700         dst += dstStride;
11701       }
11702     }
11703 
11704     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11705       foreach (immutable int x; 0..w) {
11706         int z = 0; // force zero border
11707         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11708           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11709           dst[y] = cast(ubyte)(z>>ZPREC);
11710         }
11711         dst[(h-1)*dstStride] = 0; // force zero border
11712         z = 0;
11713         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11714           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11715           dst[y] = cast(ubyte)(z>>ZPREC);
11716         }
11717         dst[0] = 0; // force zero border
11718         ++dst;
11719       }
11720     }
11721 
11722     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11723       import std.math : expf = exp;
11724       if (blur < 1) return;
11725       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11726       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11727       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11728       fons__blurRows(dst, w, h, dstStride, alpha);
11729       fons__blurCols(dst, w, h, dstStride, alpha);
11730       fons__blurRows(dst, w, h, dstStride, alpha);
11731       fons__blurCols(dst, w, h, dstStride, alpha);
11732       //fons__blurrows(dst, w, h, dstStride, alpha);
11733       //fons__blurcols(dst, w, h, dstStride, alpha);
11734     }
11735 
11736     int advance, lsb, x0, y0, x1, y1, gx, gy;
11737     FONSglyph* glyph = null;
11738     float size = isize/10.0f;
11739     FONSfont* renderFont = font;
11740 
11741     if (isize < 2) return null;
11742     if (iblur > 20) iblur = 20;
11743     int pad = iblur+2;
11744 
11745     // Reset allocator.
11746     nscratch = 0;
11747 
11748     // Find code point and size.
11749     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11750     int i = font.lut.ptr[h];
11751     while (i != -1) {
11752       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11753       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11754         glyph = &font.glyphs[i];
11755         // Negative coordinate indicates there is no bitmap data created.
11756         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11757         // At this point, glyph exists but the bitmap data is not yet created.
11758         break;
11759       }
11760       i = font.glyphs[i].next;
11761     }
11762 
11763     // Create a new glyph or rasterize bitmap data for a cached glyph.
11764     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11765     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11766     // It is possible that we did not find a fallback glyph.
11767     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11768 
11769     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11770     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11771     int gw = x1-x0+pad*2;
11772     int gh = y1-y0+pad*2;
11773 
11774     // Determines the spot to draw glyph in the atlas.
11775     if (bitmapOption == FONSBitmapFlag.Required) {
11776       // Find free spot for the rect in the atlas.
11777       bool added = atlas.addRect(gw, gh, &gx, &gy);
11778       if (!added && handleError !is null) {
11779         // Atlas is full, let the user to resize the atlas (or not), and try again.
11780         handleError(FONSError.AtlasFull, 0);
11781         added = atlas.addRect(gw, gh, &gx, &gy);
11782       }
11783       if (!added) return null;
11784     } else {
11785       // Negative coordinate indicates there is no bitmap data created.
11786       gx = -1;
11787       gy = -1;
11788     }
11789 
11790     // Init glyph.
11791     if (glyph is null) {
11792       glyph = font.allocGlyph();
11793       glyph.codepoint = codepoint;
11794       glyph.size = isize;
11795       glyph.blur = iblur;
11796       glyph.next = 0;
11797 
11798       // Insert char to hash lookup.
11799       glyph.next = font.lut.ptr[h];
11800       font.lut.ptr[h] = font.nglyphs-1;
11801     }
11802     glyph.index = g;
11803     glyph.x0 = cast(short)gx;
11804     glyph.y0 = cast(short)gy;
11805     glyph.x1 = cast(short)(glyph.x0+gw);
11806     glyph.y1 = cast(short)(glyph.y0+gh);
11807     glyph.xadv = cast(short)(scale*advance*10.0f);
11808     glyph.xoff = cast(short)(x0-pad);
11809     glyph.yoff = cast(short)(y0-pad);
11810 
11811     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11812 
11813     // Rasterize
11814     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11815     fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11816 
11817     // Make sure there is one pixel empty border.
11818     dst = &texData[glyph.x0+glyph.y0*params.width];
11819     foreach (immutable int y; 0..gh) {
11820       dst[y*params.width] = 0;
11821       dst[gw-1+y*params.width] = 0;
11822     }
11823     foreach (immutable int x; 0..gw) {
11824       dst[x] = 0;
11825       dst[x+(gh-1)*params.width] = 0;
11826     }
11827 
11828     // Debug code to color the glyph background
11829     version(none) {
11830       foreach (immutable yy; 0..gh) {
11831         foreach (immutable xx; 0..gw) {
11832           int a = cast(int)dst[xx+yy*params.width]+42;
11833           if (a > 255) a = 255;
11834           dst[xx+yy*params.width] = cast(ubyte)a;
11835         }
11836       }
11837     }
11838 
11839     // Blur
11840     if (iblur > 0) {
11841       nscratch = 0;
11842       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11843       fons__blur(bdst, gw, gh, params.width, iblur);
11844     }
11845 
11846     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11847     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11848     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11849     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11850 
11851     return glyph;
11852   }
11853 
11854   //TODO: document this
11855   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11856     if (prevGlyphIndex >= 0) {
11857       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11858       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11859       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11860     }
11861 
11862     // Each glyph has 2px border to allow good interpolation,
11863     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11864     // Inset the texture region by one pixel for correct interpolation.
11865     immutable float xoff = cast(short)(glyph.xoff+1);
11866     immutable float yoff = cast(short)(glyph.yoff+1);
11867     immutable float x0 = cast(float)(glyph.x0+1);
11868     immutable float y0 = cast(float)(glyph.y0+1);
11869     immutable float x1 = cast(float)(glyph.x1-1);
11870     immutable float y1 = cast(float)(glyph.y1-1);
11871 
11872     if (params.isZeroTopLeft) {
11873       immutable float rx = cast(float)cast(int)(*x+xoff);
11874       immutable float ry = cast(float)cast(int)(*y+yoff);
11875 
11876       q.x0 = rx;
11877       q.y0 = ry;
11878       q.x1 = rx+x1-x0;
11879       q.y1 = ry+y1-y0;
11880 
11881       q.s0 = x0*itw;
11882       q.t0 = y0*ith;
11883       q.s1 = x1*itw;
11884       q.t1 = y1*ith;
11885     } else {
11886       immutable float rx = cast(float)cast(int)(*x+xoff);
11887       immutable float ry = cast(float)cast(int)(*y-yoff);
11888 
11889       q.x0 = rx;
11890       q.y0 = ry;
11891       q.x1 = rx+x1-x0;
11892       q.y1 = ry-y1+y0;
11893 
11894       q.s0 = x0*itw;
11895       q.t0 = y0*ith;
11896       q.s1 = x1*itw;
11897       q.t1 = y1*ith;
11898     }
11899 
11900     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11901   }
11902 
11903   void flush () nothrow @trusted @nogc {
11904     // flush texture
11905     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11906       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11907       // reset dirty rect
11908       dirtyRect.ptr[0] = params.width;
11909       dirtyRect.ptr[1] = params.height;
11910       dirtyRect.ptr[2] = 0;
11911       dirtyRect.ptr[3] = 0;
11912     }
11913   }
11914 }
11915 
11916 /// Free all resources used by the `stash`, and `stash` itself.
11917 /// Group: font_stash
11918 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11919   import core.stdc.stdlib : free;
11920   if (stash is null) return;
11921   stash.clear();
11922   free(stash);
11923   stash = null;
11924 }
11925 
11926 
11927 // ////////////////////////////////////////////////////////////////////////// //
11928 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11929   ubyte* ptr;
11930   FONSContext stash = cast(FONSContext)up;
11931   // 16-byte align the returned pointer
11932   size = (size+0xf)&~0xf;
11933   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11934     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11935     return null;
11936   }
11937   ptr = stash.scratch+stash.nscratch;
11938   stash.nscratch += cast(int)size;
11939   return ptr;
11940 }
11941 
11942 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11943   // empty
11944 }
11945 
11946 
11947 // ////////////////////////////////////////////////////////////////////////// //
11948 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11949 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11950 
11951 enum FONS_UTF8_ACCEPT = 0;
11952 enum FONS_UTF8_REJECT = 12;
11953 
11954 static immutable ubyte[364] utf8d = [
11955   // The first part of the table maps bytes to character classes that
11956   // to reduce the size of the transition table and create bitmasks.
11957   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,
11958   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,
11959   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,
11960   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,
11961   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,
11962   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,
11963   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,
11964   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,
11965 
11966   // The second part is a transition table that maps a combination
11967   // of a state of the automaton and a character class to a state.
11968   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11969   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11970   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11971   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11972   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11973 ];
11974 
11975 private enum DecUtfMixin(string state, string codep, string byte_) =
11976 `{
11977   uint type_ = utf8d.ptr[`~byte_~`];
11978   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11979   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11980     `~state~` = FONS_UTF8_ACCEPT;
11981     `~codep~` = 0xFFFD;
11982   }
11983  }`;
11984 
11985 /*
11986 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11987   pragma(inline, true);
11988   uint type = utf8d.ptr[byte_];
11989   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11990   *state = utf8d.ptr[256 + *state+type];
11991   return *state;
11992 }
11993 */
11994 
11995 
11996 // ////////////////////////////////////////////////////////////////////////// //
11997 /// This iterator can be used to do text measurement.
11998 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
11999 /// Group: font_stash
12000 public struct FONSTextBoundsIterator {
12001 private:
12002   FONSContext stash;
12003   FONSstate state;
12004   uint codepoint = 0xFFFD;
12005   uint utf8state = 0;
12006   int prevGlyphIndex = -1;
12007   short isize, iblur;
12008   float scale = 0;
12009   FONSfont* font;
12010   float startx = 0, x = 0, y = 0;
12011   float minx = 0, miny = 0, maxx = 0, maxy = 0;
12012 
12013 private:
12014   void clear () nothrow @trusted @nogc {
12015     import core.stdc.string : memset;
12016     memset(&this, 0, this.sizeof);
12017     this.prevGlyphIndex = -1;
12018     this.codepoint = 0xFFFD;
12019   }
12020 
12021 public:
12022   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12023   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
12024 
12025   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12026   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
12027     clear();
12028 
12029     if (astash is null || astash.nstates == 0) return;
12030 
12031     stash = astash;
12032     state = *stash.getState;
12033 
12034     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12035     font = stash.fonts[state.font];
12036     if (font is null || font.fdata is null) { clear(); return; }
12037 
12038     x = ax;
12039     y = ay;
12040     isize = cast(short)(state.size*10.0f);
12041     iblur = cast(short)state.blur;
12042     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12043 
12044     // align vertically
12045     y += astash.getVertAlign(font, state.talign, isize);
12046 
12047     minx = maxx = x;
12048     miny = maxy = y;
12049     startx = x;
12050   }
12051 
12052   /// Can this iterator be used?
12053   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12054 
12055   /// Put some text into iterator, calculate new values.
12056   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12057     enum DoCodePointMixin = q{
12058       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12059       if (glyph !is null) {
12060         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12061         if (q.x0 < minx) minx = q.x0;
12062         if (q.x1 > maxx) maxx = q.x1;
12063         if (stash.params.isZeroTopLeft) {
12064           if (q.y0 < miny) miny = q.y0;
12065           if (q.y1 > maxy) maxy = q.y1;
12066         } else {
12067           if (q.y1 < miny) miny = q.y1;
12068           if (q.y0 > maxy) maxy = q.y0;
12069         }
12070         prevGlyphIndex = glyph.index;
12071       } else {
12072         prevGlyphIndex = -1;
12073       }
12074     };
12075 
12076     if (stash is null || str.length == 0) return; // alas
12077 
12078     FONSQuad q;
12079     FONSglyph* glyph;
12080 
12081     static if (is(T == char)) {
12082       foreach (char ch; str) {
12083         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12084         if (utf8state) continue; // full char is not collected yet
12085         mixin(DoCodePointMixin);
12086       }
12087     } else {
12088       if (utf8state) {
12089         utf8state = 0;
12090         codepoint = 0xFFFD;
12091         mixin(DoCodePointMixin);
12092       }
12093       foreach (T dch; str) {
12094         static if (is(T == dchar)) {
12095           if (dch > dchar.max) dch = 0xFFFD;
12096         }
12097         codepoint = cast(uint)dch;
12098         mixin(DoCodePointMixin);
12099       }
12100     }
12101   }
12102 
12103   /// Returns current advance.
12104   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12105 
12106   /// Returns current text bounds.
12107   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12108     if (stash is null) { bounds[] = 0; return; }
12109     float lminx = minx, lmaxx = maxx;
12110     // align horizontally
12111     if (state.talign.left) {
12112       // empty
12113     } else if (state.talign.right) {
12114       float ca = advance;
12115       lminx -= ca;
12116       lmaxx -= ca;
12117     } else if (state.talign.center) {
12118       float ca = advance*0.5f;
12119       lminx -= ca;
12120       lmaxx -= ca;
12121     }
12122     bounds[0] = lminx;
12123     bounds[1] = miny;
12124     bounds[2] = lmaxx;
12125     bounds[3] = maxy;
12126   }
12127 
12128   /// Returns current horizontal text bounds.
12129   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12130     if (stash !is null) {
12131       float lminx = minx, lmaxx = maxx;
12132       // align horizontally
12133       if (state.talign.left) {
12134         // empty
12135       } else if (state.talign.right) {
12136         float ca = advance;
12137         lminx -= ca;
12138         lmaxx -= ca;
12139       } else if (state.talign.center) {
12140         float ca = advance*0.5f;
12141         lminx -= ca;
12142         lmaxx -= ca;
12143       }
12144       xmin = lminx;
12145       xmax = lmaxx;
12146     } else {
12147       xmin = xmax = 0;
12148     }
12149   }
12150 
12151   /// Returns current vertical text bounds.
12152   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12153     pragma(inline, true);
12154     if (stash !is null) {
12155       ymin = miny;
12156       ymax = maxy;
12157     } else {
12158       ymin = ymax = 0;
12159     }
12160   }
12161 
12162   /// Returns font line height.
12163   float lineHeight () nothrow @trusted @nogc {
12164     pragma(inline, true);
12165     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12166   }
12167 
12168   /// Returns font ascender (positive).
12169   float ascender () nothrow @trusted @nogc {
12170     pragma(inline, true);
12171     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12172   }
12173 
12174   /// Returns font descender (negative).
12175   float descender () nothrow @trusted @nogc {
12176     pragma(inline, true);
12177     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12178   }
12179 }
12180 
12181 
12182 // ////////////////////////////////////////////////////////////////////////// //
12183 // backgl
12184 // ////////////////////////////////////////////////////////////////////////// //
12185 import core.stdc.stdlib : malloc, realloc, free;
12186 import core.stdc.string : memcpy, memset;
12187 
12188 static if (__VERSION__ < 2076) {
12189   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12190     import std.traits;
12191     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12192     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12193   }
12194 }
12195 
12196 
12197 //import arsd.simpledisplay;
12198 version(nanovg_bindbc_opengl_bindings) {
12199   import bindbc.opengl;
12200 } else version(nanovg_builtin_opengl_bindings) {
12201   import arsd.simpledisplay;
12202 
12203 	/++
12204 		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.
12205 
12206 		History:
12207 			Added January 22, 2021 (version 9.2 release)
12208 	+/
12209 	public class NVGWindow : SimpleWindow {
12210 		NVGContext nvg;
12211 
12212 		/++
12213 
12214 		+/
12215 		this(int width, int height, string title) {
12216 			setOpenGLContextVersion(3, 0);
12217 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12218 
12219 			this.onClosing = delegate() {
12220 				nvg.kill();
12221 			};
12222 
12223 			this.visibleForTheFirstTime = delegate() {
12224 				nvg = nvgCreateContext();
12225 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12226 			};
12227 
12228 			this.redrawOpenGlScene = delegate() {
12229 				if(redrawNVGScene is null)
12230 					return;
12231 				glViewport(0, 0, this.width, this.height);
12232 				if(clearOnEachFrame) {
12233 					glClearColor(0, 0, 0, 0);
12234 					glClear(glNVGClearFlags);
12235 				}
12236 
12237 				nvg.beginFrame(this.width, this.height);
12238 				scope(exit) nvg.endFrame();
12239 
12240 				redrawNVGScene(nvg);
12241 			};
12242 
12243 			this.setEventHandlers(
12244 				&redrawOpenGlSceneNow,
12245 				(KeyEvent ke) {
12246 					if(ke.key == Key.Escape || ke.key == Key.Q)
12247 						this.close();
12248 				}
12249 			);
12250 		}
12251 
12252 		/++
12253 
12254 		+/
12255 		bool clearOnEachFrame = true;
12256 
12257 		/++
12258 
12259 		+/
12260 		void delegate(NVGContext nvg) redrawNVGScene;
12261 
12262 		/++
12263 
12264 		+/
12265 		void redrawNVGSceneNow() {
12266 			redrawOpenGlSceneNow();
12267 		}
12268 	}
12269 
12270 } else {
12271   import iv.glbinds;
12272 }
12273 
12274 private:
12275 // sdpy is missing that yet
12276 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12277 
12278 
12279 
12280 version(bindbc){
12281   private extern(System) nothrow @nogc:
12282   // this definition doesn't exist in regular OpenGL (?)
12283   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12284   private void nanovgInitOpenGL () {
12285     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12286     GLSupport support = loadOpenGL();
12287     if (support == GLSupport.noLibrary)
12288       assert(0, "OpenGL initialization failed: shared library failed to load");
12289     else if (support == GLSupport.badLibrary)
12290       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12291     else if (support == GLSupport.noContext)
12292       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12293   }
12294 } else { // OpenGL API missing from simpledisplay
12295     private void nanovgInitOpenGL () @nogc nothrow {
12296       __gshared bool initialized = false;
12297       if (initialized) return;
12298 
12299       try
12300         gl3.loadDynamicLibrary();
12301       catch(Exception)
12302       	assert(0, "GL 3 failed to load");
12303 
12304       initialized = true;
12305   }
12306 }
12307 
12308 
12309 
12310 /// Context creation flags.
12311 /// Group: context_management
12312 public enum NVGContextFlag : int {
12313   /// Nothing special, i.e. empty flag.
12314   None = 0,
12315   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12316   Antialias = 1U<<0,
12317   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12318     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12319   StencilStrokes = 1U<<1,
12320   /// Flag indicating that additional debug checks are done.
12321   Debug = 1U<<2,
12322   /// Filter (antialias) fonts
12323   FontAA = 1U<<7,
12324   /// Don't filter (antialias) fonts
12325   FontNoAA = 1U<<8,
12326   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12327   Default = 1U<<31,
12328 }
12329 
12330 public enum NANOVG_GL_USE_STATE_FILTER = true;
12331 
12332 /// Returns flags for glClear().
12333 /// Group: context_management
12334 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12335   pragma(inline, true);
12336   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12337 }
12338 
12339 
12340 // ////////////////////////////////////////////////////////////////////////// //
12341 private:
12342 
12343 version = nanovega_shared_stencil;
12344 //version = nanovega_debug_clipping;
12345 
12346 enum GLNVGuniformLoc {
12347   ViewSize,
12348   Tex,
12349   Frag,
12350   TMat,
12351   TTr,
12352   ClipTex,
12353 }
12354 
12355 alias GLNVGshaderType = int;
12356 enum /*GLNVGshaderType*/ {
12357   NSVG_SHADER_FILLCOLOR,
12358   NSVG_SHADER_FILLGRAD,
12359   NSVG_SHADER_FILLIMG,
12360   NSVG_SHADER_SIMPLE, // also used for clipfill
12361   NSVG_SHADER_IMG,
12362 }
12363 
12364 struct GLNVGshader {
12365   GLuint prog;
12366   GLuint frag;
12367   GLuint vert;
12368   GLint[GLNVGuniformLoc.max+1] loc;
12369 }
12370 
12371 struct GLNVGtexture {
12372   int id;
12373   GLuint tex;
12374   int width, height;
12375   NVGtexture type;
12376   int flags;
12377   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12378   int nextfree;
12379 }
12380 
12381 struct GLNVGblend {
12382   bool simple;
12383   GLenum srcRGB;
12384   GLenum dstRGB;
12385   GLenum srcAlpha;
12386   GLenum dstAlpha;
12387 }
12388 
12389 alias GLNVGcallType = int;
12390 enum /*GLNVGcallType*/ {
12391   GLNVG_NONE = 0,
12392   GLNVG_FILL,
12393   GLNVG_CONVEXFILL,
12394   GLNVG_STROKE,
12395   GLNVG_TRIANGLES,
12396   GLNVG_AFFINE, // change affine transformation matrix
12397   GLNVG_PUSHCLIP,
12398   GLNVG_POPCLIP,
12399   GLNVG_RESETCLIP,
12400   GLNVG_CLIP_DDUMP_ON,
12401   GLNVG_CLIP_DDUMP_OFF,
12402 }
12403 
12404 struct GLNVGcall {
12405   int type;
12406   int evenOdd; // for fill
12407   int image;
12408   int pathOffset;
12409   int pathCount;
12410   int triangleOffset;
12411   int triangleCount;
12412   int uniformOffset;
12413   NVGMatrix affine;
12414   GLNVGblend blendFunc;
12415   NVGClipMode clipmode;
12416 }
12417 
12418 struct GLNVGpath {
12419   int fillOffset;
12420   int fillCount;
12421   int strokeOffset;
12422   int strokeCount;
12423 }
12424 
12425 align(1) struct GLNVGfragUniforms {
12426 align(1):
12427   enum UNIFORM_ARRAY_SIZE = 13;
12428   // note: after modifying layout or size of uniform array,
12429   // don't forget to also update the fragment shader source!
12430   align(1) union {
12431   align(1):
12432     align(1) struct {
12433     align(1):
12434       float[12] scissorMat; // matrices are actually 3 vec4s
12435       float[12] paintMat;
12436       NVGColor innerCol;
12437       NVGColor middleCol;
12438       NVGColor outerCol;
12439       float[2] scissorExt;
12440       float[2] scissorScale;
12441       float[2] extent;
12442       float radius;
12443       float feather;
12444       float strokeMult;
12445       float strokeThr;
12446       float texType;
12447       float type;
12448       float doclip;
12449       float midp; // for gradients
12450       float unused2, unused3;
12451     }
12452     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12453   }
12454 }
12455 
12456 enum GLMaskState {
12457   DontMask = -1,
12458   Uninitialized = 0,
12459   Initialized = 1,
12460   JustCleared = 2,
12461 }
12462 
12463 final class GLNVGTextureLocker {}
12464 
12465 struct GLNVGcontext {
12466   private import core.thread : ThreadID;
12467 
12468   GLNVGshader shader;
12469   GLNVGtexture* textures;
12470   float[2] view;
12471   int freetexid; // -1: none
12472   int ntextures;
12473   int ctextures;
12474   GLuint vertBuf;
12475   int fragSize;
12476   int flags;
12477   // FBOs for masks
12478   GLuint[NVG_MAX_STATES] fbo;
12479   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12480   int fboWidth, fboHeight;
12481   GLMaskState[NVG_MAX_STATES] maskStack;
12482   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12483   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12484   int lastClipUniOfs;
12485   bool doClipUnion; // specal mode
12486   GLNVGshader shaderFillFBO;
12487   GLNVGshader shaderCopyFBO;
12488 
12489   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12490   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12491   ThreadID mainTID;
12492   uint mainFBO;
12493 
12494   // Per frame buffers
12495   GLNVGcall* calls;
12496   int ccalls;
12497   int ncalls;
12498   GLNVGpath* paths;
12499   int cpaths;
12500   int npaths;
12501   NVGVertex* verts;
12502   int cverts;
12503   int nverts;
12504   ubyte* uniforms;
12505   int cuniforms;
12506   int nuniforms;
12507   NVGMatrix lastAffine;
12508 
12509   // cached state
12510   static if (NANOVG_GL_USE_STATE_FILTER) {
12511     GLuint boundTexture;
12512     GLuint stencilMask;
12513     GLenum stencilFunc;
12514     GLint stencilFuncRef;
12515     GLuint stencilFuncMask;
12516     GLNVGblend blendFunc;
12517   }
12518 }
12519 
12520 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12521 
12522 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12523   static if (NANOVG_GL_USE_STATE_FILTER) {
12524     if (gl.boundTexture != tex) {
12525       gl.boundTexture = tex;
12526       glBindTexture(GL_TEXTURE_2D, tex);
12527     }
12528   } else {
12529     glBindTexture(GL_TEXTURE_2D, tex);
12530   }
12531 }
12532 
12533 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12534   static if (NANOVG_GL_USE_STATE_FILTER) {
12535     if (gl.stencilMask != mask) {
12536       gl.stencilMask = mask;
12537       glStencilMask(mask);
12538     }
12539   } else {
12540     glStencilMask(mask);
12541   }
12542 }
12543 
12544 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12545   static if (NANOVG_GL_USE_STATE_FILTER) {
12546     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12547       gl.stencilFunc = func;
12548       gl.stencilFuncRef = ref_;
12549       gl.stencilFuncMask = mask;
12550       glStencilFunc(func, ref_, mask);
12551     }
12552   } else {
12553     glStencilFunc(func, ref_, mask);
12554   }
12555 }
12556 
12557 // texture id is never zero
12558 // sets refcount to one
12559 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12560   GLNVGtexture* tex = null;
12561 
12562   int tid = gl.freetexid;
12563   if (tid == -1) {
12564     if (gl.ntextures >= gl.ctextures) {
12565       assert(gl.ntextures == gl.ctextures);
12566       //pragma(msg, GLNVGtexture.sizeof*32);
12567       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12568       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12569       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12570       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12571       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12572       gl.textures = textures;
12573       gl.ctextures = ctextures;
12574     }
12575     assert(gl.ntextures+1 <= gl.ctextures);
12576     tid = gl.ntextures++;
12577     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12578   } else {
12579     gl.freetexid = gl.textures[tid].nextfree;
12580   }
12581   assert(tid <= gl.ntextures);
12582 
12583   assert(gl.textures[tid].id == 0);
12584   tex = &gl.textures[tid];
12585   memset(tex, 0, (*tex).sizeof);
12586   tex.id = tid+1;
12587   tex.rc = 1;
12588   tex.nextfree = -1;
12589 
12590   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12591 
12592   return tex;
12593 }
12594 
12595 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12596   if (id <= 0 || id > gl.ntextures) return null;
12597   if (gl.textures[id-1].id == 0) return null; // free one
12598   assert(gl.textures[id-1].id == id);
12599   return &gl.textures[id-1];
12600 }
12601 
12602 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12603   if (id <= 0 || id > gl.ntextures) return false;
12604   auto tx = &gl.textures[id-1];
12605   if (tx.id == 0) { id = 0; return false; } // free one
12606   assert(tx.id == id);
12607   assert(tx.tex != 0);
12608   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12609   import core.atomic : atomicOp;
12610   if (atomicOp!"-="(tx.rc, 1) == 0) {
12611     import core.thread : ThreadID;
12612     ThreadID mytid;
12613     static if (__VERSION__ < 2076) {
12614       DGNoThrowNoGC(() {
12615         import core.thread; mytid = Thread.getThis.id;
12616       })();
12617     } else {
12618       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12619     }
12620     if (gl.mainTID == mytid && gl.inFrame) {
12621       // can delete it right now
12622       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12623       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12624       memset(tx, 0, (*tx).sizeof);
12625       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12626       tx.nextfree = gl.freetexid;
12627       gl.freetexid = id-1;
12628     } else {
12629       // alas, we aren't doing frame business, so we should postpone deletion
12630       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12631       version(aliced) {
12632         synchronized(GLNVGTextureLocker.classinfo) {
12633           tx.id = 0; // mark it as dead
12634           gl.mustCleanTextures = true; // set "need cleanup" flag
12635         }
12636       } else {
12637         try {
12638           synchronized(GLNVGTextureLocker.classinfo) {
12639             tx.id = 0; // mark it as dead
12640             gl.mustCleanTextures = true; // set "need cleanup" flag
12641           }
12642         } catch (Exception e) {}
12643       }
12644     }
12645   }
12646   id = 0;
12647   return true;
12648 }
12649 
12650 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12651   import core.stdc.stdio : fprintf, stderr;
12652   GLchar[512+1] str = 0;
12653   GLsizei len = 0;
12654   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12655   if (len > 512) len = 512;
12656   str[len] = '\0';
12657   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12658 }
12659 
12660 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12661   import core.stdc.stdio : fprintf, stderr;
12662   GLchar[512+1] str = 0;
12663   GLsizei len = 0;
12664   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12665   if (len > 512) len = 512;
12666   str[len] = '\0';
12667   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12668 }
12669 
12670 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12671   static if (!force) {
12672     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12673   }
12674   glGetError();
12675 }
12676 
12677 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12678   GLenum err;
12679   static if (!force) {
12680     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12681   }
12682   err = glGetError();
12683   if (err != GL_NO_ERROR) {
12684     import core.stdc.stdio : fprintf, stderr;
12685     fprintf(stderr, "Error %08x after %s\n", err, str);
12686     return;
12687   }
12688 }
12689 
12690 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12691   GLint status;
12692   GLuint prog, vert, frag;
12693   const(char)*[3] str;
12694 
12695   memset(shader, 0, (*shader).sizeof);
12696 
12697   prog = glCreateProgram();
12698   vert = glCreateShader(GL_VERTEX_SHADER);
12699   frag = glCreateShader(GL_FRAGMENT_SHADER);
12700   str[0] = header;
12701   str[1] = (opts !is null ? opts : "");
12702   str[2] = vshader;
12703   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12704 
12705   glCompileShader(vert);
12706   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12707   if (status != GL_TRUE) {
12708     glnvg__dumpShaderError(vert, name, "vert");
12709     return false;
12710   }
12711 
12712   str[0] = header;
12713   str[1] = (opts !is null ? opts : "");
12714   str[2] = fshader;
12715   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12716 
12717   glCompileShader(frag);
12718   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12719   if (status != GL_TRUE) {
12720     glnvg__dumpShaderError(frag, name, "frag");
12721     return false;
12722   }
12723 
12724   glAttachShader(prog, vert);
12725   glAttachShader(prog, frag);
12726 
12727   glBindAttribLocation(prog, 0, "vertex");
12728   glBindAttribLocation(prog, 1, "tcoord");
12729 
12730   glLinkProgram(prog);
12731   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12732   if (status != GL_TRUE) {
12733     glnvg__dumpProgramError(prog, name);
12734     return false;
12735   }
12736 
12737   shader.prog = prog;
12738   shader.vert = vert;
12739   shader.frag = frag;
12740 
12741   return true;
12742 }
12743 
12744 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12745   if (shader.prog != 0) glDeleteProgram(shader.prog);
12746   if (shader.vert != 0) glDeleteShader(shader.vert);
12747   if (shader.frag != 0) glDeleteShader(shader.frag);
12748 }
12749 
12750 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12751   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12752   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12753   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12754   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12755   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12756   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12757 }
12758 
12759 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12760   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12761     if (fbo != 0) {
12762       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12763       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12764       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12765       glDeleteFramebuffers(1, &fbo);
12766       fbo = 0;
12767     }
12768   }
12769   gl.fboWidth = gl.fboHeight = 0;
12770 }
12771 
12772 // returns `true` is new FBO was created
12773 // will not unbind buffer, if it was created
12774 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12775   assert(fidx >= 0 && fidx < gl.fbo.length);
12776   assert(gl.fboWidth > 0);
12777   assert(gl.fboHeight > 0);
12778 
12779   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12780 
12781   glnvg__resetError(gl);
12782 
12783   // allocate FBO object
12784   GLuint fbo = 0;
12785   glGenFramebuffers(1, &fbo);
12786   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12787   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12788   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12789   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12790 
12791   // attach 2D texture to this FBO
12792   GLuint tidColor = 0;
12793   glGenTextures(1, &tidColor);
12794   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12795   glBindTexture(GL_TEXTURE_2D, tidColor);
12796   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12797   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12798   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12799   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12800   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12801   //FIXME: linear or nearest?
12802   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12803   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12804   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12805   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12806   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12807   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12808   // empty texture
12809   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12810   // create texture with only one color channel
12811   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12812   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12813   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12814   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12815   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12816 
12817   // attach stencil texture to this FBO
12818   GLuint tidStencil = 0;
12819   version(nanovega_shared_stencil) {
12820     if (gl.fboTex.ptr[0].ptr[0] == 0) {
12821       glGenTextures(1, &tidStencil);
12822       if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12823       gl.fboTex.ptr[0].ptr[0] = tidStencil;
12824     } else {
12825       tidStencil = gl.fboTex.ptr[0].ptr[0];
12826     }
12827     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12828   } else {
12829     glGenTextures(1, &tidStencil);
12830     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12831     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12832   }
12833   glBindTexture(GL_TEXTURE_2D, tidStencil);
12834   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12835   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12836   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12837   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12838 
12839   {
12840     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12841     if (status != GL_FRAMEBUFFER_COMPLETE) {
12842       version(all) {
12843         import core.stdc.stdio;
12844         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12845         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12846         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12847         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12848       }
12849       assert(0, "NanoVega: framebuffer creation failed");
12850     }
12851   }
12852 
12853   // clear 'em all
12854   if (doclear) {
12855     glClearColor(0, 0, 0, 0);
12856     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12857   }
12858 
12859   // save texture ids
12860   gl.fbo.ptr[fidx] = fbo;
12861   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12862   version(nanovega_shared_stencil) {} else {
12863     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12864   }
12865 
12866   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12867 
12868   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12869 
12870   return true;
12871 }
12872 
12873 // will not unbind buffer
12874 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12875   assert(fidx >= 0 && fidx < gl.fbo.length);
12876   assert(gl.fboWidth > 0);
12877   assert(gl.fboHeight > 0);
12878   assert(gl.fbo.ptr[fidx] != 0);
12879   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12880   glClearColor(0, 0, 0, 0);
12881   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12882   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12883 }
12884 
12885 // will not unbind buffer
12886 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12887   import core.stdc.string : memset;
12888   assert(didx >= 0 && didx < gl.fbo.length);
12889   assert(sidx >= 0 && sidx < gl.fbo.length);
12890   assert(gl.fboWidth > 0);
12891   assert(gl.fboHeight > 0);
12892   assert(gl.fbo.ptr[didx] != 0);
12893   assert(gl.fbo.ptr[sidx] != 0);
12894   if (didx == sidx) return;
12895 
12896   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12897 
12898   glUseProgram(gl.shaderCopyFBO.prog);
12899 
12900   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12901   glDisable(GL_CULL_FACE);
12902   glDisable(GL_BLEND);
12903   glDisable(GL_SCISSOR_TEST);
12904   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12905   // copy texture by drawing full quad
12906   enum x = 0;
12907   enum y = 0;
12908   immutable int w = gl.fboWidth;
12909   immutable int h = gl.fboHeight;
12910   immutable(NVGVertex[4]) vertices =
12911    [NVGVertex(x, y), // top-left
12912     NVGVertex(w, y), // top-right
12913     NVGVertex(w, h), // bottom-right
12914     NVGVertex(x, h)]; // bottom-left
12915 
12916   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12917   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12918 
12919   // restore state (but don't unbind FBO)
12920   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12921   glEnable(GL_CULL_FACE);
12922   glEnable(GL_BLEND);
12923   glUseProgram(gl.shader.prog);
12924 }
12925 
12926 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12927   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12928   /*
12929   if (gl.lastClipFBO >= 0) {
12930     glActiveTexture(GL_TEXTURE1);
12931     glBindTexture(GL_TEXTURE_2D, 0);
12932     glActiveTexture(GL_TEXTURE0);
12933   }
12934   */
12935   gl.lastClipFBO = -666;
12936 }
12937 
12938 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12939   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12940   if (gl.lastClipFBO != -666) {
12941     // cached
12942     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12943     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12944     return;
12945   }
12946 
12947   // no cache
12948   int fboidx = -1;
12949   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12950     final switch (mst) {
12951       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12952       case GLMaskState.Uninitialized: break;
12953       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12954       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12955     }
12956   }
12957 
12958   if (fboidx < 0) {
12959     // don't mask
12960     gl.lastClipFBO = -1;
12961     frag.doclip = 0;
12962   } else {
12963     // do masking
12964     assert(gl.fbo.ptr[fboidx] != 0);
12965     gl.lastClipFBO = fboidx;
12966     frag.doclip = 1;
12967   }
12968 
12969   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12970 
12971   if (gl.lastClipFBO >= 0) {
12972     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12973     glActiveTexture(GL_TEXTURE1);
12974     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12975     glActiveTexture(GL_TEXTURE0);
12976   }
12977 }
12978 
12979 // returns index in `gl.fbo`, or -1 for "don't mask"
12980 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12981   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12982   // we need initialized FBO, even for "don't mask" case
12983   // for this, look back in stack, and either copy initialized FBO,
12984   // or stop at first uninitialized one, and clear it
12985   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12986     // shortcut
12987     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); }
12988     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
12989     return gl.msp-1;
12990   }
12991   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
12992     final switch (gl.maskStack.ptr[sp]) {
12993       case GLMaskState.DontMask:
12994         // clear it
12995         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
12996         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
12997         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
12998         return gl.msp-1;
12999       case GLMaskState.Uninitialized: break; // do nothing
13000       case GLMaskState.Initialized:
13001         // i found her! copy to TOS
13002         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13003         glnvg__allocFBO(gl, gl.msp-1, false);
13004         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13005         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13006         return gl.msp-1;
13007       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13008     }
13009   }
13010   // nothing was initialized, lol
13011   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13012   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13013   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13014   return gl.msp-1;
13015 }
13016 
13017 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13018   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13019   GLNVGcall* call = glnvg__allocCall(gl);
13020   if (call is null) return;
13021   call.type = GLNVG_PUSHCLIP;
13022 }
13023 
13024 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13025   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13026   GLNVGcall* call = glnvg__allocCall(gl);
13027   if (call is null) return;
13028   call.type = GLNVG_POPCLIP;
13029 }
13030 
13031 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13032   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13033   GLNVGcall* call = glnvg__allocCall(gl);
13034   if (call is null) return;
13035   call.type = GLNVG_RESETCLIP;
13036 }
13037 
13038 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13039   version(nanovega_debug_clipping) {
13040     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13041     GLNVGcall* call = glnvg__allocCall(gl);
13042     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13043   }
13044 }
13045 
13046 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13047   import core.stdc.stdio : snprintf;
13048 
13049   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13050   enum align_ = 4;
13051 
13052   char[64] shaderHeader = void;
13053   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13054   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13055 
13056   enum fillVertShader = q{
13057     uniform vec2 viewSize;
13058     attribute vec2 vertex;
13059     attribute vec2 tcoord;
13060     varying vec2 ftcoord;
13061     varying vec2 fpos;
13062     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13063     uniform vec2 ttr; /* tx and ty of affine matrix */
13064     void main (void) {
13065       /* affine transformation */
13066       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13067       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13068       ftcoord = tcoord;
13069       fpos = vec2(nx, ny);
13070       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13071     }
13072   };
13073 
13074   enum fillFragShader = `
13075     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13076     uniform sampler2D tex;
13077     uniform sampler2D clipTex;
13078     uniform vec2 viewSize;
13079     varying vec2 ftcoord;
13080     varying vec2 fpos;
13081     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13082     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13083     #define innerCol frag[6]
13084     #define middleCol frag[7]
13085     #define outerCol frag[7+1]
13086     #define scissorExt frag[8+1].xy
13087     #define scissorScale frag[8+1].zw
13088     #define extent frag[9+1].xy
13089     #define radius frag[9+1].z
13090     #define feather frag[9+1].w
13091     #define strokeMult frag[10+1].x
13092     #define strokeThr frag[10+1].y
13093     #define texType int(frag[10+1].z)
13094     #define type int(frag[10+1].w)
13095     #define doclip int(frag[11+1].x)
13096     #define midp frag[11+1].y
13097 
13098     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13099       vec2 ext2 = ext-vec2(rad, rad);
13100       vec2 d = abs(pt)-ext2;
13101       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13102     }
13103 
13104     // Scissoring
13105     float scissorMask (in vec2 p) {
13106       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13107       sc = vec2(0.5, 0.5)-sc*scissorScale;
13108       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13109     }
13110 
13111     #ifdef EDGE_AA
13112     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13113     float strokeMask () {
13114       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13115     }
13116     #endif
13117 
13118     void main (void) {
13119       // clipping
13120       if (doclip != 0) {
13121         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13122         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13123         if (clr.r == 0.0) discard;
13124       }
13125       float scissor = scissorMask(fpos);
13126       if (scissor <= 0.0) discard; //k8: is it really faster?
13127       #ifdef EDGE_AA
13128       float strokeAlpha = strokeMask();
13129       if (strokeAlpha < strokeThr) discard;
13130       #else
13131       float strokeAlpha = 1.0;
13132       #endif
13133       // rendering
13134       vec4 color;
13135       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13136         color = innerCol;
13137         // Combine alpha
13138         color *= strokeAlpha*scissor;
13139       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13140         // Gradient
13141         // Calculate gradient color using box gradient
13142         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13143         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13144         if (midp <= 0.0) {
13145           color = mix(innerCol, outerCol, d);
13146         } else {
13147           float gdst = min(midp, 1.0);
13148           if (d < gdst) {
13149             color = mix(innerCol, middleCol, d/gdst);
13150           } else {
13151             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13152           }
13153         }
13154         // Combine alpha
13155         color *= strokeAlpha*scissor;
13156       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13157         // Image
13158         // Calculate color from texture
13159         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13160         color = texture2D(tex, pt);
13161         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13162         if (texType == 2) color = vec4(color.x);
13163         // Apply color tint and alpha
13164         color *= innerCol;
13165         // Combine alpha
13166         color *= strokeAlpha*scissor;
13167       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13168         // Stencil fill
13169         color = vec4(1, 1, 1, 1);
13170       } else if (type == 4) { /* NSVG_SHADER_IMG */
13171         // Textured tris
13172         color = texture2D(tex, ftcoord);
13173         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13174         if (texType == 2) color = vec4(color.x);
13175         color *= scissor;
13176         color *= innerCol; // Apply color tint
13177       }
13178       gl_FragColor = color;
13179     }
13180   `;
13181 
13182   enum clipVertShaderFill = q{
13183     uniform vec2 viewSize;
13184     attribute vec2 vertex;
13185     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13186     uniform vec2 ttr; /* tx and ty of affine matrix */
13187     void main (void) {
13188       /* affine transformation */
13189       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13190       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13191       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13192     }
13193   };
13194 
13195   enum clipFragShaderFill = q{
13196     uniform vec2 viewSize;
13197     void main (void) {
13198       gl_FragColor = vec4(1, 1, 1, 1);
13199     }
13200   };
13201 
13202   enum clipVertShaderCopy = q{
13203     uniform vec2 viewSize;
13204     attribute vec2 vertex;
13205     void main (void) {
13206       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13207     }
13208   };
13209 
13210   enum clipFragShaderCopy = q{
13211     uniform sampler2D tex;
13212     uniform vec2 viewSize;
13213     void main (void) {
13214       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13215       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13216     }
13217   };
13218 
13219   glnvg__checkError(gl, "init");
13220 
13221   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13222   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13223   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13224   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13225 
13226   glnvg__checkError(gl, "uniform locations");
13227   glnvg__getUniforms(&gl.shader);
13228   glnvg__getUniforms(&gl.shaderFillFBO);
13229   glnvg__getUniforms(&gl.shaderCopyFBO);
13230 
13231   // Create dynamic vertex array
13232   glGenBuffers(1, &gl.vertBuf);
13233 
13234   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13235 
13236   glnvg__checkError(gl, "create done");
13237 
13238   glFinish();
13239 
13240   return true;
13241 }
13242 
13243 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13244   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13245   GLNVGtexture* tex = glnvg__allocTexture(gl);
13246 
13247   if (tex is null) return 0;
13248 
13249   glGenTextures(1, &tex.tex);
13250   tex.width = w;
13251   tex.height = h;
13252   tex.type = type;
13253   tex.flags = imageFlags;
13254   glnvg__bindTexture(gl, tex.tex);
13255 
13256   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13257 
13258   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13259   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13260   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13261   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13262 
13263 
13264 
13265   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13266   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13267   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13268   // it needs to be called after the glTexImage2D call
13269   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13270     glGenerateMipmap(GL_TEXTURE_2D);
13271 
13272   immutable tfmin =
13273     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13274      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13275      GL_LINEAR);
13276   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13277   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13278   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13279 
13280   int flag;
13281   if (imageFlags&NVGImageFlag.RepeatX)
13282     flag = GL_REPEAT;
13283   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13284     flag = GL_CLAMP_TO_BORDER;
13285   else 
13286     flag = GL_CLAMP_TO_EDGE;
13287   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13288 
13289 
13290   if (imageFlags&NVGImageFlag.RepeatY)
13291     flag = GL_REPEAT;
13292   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13293     flag = GL_CLAMP_TO_BORDER;
13294   else 
13295     flag = GL_CLAMP_TO_EDGE;
13296   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13297 
13298   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13299   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13300   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13301   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13302 
13303   glnvg__checkError(gl, "create tex");
13304   glnvg__bindTexture(gl, 0);
13305 
13306   return tex.id;
13307 }
13308 
13309 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13310   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13311   return glnvg__deleteTexture(gl, image);
13312 }
13313 
13314 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13315   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13316   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13317   if (tex is null) {
13318     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13319     return false;
13320   }
13321   import core.atomic : atomicOp;
13322   atomicOp!"+="(tex.rc, 1);
13323   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13324   return true;
13325 }
13326 
13327 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13328   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13329   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13330 
13331   if (tex is null) {
13332     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13333     return false;
13334   }
13335 
13336   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13337 
13338   glnvg__bindTexture(gl, tex.tex);
13339 
13340   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13341   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13342   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13343   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13344 
13345   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13346   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13347 
13348   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13349   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13350   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13351   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13352 
13353   glnvg__bindTexture(gl, 0);
13354 
13355   return true;
13356 }
13357 
13358 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13359   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13360   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13361   if (tex is null) {
13362     if (w !is null) *w = 0;
13363     if (h !is null) *h = 0;
13364     return false;
13365   } else {
13366     if (w !is null) *w = tex.width;
13367     if (h !is null) *h = tex.height;
13368     return true;
13369   }
13370 }
13371 
13372 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13373   assert(t.length >= 6);
13374   assert(m3.length >= 12);
13375   m3.ptr[0] = t.ptr[0];
13376   m3.ptr[1] = t.ptr[1];
13377   m3.ptr[2] = 0.0f;
13378   m3.ptr[3] = 0.0f;
13379   m3.ptr[4] = t.ptr[2];
13380   m3.ptr[5] = t.ptr[3];
13381   m3.ptr[6] = 0.0f;
13382   m3.ptr[7] = 0.0f;
13383   m3.ptr[8] = t.ptr[4];
13384   m3.ptr[9] = t.ptr[5];
13385   m3.ptr[10] = 1.0f;
13386   m3.ptr[11] = 0.0f;
13387 }
13388 
13389 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc {
13390   //pragma(inline, true);
13391   NVGColor res = void;
13392   res.r = c.r*c.a;
13393   res.g = c.g*c.a;
13394   res.b = c.b*c.a;
13395   res.a = c.a;
13396   return res;
13397 }
13398 
13399 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13400   import core.stdc.math : sqrtf;
13401   GLNVGtexture* tex = null;
13402   NVGMatrix invxform = void;
13403 
13404   memset(frag, 0, (*frag).sizeof);
13405 
13406   frag.innerCol = glnvg__premulColor(paint.innerColor);
13407   frag.middleCol = glnvg__premulColor(paint.middleColor);
13408   frag.outerCol = glnvg__premulColor(paint.outerColor);
13409   frag.midp = paint.midp;
13410 
13411   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13412     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13413     frag.scissorExt.ptr[0] = 1.0f;
13414     frag.scissorExt.ptr[1] = 1.0f;
13415     frag.scissorScale.ptr[0] = 1.0f;
13416     frag.scissorScale.ptr[1] = 1.0f;
13417   } else {
13418     //nvgTransformInverse(invxform[], scissor.xform[]);
13419     invxform = scissor.xform.inverted;
13420     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13421     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13422     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13423     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;
13424     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;
13425   }
13426 
13427   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13428   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13429   frag.strokeThr = strokeThr;
13430 
13431   if (paint.image.valid) {
13432     tex = glnvg__findTexture(gl, paint.image.id);
13433     if (tex is null) return false;
13434     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13435       /*
13436       NVGMatrix flipped;
13437       nvgTransformScale(flipped[], 1.0f, -1.0f);
13438       nvgTransformMultiply(flipped[], paint.xform[]);
13439       nvgTransformInverse(invxform[], flipped[]);
13440       */
13441       /*
13442       NVGMatrix m1 = void, m2 = void;
13443       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13444       nvgTransformMultiply(m1[], paint.xform[]);
13445       nvgTransformScale(m2[], 1.0f, -1.0f);
13446       nvgTransformMultiply(m2[], m1[]);
13447       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13448       nvgTransformMultiply(m1[], m2[]);
13449       nvgTransformInverse(invxform[], m1[]);
13450       */
13451       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13452       m1.mul(paint.xform);
13453       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13454       m2.mul(m1);
13455       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13456       m1.mul(m2);
13457       invxform = m1.inverted;
13458     } else {
13459       //nvgTransformInverse(invxform[], paint.xform[]);
13460       invxform = paint.xform.inverted;
13461     }
13462     frag.type = NSVG_SHADER_FILLIMG;
13463 
13464     if (tex.type == NVGtexture.RGBA) {
13465       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13466     } else {
13467       frag.texType = 2;
13468     }
13469     //printf("frag.texType = %d\n", frag.texType);
13470   } else {
13471     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13472     frag.radius = paint.radius;
13473     frag.feather = paint.feather;
13474     //nvgTransformInverse(invxform[], paint.xform[]);
13475     invxform = paint.xform.inverted;
13476   }
13477 
13478   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13479 
13480   return true;
13481 }
13482 
13483 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13484   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13485   glnvg__setFBOClipTexture(gl, frag);
13486   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13487   glnvg__checkError(gl, "glnvg__setUniforms");
13488   if (image != 0) {
13489     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13490     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13491     glnvg__checkError(gl, "tex paint tex");
13492   } else {
13493     glnvg__bindTexture(gl, 0);
13494   }
13495 }
13496 
13497 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13498   assert(clipmode != NVGClipMode.None);
13499 
13500   // fill FBO, clear stencil buffer
13501   //TODO: optimize with bounds?
13502   version(all) {
13503     //glnvg__resetAffine(gl);
13504     //glUseProgram(gl.shaderFillFBO.prog);
13505     glDisable(GL_CULL_FACE);
13506     glDisable(GL_BLEND);
13507     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13508     glEnable(GL_STENCIL_TEST);
13509     if (gl.doClipUnion) {
13510       // for "and" we should clear everything that is NOT stencil-masked
13511       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13512       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13513     } else {
13514       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13515       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13516     }
13517 
13518     immutable(NVGVertex[4]) vertices =
13519      [NVGVertex(0, 0, 0, 0),
13520       NVGVertex(0, gl.fboHeight, 0, 0),
13521       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13522       NVGVertex(gl.fboWidth, 0, 0, 0)];
13523 
13524     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13525     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13526 
13527     //glnvg__restoreAffine(gl);
13528   }
13529 
13530   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13531   glDisable(GL_COLOR_LOGIC_OP);
13532   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13533   glEnable(GL_BLEND);
13534   glDisable(GL_STENCIL_TEST);
13535   glEnable(GL_CULL_FACE);
13536   glUseProgram(gl.shader.prog);
13537 
13538   // set current FBO as used one
13539   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13540   if (gl.lastClipFBO != gl.msp-1) {
13541     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); }
13542     gl.lastClipFBO = gl.msp-1;
13543     glActiveTexture(GL_TEXTURE1);
13544     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13545     glActiveTexture(GL_TEXTURE0);
13546   }
13547 }
13548 
13549 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13550   assert(clipmode != NVGClipMode.None);
13551   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13552   // save uniform offset for `glnvg__finishClip()`
13553   gl.lastClipUniOfs = uniformOffset;
13554   // get FBO index, bind this FBO
13555   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13556   assert(clipTexId >= 0);
13557   glUseProgram(gl.shaderFillFBO.prog);
13558   glnvg__checkError(gl, "use");
13559   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13560   // set logic op for clip
13561   gl.doClipUnion = false;
13562   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13563     // it is cleared to zero, we can just draw a path
13564     glDisable(GL_COLOR_LOGIC_OP);
13565     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13566   } else {
13567     glEnable(GL_COLOR_LOGIC_OP);
13568     final switch (clipmode) {
13569       case NVGClipMode.None: assert(0, "wtf?!");
13570       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13571       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13572       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13573       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13574       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13575     }
13576   }
13577   // set affine matrix
13578   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13579   glnvg__checkError(gl, "affine 0");
13580   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13581   glnvg__checkError(gl, "affine 1");
13582   // setup common OpenGL parameters
13583   glDisable(GL_BLEND);
13584   glDisable(GL_CULL_FACE);
13585   glEnable(GL_STENCIL_TEST);
13586   glnvg__stencilMask(gl, 0xff);
13587   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13588   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13589   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13590 }
13591 
13592 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13593   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13594   gl.inFrame = true;
13595   gl.view.ptr[0] = cast(float)width;
13596   gl.view.ptr[1] = cast(float)height;
13597   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13598   if (width != gl.fboWidth || height != gl.fboHeight) {
13599     glnvg__killFBOs(gl);
13600     gl.fboWidth = width;
13601     gl.fboHeight = height;
13602   }
13603   gl.msp = 1;
13604   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13605   // texture cleanup
13606   import core.atomic : atomicLoad;
13607   if (atomicLoad(gl.mustCleanTextures)) {
13608     try {
13609       import core.thread : Thread;
13610       static if (__VERSION__ < 2076) {
13611         DGNoThrowNoGC(() {
13612           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13613         })();
13614       } else {
13615         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13616       }
13617       synchronized(GLNVGTextureLocker.classinfo) {
13618         gl.mustCleanTextures = false;
13619         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13620           // no need to use atomic ops here, as we're locked
13621           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13622             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13623             import core.stdc.string : memset;
13624             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13625             memset(&tex, 0, tex.sizeof);
13626             tex.nextfree = gl.freetexid;
13627             gl.freetexid = cast(int)tidx;
13628           }
13629         }
13630       }
13631     } catch (Exception e) {}
13632   }
13633 }
13634 
13635 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13636   GLNVGpath* paths = &gl.paths[call.pathOffset];
13637   int npaths = call.pathCount;
13638 
13639   if (call.clipmode == NVGClipMode.None) {
13640     // Draw shapes
13641     glEnable(GL_STENCIL_TEST);
13642     glnvg__stencilMask(gl, 0xffU);
13643     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13644 
13645     glnvg__setUniforms(gl, call.uniformOffset, 0);
13646     glnvg__checkError(gl, "fill simple");
13647 
13648     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13649     if (call.evenOdd) {
13650       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13651       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13652       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13653     } else {
13654       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13655       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13656     }
13657     glDisable(GL_CULL_FACE);
13658     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13659     glEnable(GL_CULL_FACE);
13660 
13661     // Draw anti-aliased pixels
13662     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13663     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13664     glnvg__checkError(gl, "fill fill");
13665 
13666     if (gl.flags&NVGContextFlag.Antialias) {
13667       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13668       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13669       // Draw fringes
13670       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13671     }
13672 
13673     // Draw fill
13674     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13675     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13676     if (call.evenOdd) {
13677       glDisable(GL_CULL_FACE);
13678       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13679       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13680       glEnable(GL_CULL_FACE);
13681     } else {
13682       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13683     }
13684 
13685     glDisable(GL_STENCIL_TEST);
13686   } else {
13687     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13688     glnvg__checkError(gl, "fillclip simple");
13689     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13690     if (call.evenOdd) {
13691       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13692       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13693       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13694     } else {
13695       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13696       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13697     }
13698     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13699     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13700   }
13701 }
13702 
13703 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13704   GLNVGpath* paths = &gl.paths[call.pathOffset];
13705   int npaths = call.pathCount;
13706 
13707   if (call.clipmode == NVGClipMode.None) {
13708     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13709     glnvg__checkError(gl, "convex fill");
13710     if (call.evenOdd) glDisable(GL_CULL_FACE);
13711     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13712     if (gl.flags&NVGContextFlag.Antialias) {
13713       // Draw fringes
13714       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13715     }
13716     if (call.evenOdd) glEnable(GL_CULL_FACE);
13717   } else {
13718     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13719     glnvg__checkError(gl, "clip convex fill");
13720     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13721     if (gl.flags&NVGContextFlag.Antialias) {
13722       // Draw fringes
13723       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13724     }
13725     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13726   }
13727 }
13728 
13729 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13730   GLNVGpath* paths = &gl.paths[call.pathOffset];
13731   int npaths = call.pathCount;
13732 
13733   if (call.clipmode == NVGClipMode.None) {
13734     if (gl.flags&NVGContextFlag.StencilStrokes) {
13735       glEnable(GL_STENCIL_TEST);
13736       glnvg__stencilMask(gl, 0xff);
13737 
13738       // Fill the stroke base without overlap
13739       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13740       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13741       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13742       glnvg__checkError(gl, "stroke fill 0");
13743       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13744 
13745       // Draw anti-aliased pixels.
13746       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13747       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13748       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13749       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13750 
13751       // Clear stencil buffer.
13752       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13753       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13754       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13755       glnvg__checkError(gl, "stroke fill 1");
13756       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13757       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13758 
13759       glDisable(GL_STENCIL_TEST);
13760 
13761       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13762     } else {
13763       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13764       glnvg__checkError(gl, "stroke fill");
13765       // Draw Strokes
13766       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13767     }
13768   } else {
13769     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13770     glnvg__checkError(gl, "stroke fill 0");
13771     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13772     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13773   }
13774 }
13775 
13776 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13777   if (call.clipmode == NVGClipMode.None) {
13778     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13779     glnvg__checkError(gl, "triangles fill");
13780     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13781   } else {
13782     //TODO(?): use texture as mask?
13783   }
13784 }
13785 
13786 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13787   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13788   glnvg__checkError(gl, "affine");
13789   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13790   glnvg__checkError(gl, "affine");
13791   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13792 }
13793 
13794 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13795   scope(exit) gl.inFrame = false;
13796   if (clearTextures && gl.inFrame) {
13797     try {
13798       import core.thread : Thread;
13799       static if (__VERSION__ < 2076) {
13800         DGNoThrowNoGC(() {
13801           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13802         })();
13803       } else {
13804         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13805       }
13806     } catch (Exception e) {}
13807     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13808   }
13809   gl.nverts = 0;
13810   gl.npaths = 0;
13811   gl.ncalls = 0;
13812   gl.nuniforms = 0;
13813   gl.msp = 1;
13814   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13815 }
13816 
13817 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13818   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13819 }
13820 
13821 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13822   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13823   if (factor == NVGBlendFactor.One) return GL_ONE;
13824   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13825   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13826   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13827   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13828   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13829   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13830   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13831   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13832   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13833   return GL_INVALID_ENUM;
13834 }
13835 
13836 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13837   GLNVGblend res;
13838   res.simple = op.simple;
13839   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13840   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13841   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13842   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13843   if (res.simple) {
13844     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13845       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13846     }
13847   } else {
13848     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13849       res.simple = true;
13850       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13851     }
13852   }
13853   return res;
13854 }
13855 
13856 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc {
13857   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13858   static if (NANOVG_GL_USE_STATE_FILTER) {
13859     if (gl.blendFunc.simple == op.simple) {
13860       if (op.simple) {
13861         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13862       } else {
13863         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13864       }
13865     }
13866     gl.blendFunc = op;
13867   }
13868   if (op.simple) {
13869     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13870       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13871     } else {
13872       glBlendFunc(op.srcAlpha, op.dstAlpha);
13873     }
13874   } else {
13875     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13876       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13877     } else {
13878       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13879     }
13880   }
13881 }
13882 
13883 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc {
13884   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13885   GLNVGcall* call;
13886   // if last operation was GLNVG_AFFINE, simply replace the matrix
13887   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13888     call = &gl.calls[gl.ncalls-1];
13889   } else {
13890     call = glnvg__allocCall(gl);
13891     if (call is null) return;
13892     call.type = GLNVG_AFFINE;
13893   }
13894   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13895 }
13896 
13897 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13898 
13899 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13900   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13901   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13902   try {
13903     import core.thread : Thread;
13904     static if (__VERSION__ < 2076) {
13905       DGNoThrowNoGC(() {
13906         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13907       })();
13908     } else {
13909       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13910     }
13911   } catch (Exception e) {}
13912   scope(exit) gl.inFrame = false;
13913 
13914   glnvg__resetError!true(gl);
13915   {
13916     int vv = 0;
13917     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13918     if (glGetError() || vv < 0) vv = 0;
13919     gl.mainFBO = cast(uint)vv;
13920   }
13921 
13922   enum ShaderType { None, Fill, Clip }
13923   auto lastShader = ShaderType.None;
13924   if (gl.ncalls > 0) {
13925     gl.msp = 1;
13926     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13927 
13928     // Setup require GL state.
13929     glUseProgram(gl.shader.prog);
13930 
13931     glActiveTexture(GL_TEXTURE1);
13932     glBindTexture(GL_TEXTURE_2D, 0);
13933     glActiveTexture(GL_TEXTURE0);
13934     glnvg__resetFBOClipTextureCache(gl);
13935 
13936     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13937     static if (NANOVG_GL_USE_STATE_FILTER) {
13938       gl.blendFunc.simple = true;
13939       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13940     }
13941     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13942     glEnable(GL_CULL_FACE);
13943     glCullFace(GL_BACK);
13944     glFrontFace(GL_CCW);
13945     glEnable(GL_BLEND);
13946     glDisable(GL_DEPTH_TEST);
13947     glDisable(GL_SCISSOR_TEST);
13948     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13949     glStencilMask(0xffffffff);
13950     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13951     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13952     glActiveTexture(GL_TEXTURE0);
13953     glBindTexture(GL_TEXTURE_2D, 0);
13954     static if (NANOVG_GL_USE_STATE_FILTER) {
13955       gl.boundTexture = 0;
13956       gl.stencilMask = 0xffffffff;
13957       gl.stencilFunc = GL_ALWAYS;
13958       gl.stencilFuncRef = 0;
13959       gl.stencilFuncMask = 0xffffffff;
13960     }
13961     glnvg__checkError(gl, "OpenGL setup");
13962 
13963     // Upload vertex data
13964     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13965     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
13966     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13967     glEnableVertexAttribArray(0);
13968     glEnableVertexAttribArray(1);
13969     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13970     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13971     glnvg__checkError(gl, "vertex data uploading");
13972 
13973     // Set view and texture just once per frame.
13974     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13975     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13976       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13977       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13978     }
13979     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13980     glnvg__checkError(gl, "render shader setup");
13981 
13982     // Reset affine transformations.
13983     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13984     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13985     glnvg__checkError(gl, "affine setup");
13986 
13987     // set clip shaders params
13988     // fill
13989     glUseProgram(gl.shaderFillFBO.prog);
13990     glnvg__checkError(gl, "clip shaders setup (fill 0)");
13991     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13992     glnvg__checkError(gl, "clip shaders setup (fill 1)");
13993     // copy
13994     glUseProgram(gl.shaderCopyFBO.prog);
13995     glnvg__checkError(gl, "clip shaders setup (copy 0)");
13996     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13997     glnvg__checkError(gl, "clip shaders setup (copy 1)");
13998     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
13999     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14000     glnvg__checkError(gl, "clip shaders setup (copy 2)");
14001     // restore render shader
14002     glUseProgram(gl.shader.prog);
14003 
14004     //{ 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]); }
14005 
14006     gl.lastAffine.identity;
14007 
14008     foreach (int i; 0..gl.ncalls) {
14009       GLNVGcall* call = &gl.calls[i];
14010       switch (call.type) {
14011         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14012         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14013         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14014         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14015         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14016         // clip region management
14017         case GLNVG_PUSHCLIP:
14018           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]); }
14019           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14020           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14021             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14022           } else {
14023             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14024           }
14025           // no need to reset FBO cache here, as nothing was changed
14026           break;
14027         case GLNVG_POPCLIP:
14028           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14029           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]); }
14030           --gl.msp;
14031           assert(gl.msp > 0);
14032           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14033           // check popped item
14034           final switch (gl.maskStack.ptr[gl.msp]) {
14035             case GLMaskState.DontMask:
14036               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14037               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14038                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14039                 glnvg__resetFBOClipTextureCache(gl);
14040               }
14041               break;
14042             case GLMaskState.Uninitialized:
14043               // if last FBO texture was uninitialized, it means that nothing was changed,
14044               // so we can keep using cached FBO
14045               break;
14046             case GLMaskState.Initialized:
14047               // if last FBO was initialized, it means that something was definitely changed
14048               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14049               glnvg__resetFBOClipTextureCache(gl);
14050               break;
14051             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14052           }
14053           break;
14054         case GLNVG_RESETCLIP:
14055           // mark current mask as "don't mask"
14056           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]); }
14057           if (gl.msp > 0) {
14058             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14059               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14060               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14061               glnvg__resetFBOClipTextureCache(gl);
14062             }
14063           }
14064           break;
14065         case GLNVG_CLIP_DDUMP_ON:
14066           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14067           break;
14068         case GLNVG_CLIP_DDUMP_OFF:
14069           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14070           break;
14071         case GLNVG_NONE: break;
14072         default:
14073           {
14074             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14075           }
14076           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14077       }
14078       // and free texture, why not
14079       glnvg__deleteTexture(gl, call.image);
14080     }
14081 
14082     glDisableVertexAttribArray(0);
14083     glDisableVertexAttribArray(1);
14084     glDisable(GL_CULL_FACE);
14085     glBindBuffer(GL_ARRAY_BUFFER, 0);
14086     glUseProgram(0);
14087     glnvg__bindTexture(gl, 0);
14088   }
14089 
14090   // this will do all necessary cleanup
14091   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14092 }
14093 
14094 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14095   int count = 0;
14096   foreach (int i; 0..npaths) {
14097     count += paths[i].nfill;
14098     count += paths[i].nstroke;
14099   }
14100   return count;
14101 }
14102 
14103 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14104   GLNVGcall* ret = null;
14105   if (gl.ncalls+1 > gl.ccalls) {
14106     GLNVGcall* calls;
14107     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14108     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14109     if (calls is null) return null;
14110     gl.calls = calls;
14111     gl.ccalls = ccalls;
14112   }
14113   ret = &gl.calls[gl.ncalls++];
14114   memset(ret, 0, GLNVGcall.sizeof);
14115   return ret;
14116 }
14117 
14118 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14119   int ret = 0;
14120   if (gl.npaths+n > gl.cpaths) {
14121     GLNVGpath* paths;
14122     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14123     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14124     if (paths is null) return -1;
14125     gl.paths = paths;
14126     gl.cpaths = cpaths;
14127   }
14128   ret = gl.npaths;
14129   gl.npaths += n;
14130   return ret;
14131 }
14132 
14133 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14134   int ret = 0;
14135   if (gl.nverts+n > gl.cverts) {
14136     NVGVertex* verts;
14137     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14138     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14139     if (verts is null) return -1;
14140     gl.verts = verts;
14141     gl.cverts = cverts;
14142   }
14143   ret = gl.nverts;
14144   gl.nverts += n;
14145   return ret;
14146 }
14147 
14148 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14149   int ret = 0, structSize = gl.fragSize;
14150   if (gl.nuniforms+n > gl.cuniforms) {
14151     ubyte* uniforms;
14152     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14153     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14154     if (uniforms is null) return -1;
14155     gl.uniforms = uniforms;
14156     gl.cuniforms = cuniforms;
14157   }
14158   ret = gl.nuniforms*structSize;
14159   gl.nuniforms += n;
14160   return ret;
14161 }
14162 
14163 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14164   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14165 }
14166 
14167 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14168   vtx.x = x;
14169   vtx.y = y;
14170   vtx.u = u;
14171   vtx.v = v;
14172 }
14173 
14174 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 {
14175   if (npaths < 1) return;
14176 
14177   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14178   GLNVGcall* call = glnvg__allocCall(gl);
14179   NVGVertex* quad;
14180   GLNVGfragUniforms* frag;
14181   int maxverts, offset;
14182 
14183   if (call is null) return;
14184 
14185   call.type = GLNVG_FILL;
14186   call.evenOdd = evenOdd;
14187   call.clipmode = clipmode;
14188   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14189   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14190   call.triangleCount = 4;
14191   call.pathOffset = glnvg__allocPaths(gl, npaths);
14192   if (call.pathOffset == -1) goto error;
14193   call.pathCount = npaths;
14194   call.image = paint.image.id;
14195   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14196 
14197   if (npaths == 1 && paths[0].convex) {
14198     call.type = GLNVG_CONVEXFILL;
14199     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14200   }
14201 
14202   // Allocate vertices for all the paths.
14203   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14204   offset = glnvg__allocVerts(gl, maxverts);
14205   if (offset == -1) goto error;
14206 
14207   foreach (int i; 0..npaths) {
14208     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14209     const(NVGpath)* path = &paths[i];
14210     memset(copy, 0, GLNVGpath.sizeof);
14211     if (path.nfill > 0) {
14212       copy.fillOffset = offset;
14213       copy.fillCount = path.nfill;
14214       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14215       offset += path.nfill;
14216     }
14217     if (path.nstroke > 0) {
14218       copy.strokeOffset = offset;
14219       copy.strokeCount = path.nstroke;
14220       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14221       offset += path.nstroke;
14222     }
14223   }
14224 
14225   // Setup uniforms for draw calls
14226   if (call.type == GLNVG_FILL) {
14227     import core.stdc.string : memcpy;
14228     // Quad
14229     call.triangleOffset = offset;
14230     quad = &gl.verts[call.triangleOffset];
14231     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14232     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14233     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14234     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14235     // Get uniform
14236     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14237     if (call.uniformOffset == -1) goto error;
14238     // Simple shader for stencil
14239     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14240     memset(frag, 0, (*frag).sizeof);
14241     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14242     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14243     frag.strokeThr = -1.0f;
14244     frag.type = NSVG_SHADER_SIMPLE;
14245     // Fill shader
14246     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14247   } else {
14248     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14249     if (call.uniformOffset == -1) goto error;
14250     // Fill shader
14251     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14252   }
14253 
14254   return;
14255 
14256 error:
14257   // We get here if call alloc was ok, but something else is not.
14258   // Roll back the last call to prevent drawing it.
14259   if (gl.ncalls > 0) --gl.ncalls;
14260 }
14261 
14262 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 {
14263   if (npaths < 1) return;
14264 
14265   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14266   GLNVGcall* call = glnvg__allocCall(gl);
14267   int maxverts, offset;
14268 
14269   if (call is null) return;
14270 
14271   call.type = GLNVG_STROKE;
14272   call.clipmode = clipmode;
14273   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14274   call.pathOffset = glnvg__allocPaths(gl, npaths);
14275   if (call.pathOffset == -1) goto error;
14276   call.pathCount = npaths;
14277   call.image = paint.image.id;
14278   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14279 
14280   // Allocate vertices for all the paths.
14281   maxverts = glnvg__maxVertCount(paths, npaths);
14282   offset = glnvg__allocVerts(gl, maxverts);
14283   if (offset == -1) goto error;
14284 
14285   foreach (int i; 0..npaths) {
14286     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14287     const(NVGpath)* path = &paths[i];
14288     memset(copy, 0, GLNVGpath.sizeof);
14289     if (path.nstroke) {
14290       copy.strokeOffset = offset;
14291       copy.strokeCount = path.nstroke;
14292       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14293       offset += path.nstroke;
14294     }
14295   }
14296 
14297   if (gl.flags&NVGContextFlag.StencilStrokes) {
14298     // Fill shader
14299     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14300     if (call.uniformOffset == -1) goto error;
14301     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14302     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14303   } else {
14304     // Fill shader
14305     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14306     if (call.uniformOffset == -1) goto error;
14307     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14308   }
14309 
14310   return;
14311 
14312 error:
14313   // We get here if call alloc was ok, but something else is not.
14314   // Roll back the last call to prevent drawing it.
14315   if (gl.ncalls > 0) --gl.ncalls;
14316 }
14317 
14318 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14319   if (nverts < 1) return;
14320 
14321   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14322   GLNVGcall* call = glnvg__allocCall(gl);
14323   GLNVGfragUniforms* frag;
14324 
14325   if (call is null) return;
14326 
14327   call.type = GLNVG_TRIANGLES;
14328   call.clipmode = clipmode;
14329   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14330   call.image = paint.image.id;
14331   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14332 
14333   // Allocate vertices for all the paths.
14334   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14335   if (call.triangleOffset == -1) goto error;
14336   call.triangleCount = nverts;
14337 
14338   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14339 
14340   // Fill shader
14341   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14342   if (call.uniformOffset == -1) goto error;
14343   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14344   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14345   frag.type = NSVG_SHADER_IMG;
14346 
14347   return;
14348 
14349 error:
14350   // We get here if call alloc was ok, but something else is not.
14351   // Roll back the last call to prevent drawing it.
14352   if (gl.ncalls > 0) --gl.ncalls;
14353 }
14354 
14355 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14356   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14357   if (gl is null) return;
14358 
14359   glnvg__killFBOs(gl);
14360   glnvg__deleteShader(&gl.shader);
14361   glnvg__deleteShader(&gl.shaderFillFBO);
14362   glnvg__deleteShader(&gl.shaderCopyFBO);
14363 
14364   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14365 
14366   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14367     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14368       assert(tex.tex != 0);
14369       glDeleteTextures(1, &tex.tex);
14370     }
14371   }
14372   free(gl.textures);
14373 
14374   free(gl.paths);
14375   free(gl.verts);
14376   free(gl.uniforms);
14377   free(gl.calls);
14378 
14379   free(gl);
14380 }
14381 
14382 
14383 /** Creates NanoVega contexts for OpenGL2+.
14384  *
14385  * Specify creation flags as additional arguments, like this:
14386  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14387  *
14388  * If you won't specify any flags, defaults will be used:
14389  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14390  *
14391  * Group: context_management
14392  */
14393 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14394   version(aliced) {
14395     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14396   } else {
14397     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14398   }
14399   uint flags = 0;
14400   if (flagList.length != 0) {
14401     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14402   } else {
14403     flags = DefaultFlags;
14404   }
14405   NVGparams params = void;
14406   NVGContext ctx = null;
14407   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14408   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14409   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14410   if (gl is null) goto error;
14411   memset(gl, 0, GLNVGcontext.sizeof);
14412 
14413   memset(&params, 0, params.sizeof);
14414   params.renderCreate = &glnvg__renderCreate;
14415   params.renderCreateTexture = &glnvg__renderCreateTexture;
14416   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14417   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14418   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14419   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14420   params.renderViewport = &glnvg__renderViewport;
14421   params.renderCancel = &glnvg__renderCancel;
14422   params.renderFlush = &glnvg__renderFlush;
14423   params.renderPushClip = &glnvg__renderPushClip;
14424   params.renderPopClip = &glnvg__renderPopClip;
14425   params.renderResetClip = &glnvg__renderResetClip;
14426   params.renderFill = &glnvg__renderFill;
14427   params.renderStroke = &glnvg__renderStroke;
14428   params.renderTriangles = &glnvg__renderTriangles;
14429   params.renderSetAffine = &glnvg__renderSetAffine;
14430   params.renderDelete = &glnvg__renderDelete;
14431   params.userPtr = gl;
14432   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14433   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14434     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14435   } else {
14436     params.fontAA = NVG_INVERT_FONT_AA;
14437   }
14438 
14439   gl.flags = flags;
14440   gl.freetexid = -1;
14441 
14442   ctx = createInternal(&params);
14443   if (ctx is null) goto error;
14444 
14445   static if (__VERSION__ < 2076) {
14446     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14447   } else {
14448     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14449   }
14450 
14451   return ctx;
14452 
14453 error:
14454   // 'gl' is freed by nvgDeleteInternal.
14455   if (ctx !is null) ctx.deleteInternal();
14456   return null;
14457 }
14458 
14459 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14460 /// Group: images
14461 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14462   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14463   GLNVGtexture* tex = glnvg__allocTexture(gl);
14464 
14465   if (tex is null) return 0;
14466 
14467   tex.type = NVGtexture.RGBA;
14468   tex.tex = textureId;
14469   tex.flags = imageFlags;
14470   tex.width = w;
14471   tex.height = h;
14472 
14473   return tex.id;
14474 }
14475 
14476 /// Create NVGImage from OpenGL texture id.
14477 /// Group: images
14478 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14479   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14480 
14481   NVGImage res;
14482   if (id > 0) {
14483     res.id = id;
14484     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14485     res.ctx = ctx;
14486     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14487   }
14488   return res;
14489 }
14490 
14491 /// Returns OpenGL texture id for NanoVega image.
14492 /// Group: images
14493 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14494   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14495   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14496   return tex.tex;
14497 }
14498 
14499 
14500 // ////////////////////////////////////////////////////////////////////////// //
14501 private:
14502 
14503 static if (NanoVegaHasFontConfig) {
14504   version(nanovg_builtin_fontconfig_bindings) {
14505     pragma(lib, "fontconfig");
14506 
14507     private extern(C) nothrow @trusted @nogc {
14508       enum FC_FILE = "file"; /* String */
14509       alias FcBool = int;
14510       alias FcChar8 = char;
14511       struct FcConfig;
14512       struct FcPattern;
14513       alias FcMatchKind = int;
14514       enum : FcMatchKind {
14515         FcMatchPattern,
14516         FcMatchFont,
14517         FcMatchScan
14518       }
14519       alias FcResult = int;
14520       enum : FcResult {
14521         FcResultMatch,
14522         FcResultNoMatch,
14523         FcResultTypeMismatch,
14524         FcResultNoId,
14525         FcResultOutOfMemory
14526       }
14527       FcBool FcInit ();
14528       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14529       void FcDefaultSubstitute (FcPattern* pattern);
14530       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14531       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14532       FcPattern* FcNameParse (const(FcChar8)* name);
14533       void FcPatternDestroy (FcPattern* p);
14534       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14535     }
14536   }
14537 
14538   __gshared bool fontconfigAvailable = false;
14539   // initialize fontconfig
14540   shared static this () {
14541     if (FcInit()) {
14542       fontconfigAvailable = true;
14543     } else {
14544       import core.stdc.stdio : stderr, fprintf;
14545       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14546     }
14547   }
14548 }
14549 
14550 
14551 // ////////////////////////////////////////////////////////////////////////// //
14552 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14553 
14554 private static immutable ubyte[7641] baphometPath = [
14555   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14556   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14557   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14558   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14559   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14560   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14561   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14562   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14563   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14564   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14565   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14566   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14567   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14568   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14569   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14570   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14571   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14572   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14573   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14574   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14575   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14576   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14577   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14578   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14579   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14580   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14581   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14582   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14583   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14584   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14585   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14586   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14587   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14588   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14589   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14590   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14591   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14592   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14593   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14594   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14595   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14596   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14597   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14598   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14599   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14600   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14601   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14602   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14603   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14604   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14605   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14606   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14607   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14608   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14609   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14610   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14611   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14612   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14613   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14614   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14615   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14616   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14617   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14618   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14619   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14620   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14621   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14622   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14623   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14624   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14625   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14626   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14627   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14628   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14629   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14630   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14631   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14632   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14633   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14634   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14635   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14636   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14637   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14638   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14639   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14640   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14641   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14642   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14643   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14644   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14645   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14646   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14647   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14648   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14649   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14650   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14651   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14652   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14653   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14654   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14655   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14656   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14657   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14658   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14659   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14660   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14661   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14662   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14663   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14664   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14665   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14666   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14667   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14668   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14669   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14670   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14671   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14672   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14673   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14674   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14675   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14676   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14677   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14678   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14679   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14680   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14681   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14682   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14683   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14684   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14685   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14686   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14687   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14688   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14689   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14690   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14691   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14692   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14693   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14694   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14695   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14696   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14697   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14698   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14699   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14700   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14701   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14702   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14703   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14704   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14705   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14706   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14707   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14708   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14709   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14710   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14711   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14712   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14713   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14714   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14715   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14716   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14717   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14718   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14719   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14720   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14721   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14722   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14723   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14724   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14725   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14726   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14727   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14728   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14729   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14730   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14731   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14732   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14733   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14734   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14735   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14736   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14737   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14738   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14739   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14740   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14741   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14742   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14743   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14744   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14745   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14746   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14747   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14748   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14749   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14750   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14751   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14752   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14753   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14754   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14755   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14756   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14757   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14758   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14759   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14760   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14761   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14762   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14763   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14764   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14765   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14766   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14767   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14768   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14769   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14770   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14771   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14772   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14773   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14774   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14775   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14776   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14777   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14778   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14779   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14780   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14781   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14782   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14783   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14784   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14785   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14786   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14787   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14788   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14789   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14790   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14791   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14792   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14793   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14794   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14795   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14796   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14797   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14798   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14799   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14800   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14801   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14802   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14803   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14804   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14805   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14806   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14807   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14808   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14809   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14810   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14811   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14812   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14813   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14814   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14815   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14816   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14817   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14818   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14819   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14820   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14821   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14822   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14823   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14824   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14825   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14826   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14827   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14828   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14829   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14830   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14831   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14832   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14833   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14834   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14835   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14836   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14837   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14838   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14839   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14840   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14841   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14842   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14843   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14844   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14845   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14846   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14847   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14848   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14849   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14850   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14851   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14852   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14853   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14854   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14855   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14856   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14857   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14858   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14859   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14860   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14861   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14862   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14863   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14864   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14865   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14866   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14867   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14868   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14869   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14870   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14871   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14872   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14873   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14874   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14875   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14876   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14877   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14878   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14879   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14880   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14881   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14882   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14883   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14884   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14885   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14886   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14887   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14888   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14889   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14890   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14891   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14892   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14893   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14894   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14895   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14896   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14897   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14898   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14899   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14900   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14901   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14902   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14903   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14904   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14905   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14906   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14907   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14908   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14909   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14910   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14911   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14912   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14913   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14914   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14915   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14916   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14917   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14918   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14919   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14920   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14921   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14922   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14923   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14924   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14925   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14926   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14927   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14928   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14929   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14930   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14931   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14932   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14933   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14934   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14935   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14936   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14937   0x09,];
14938 
14939 private struct ThePath {
14940 public:
14941   enum Command {
14942     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14943     StrokeMode,
14944     FillMode,
14945     StrokeFillMode,
14946     NormalStroke,
14947     ThinStroke,
14948     MoveTo,
14949     LineTo,
14950     CubicTo, // cubic bezier
14951     EndPath,
14952   }
14953 
14954 public:
14955   const(ubyte)[] path;
14956   uint ppos;
14957 
14958 public:
14959   this (const(void)[] apath) pure nothrow @trusted @nogc {
14960     path = cast(const(ubyte)[])apath;
14961   }
14962 
14963   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14964 
14965   Command getCommand () nothrow @trusted @nogc {
14966     pragma(inline, true);
14967     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14968     return cast(Command)(path.ptr[ppos++]);
14969   }
14970 
14971   // number of (x,y) pairs for this command
14972   static int argCount (in Command cmd) nothrow @safe @nogc {
14973     version(aliced) pragma(inline, true);
14974          if (cmd == Command.Bounds) return 2;
14975     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14976     else if (cmd == Command.CubicTo) return 3;
14977     else return 0;
14978   }
14979 
14980   void skipArgs (int argc) nothrow @trusted @nogc {
14981     pragma(inline, true);
14982     ppos += cast(uint)(float.sizeof*2*argc);
14983   }
14984 
14985   float getFloat () nothrow @trusted @nogc {
14986     pragma(inline, true);
14987     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
14988     version(LittleEndian) {
14989       float res = *cast(const(float)*)(&path.ptr[ppos]);
14990       ppos += cast(uint)float.sizeof;
14991       return res;
14992     } else {
14993       static assert(float.sizeof == 4);
14994       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
14995       ppos += cast(uint)float.sizeof;
14996       return *cast(const(float)*)(&xp);
14997     }
14998   }
14999 }
15000 
15001 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
15002 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15003   if (nvg is null) return;
15004 
15005   auto path = ThePath(baphometPath);
15006 
15007   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15008   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15009 
15010   bool inPath = false;
15011   while (!path.empty) {
15012     auto cmd = path.getCommand();
15013     switch (cmd) {
15014       case ThePath.Command.MoveTo:
15015         inPath = true;
15016         immutable float ex = getScaledX();
15017         immutable float ey = getScaledY();
15018         nvg.moveTo(ex, ey);
15019         break;
15020       case ThePath.Command.LineTo:
15021         inPath = true;
15022         immutable float ex = getScaledX();
15023         immutable float ey = getScaledY();
15024         nvg.lineTo(ex, ey);
15025         break;
15026       case ThePath.Command.CubicTo: // cubic bezier
15027         inPath = true;
15028         immutable float x1 = getScaledX();
15029         immutable float y1 = getScaledY();
15030         immutable float x2 = getScaledX();
15031         immutable float y2 = getScaledY();
15032         immutable float ex = getScaledX();
15033         immutable float ey = getScaledY();
15034         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15035         break;
15036       case ThePath.Command.EndPath:
15037         if (inPath) return;
15038         break;
15039       default:
15040         path.skipArgs(path.argCount(cmd));
15041         break;
15042     }
15043   }
15044 }
15045 
15046 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15047 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 {
15048   // pupils starts with "fill-and-stroke" mode
15049   if (nvg is null) return;
15050 
15051   auto path = ThePath(baphometPath);
15052 
15053   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15054   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15055 
15056   bool inPath = false;
15057   bool pupLeft = true;
15058   while (!path.empty) {
15059     auto cmd = path.getCommand();
15060     switch (cmd) {
15061       case ThePath.Command.StrokeFillMode: inPath = true; break;
15062       case ThePath.Command.MoveTo:
15063         if (!inPath) goto default;
15064         static if (!left) { if (pupLeft) goto default; }
15065         static if (!right) { if (!pupLeft) goto default; }
15066         immutable float ex = getScaledX();
15067         immutable float ey = getScaledY();
15068         nvg.moveTo(ex, ey);
15069         break;
15070       case ThePath.Command.LineTo:
15071         if (!inPath) goto default;
15072         static if (!left) { if (pupLeft) goto default; }
15073         static if (!right) { if (!pupLeft) goto default; }
15074         immutable float ex = getScaledX();
15075         immutable float ey = getScaledY();
15076         nvg.lineTo(ex, ey);
15077         break;
15078       case ThePath.Command.CubicTo: // cubic bezier
15079         if (!inPath) goto default;
15080         static if (!left) { if (pupLeft) goto default; }
15081         static if (!right) { if (!pupLeft) goto default; }
15082         immutable float x1 = getScaledX();
15083         immutable float y1 = getScaledY();
15084         immutable float x2 = getScaledX();
15085         immutable float y2 = getScaledY();
15086         immutable float ex = getScaledX();
15087         immutable float ey = getScaledY();
15088         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15089         break;
15090       case ThePath.Command.EndPath:
15091         if (inPath) {
15092           if (pupLeft) pupLeft = false; else return;
15093         }
15094         break;
15095       default:
15096         path.skipArgs(path.argCount(cmd));
15097         break;
15098     }
15099   }
15100 }
15101 
15102 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15103 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15104   template hasChar(char ch, string s) {
15105          static if (s.length == 0) enum hasChar = false;
15106     else static if (s[0] == ch) enum hasChar = true;
15107     else enum hasChar = hasChar!(ch, s[1..$]);
15108   }
15109   enum AllowStroke = hasChar!('s', mode);
15110   enum AllowFill = hasChar!('f', mode);
15111   enum AllowWidth = hasChar!('w', mode);
15112   enum Contour = hasChar!('c', mode);
15113   //static assert(AllowWidth || AllowFill);
15114 
15115   if (nvg is null) return;
15116 
15117   auto path = ThePath(baphometPath);
15118 
15119   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15120   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15121 
15122   int mode = 0;
15123   int sw = ThePath.Command.NormalStroke;
15124   nvg.beginPath();
15125   while (!path.empty) {
15126     auto cmd = path.getCommand();
15127     switch (cmd) {
15128       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15129       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15130       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15131       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15132       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15133       case ThePath.Command.MoveTo:
15134         immutable float ex = getScaledX();
15135         immutable float ey = getScaledY();
15136         nvg.moveTo(ex, ey);
15137         break;
15138       case ThePath.Command.LineTo:
15139         immutable float ex = getScaledX();
15140         immutable float ey = getScaledY();
15141         nvg.lineTo(ex, ey);
15142         break;
15143       case ThePath.Command.CubicTo: // cubic bezier
15144         immutable float x1 = getScaledX();
15145         immutable float y1 = getScaledY();
15146         immutable float x2 = getScaledX();
15147         immutable float y2 = getScaledY();
15148         immutable float ex = getScaledX();
15149         immutable float ey = getScaledY();
15150         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15151         break;
15152       case ThePath.Command.EndPath:
15153         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15154           static if (AllowFill || Contour) {
15155             static if (Contour) {
15156               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15157             } else {
15158               nvg.fill();
15159             }
15160           }
15161         }
15162         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15163           static if (AllowStroke || Contour) {
15164             static if (AllowWidth) {
15165                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15166               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15167               else assert(0, "wtf?!");
15168             }
15169             nvg.stroke();
15170           }
15171         }
15172         nvg.newPath();
15173         break;
15174       default:
15175         path.skipArgs(path.argCount(cmd));
15176         break;
15177     }
15178   }
15179   nvg.newPath();
15180 }