1 //
2 // Would be nice: way to take output of the canvas to an image file (raster and/or svg)
3 //
4 //
5 // Copyright (c) 2013 Mikko Mononen memon@inside.org
6 //
7 // This software is provided 'as-is', without any express or implied
8 // warranty.  In no event will the authors be held liable for any damages
9 // arising from the use of this software.
10 // Permission is granted to anyone to use this software for any purpose,
11 // including commercial applications, and to alter it and redistribute it
12 // freely, subject to the following restrictions:
13 // 1. The origin of this software must not be misrepresented; you must not
14 //    claim that you wrote the original software. If you use this software
15 //    in a product, an acknowledgment in the product documentation would be
16 //    appreciated but is not required.
17 // 2. Altered source versions must be plainly marked as such, and must not be
18 //    misrepresented as being the original software.
19 // 3. This notice may not be removed or altered from any source distribution.
20 //
21 // Fork developement, feature integration and new bugs:
22 // Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
23 // Contains code from various contributors.
24 /**
25 The NanoVega API is modeled loosely on HTML5 canvas API.
26 If you know canvas, you're up to speed with NanoVega in no time.
27 
28 $(SIDE_BY_SIDE
29 
30 	$(COLUMN
31 		D code with nanovega:
32 
33 		---
34 		import arsd.nanovega;
35 		import arsd.simpledisplay;
36 
37 		void main () {
38 			// The NVGWindow class creates a window and sets up the nvg context for you
39 			// you can also do these steps yourself, see the other examples in these docs
40 			auto window = new NVGWindow(800, 600, "NanoVega Simple Sample");
41 
42 			window.redrawNVGScene = delegate (nvg) {
43 				nvg.beginPath(); // start new path
44 				nvg.roundedRect(20.5, 30.5, window.width-40, window.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
45 				// now set filling mode for our rectangle
46 				// you can create colors using HTML syntax, or with convenient constants
47 				nvg.fillPaint = nvg.linearGradient(20.5, 30.5, window.width-40, window.height-60,
48 				NVGColor("#f70"), NVGColor.green);
49 				// now fill our rect
50 				nvg.fill();
51 				// and draw a nice outline
52 				nvg.strokeColor = NVGColor.white;
53 				nvg.strokeWidth = 2;
54 				nvg.stroke();
55 				// that's all, folks!
56 			};
57 
58 			window.eventLoop(0,
59 				delegate (KeyEvent event) {
60 					if (event == "*-Q" || event == "Escape") { window.close(); return; } // quit on Q, Ctrl+Q, and so on
61 				},
62 			);
63 		}
64 		---
65 	)
66 
67 	$(COLUMN
68 		Javascript code with HTML5 Canvas
69 
70 		```html
71 		<!DOCTYPE html>
72 		<html>
73 		<head>
74 			<title>NanoVega Simple Sample (HTML5 Translation)</title>
75 			<style>
76 				body { background-color: black; }
77 			</style>
78 		</head>
79 		<body>
80 			<canvas id="my-canvas" width="800" height="600"></canvas>
81 		<script>
82 			var canvas = document.getElementById("my-canvas");
83 			var context = canvas.getContext("2d");
84 
85 			context.beginPath();
86 
87 			context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60);
88 
89 			var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60);
90 			gradient.addColorStop(0, "#f70");
91 			gradient.addColorStop(1, "green");
92 
93 			context.fillStyle = gradient;
94 			context.fill();
95 			context.closePath();
96 			context.strokeStyle = "white";
97 			context.lineWidth = 2;
98 			context.stroke();
99 		</script>
100 		</body>
101 		</html>
102 		```
103 	)
104 )
105 
106 $(TIP
107     This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType.
108     Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler.
109 )
110 
111 
112 Creating drawing context
113 ========================
114 
115 The drawing context is created using platform specific constructor function.
116 
117   ---
118   NVGContext vg = nvgCreateContext();
119   ---
120 
121 $(WARNING You must use created context ONLY in that thread where you created it.
122           There is no way to "transfer" context between threads. Trying to do so
123           will lead to UB.)
124 
125 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to
126           do so will lead to UB.)
127 
128 
129 Drawing shapes with NanoVega
130 ============================
131 
132 Drawing a simple shape using NanoVega consists of four steps:
133 $(LIST
134   * begin a new shape,
135   * define the path to draw,
136   * set fill or stroke,
137   * and finally fill or stroke the path.
138 )
139 
140   ---
141   vg.beginPath();
142   vg.rect(100, 100, 120, 30);
143   vg.fillColor(nvgRGBA(255, 192, 0, 255));
144   vg.fill();
145   ---
146 
147 Calling [beginPath] will clear any existing paths and start drawing from blank slate.
148 There are number of number of functions to define the path to draw, such as rectangle,
149 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and
150 arcTo API to compose the paths step by step.
151 
152 
153 Understanding Composite Paths
154 =============================
155 
156 Because of the way the rendering backend is built in NanoVega, drawing a composite path,
157 that is path consisting from multiple paths defining holes and fills, is a bit more
158 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter
159 clockwise order. Keep that in mind when drawing using the low level draw API. In order to
160 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`,
161 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path.
162 
163   ---
164   vg.beginPath();
165   vg.rect(100, 100, 120, 30);
166   vg.circle(120, 120, 5);
167   vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole
168   vg.fillColor(nvgRGBA(255, 192, 0, 255));
169   vg.fill();
170   ---
171 
172 
173 Rendering is wrong, what to do?
174 ===============================
175 
176 $(LIST
177   * make sure you have created NanoVega context using [nvgCreateContext] call
178   * make sure you have initialised OpenGL with $(B stencil buffer)
179   * make sure you have cleared stencil buffer
180   * make sure all rendering calls happen between [beginFrame] and [endFrame]
181   * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext]
182 )
183 
184 
185 OpenGL state touched by the backend
186 ===================================
187 
188 The OpenGL back-end touches following states:
189 
190 When textures are uploaded or updated, the following pixel store is set to defaults:
191 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`.
192 Texture binding is also affected. Texture updates can happen when the user loads images,
193 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame]
194 and [endFrame].
195 
196 The data for the whole frame is buffered and flushed in [endFrame].
197 The following code illustrates the OpenGL state touched by the rendering code:
198 
199   ---
200   glUseProgram(prog);
201   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
202   glEnable(GL_CULL_FACE);
203   glCullFace(GL_BACK);
204   glFrontFace(GL_CCW);
205   glEnable(GL_BLEND);
206   glDisable(GL_DEPTH_TEST);
207   glDisable(GL_SCISSOR_TEST);
208   glDisable(GL_COLOR_LOGIC_OP);
209   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
210   glStencilMask(0xffffffff);
211   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
212   glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
213   glActiveTexture(GL_TEXTURE1);
214   glActiveTexture(GL_TEXTURE0);
215   glBindBuffer(GL_UNIFORM_BUFFER, buf);
216   glBindVertexArray(arr);
217   glBindBuffer(GL_ARRAY_BUFFER, buf);
218   glBindTexture(GL_TEXTURE_2D, tex);
219   glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
220   ---
221 
222   Symbol_groups:
223 
224   context_management =
225     ## Context Management
226 
227     Functions to create and destory NanoVega context.
228 
229   frame_management =
230     ## Frame Management
231 
232     To start drawing with NanoVega context, you have to "begin frame", and then
233     "end frame" to flush your rendering commands to GPU.
234 
235   composite_operation =
236     ## Composite Operation
237 
238     The composite operations in NanoVega are modeled after HTML Canvas API, and
239     the blend func is based on OpenGL (see corresponding manuals for more info).
240     The colors in the blending state have premultiplied alpha.
241 
242   color_utils =
243     ## Color Utils
244 
245     Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color".
246 
247   matrices =
248     ## Matrices and Transformations
249 
250     The paths, gradients, patterns and scissor region are transformed by an transformation
251     matrix at the time when they are passed to the API.
252     The current transformation matrix is an affine matrix:
253 
254     ----------------------
255       [sx kx tx]
256       [ky sy ty]
257       [ 0  0  1]
258     ----------------------
259 
260     Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation.
261     The last row is assumed to be (0, 0, 1) and is not stored.
262 
263     Apart from [resetTransform], each transformation function first creates
264     specific transformation matrix and pre-multiplies the current transformation by it.
265 
266     Current coordinate system (transformation) can be saved and restored using [save] and [restore].
267 
268     The following functions can be used to make calculations on 2x3 transformation matrices.
269     A 2x3 matrix is represented as float[6].
270 
271   state_handling =
272     ## State Handling
273 
274     NanoVega contains state which represents how paths will be rendered.
275     The state contains transform, fill and stroke styles, text and font styles,
276     and scissor clipping.
277 
278   render_styles =
279     ## Render Styles
280 
281     Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
282     Solid color is simply defined as a color value, different kinds of paints can be created
283     using [linearGradient], [boxGradient], [radialGradient] and [imagePattern].
284 
285     Current render style can be saved and restored using [save] and [restore].
286 
287     Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1,
288     and use `integerCoord+0.5f` as pixel coordinates.
289 
290   render_transformations =
291     ## Render Transformations
292 
293     Transformation matrix management for the current rendering style. Transformations are applied in
294     backwards order. I.e. if you first translate, and then rotate, your path will be rotated around
295     it's origin, and then translated to the destination point.
296 
297   scissoring =
298     ## Scissoring
299 
300     Scissoring allows you to clip the rendering into a rectangle. This is useful for various
301     user interface cases like rendering a text edit or a timeline.
302 
303   images =
304     ## Images
305 
306     NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering.
307     In addition you can upload your own image.
308     The parameter imageFlagsList is a list of flags defined in [NVGImageFlag].
309 
310     If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass
311     [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively.
312 
313   paints =
314     ## Paints
315 
316     NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
317     These can be used as paints for strokes and fills.
318 
319   gpu_affine =
320     ## Render-Time Affine Transformations
321 
322     It is possible to set affine transformation matrix for GPU. That matrix will
323     be applied by the shader code. This can be used to quickly translate and rotate
324     saved paths. Call this $(B only) between [beginFrame] and [endFrame].
325 
326     Note that [beginFrame] resets this matrix to identity one.
327 
328     $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!)
329 
330   paths =
331     ## Paths
332 
333     Drawing a new shape starts with [beginPath], it clears all the currently defined paths.
334     Then you define one or more paths and sub-paths which describe the shape. The are functions
335     to draw common shapes like rectangles and circles, and lower level step-by-step functions,
336     which allow to define a path curve by curve.
337 
338     NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
339     winding and holes should have counter clockwise order. To specify winding of a path you can
340     call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW.
341 
342     Finally you can fill the path using current fill style by calling [fill], and stroke it
343     with current stroke style by calling [stroke].
344 
345     The curve segments and sub-paths are transformed by the current transform.
346 
347   picking_api =
348     ## Picking API
349 
350     This is picking API that works directly on paths, without rasterizing them first.
351 
352     [beginFrame] resets picking state. Then you can create paths as usual, but
353     there is a possibility to perform hit checks $(B before) rasterizing a path.
354     Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or
355     immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke])
356     before rasterizing (i.e. calling [fill] or [stroke]) to perform hover
357     effects, for example.
358 
359     Also note that picking API is ignoring GPU affine transformation matrix.
360     You can "untransform" picking coordinates before checking with [gpuUntransformPoint].
361 
362     $(WARNING Picking API completely ignores clipping. If you want to check for
363               clip regions, you have to manually register them as fill/stroke paths,
364               and perform the necessary logic. See [hitTestForId] function.)
365 
366   clipping =
367     ## Clipping with paths
368 
369     If scissoring is not enough for you, you can clip rendering with arbitrary path,
370     or with combination of paths. Clip region is saved by [save] and restored by
371     [restore] NanoVega functions. You can combine clip paths with various logic
372     operations, see [NVGClipMode].
373 
374     Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask
375     is created as if there was no scissor set). Actual rendering is affected by
376     scissors, though.
377 
378   text_api =
379     ## Text
380 
381     NanoVega allows you to load .ttf files and use the font to render text.
382     You have to load some font, and set proper font size before doing anything
383     with text, as there is no "default" font provided by NanoVega. Also, don't
384     forget to check return value of `createFont()`, 'cause NanoVega won't fail
385     if it cannot load font, it will silently try to render nothing.
386 
387     The appearance of the text can be defined by setting the current text style
388     and by specifying the fill color. Common text and font settings such as
389     font size, letter spacing and text align are supported. Font blur allows you
390     to create simple text effects such as drop shadows.
391 
392     At render time the font face can be set based on the font handles or name.
393 
394     Font measure functions return values in local space, the calculations are
395     carried in the same resolution as the final rendering. This is done because
396     the text glyph positions are snapped to the nearest pixels sharp rendering.
397 
398     The local space means that values are not rotated or scale as per the current
399     transformation. For example if you set font size to 12, which would mean that
400     line height is 16, then regardless of the current scaling and rotation, the
401     returned line height is always 16. Some measures may vary because of the scaling
402     since aforementioned pixel snapping.
403 
404     While this may sound a little odd, the setup allows you to always render the
405     same way regardless of scaling. I.e. following works regardless of scaling:
406 
407     ----------------------
408        string txt = "Text me up.";
409        vg.textBounds(x, y, txt, bounds);
410        vg.beginPath();
411        vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6);
412        vg.fill();
413     ----------------------
414 
415     Note: currently only solid color fill is supported for text.
416 
417   font_stash =
418     ## Low-Level Font Engine (FontStash)
419 
420     FontStash is used to load fonts, to manage font atlases, and to get various text metrics.
421     You don't need any graphics context to use FontStash, so you can do things like text
422     layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap
423     to create new FontStash, copy fonts from NanoVega context into it, and use that new
424     FontStash to do some UI layouting, for example. Also note that you can get text metrics
425     without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way
426     you don't need to waste CPU and memory resources to render unneeded images into font atlas,
427     and you can layout alot of text very fast.
428 
429     Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains
430     word "fons" in it, this is not a typo, and it should not read "font" intead.
431 
432     TODO for Ketmar: write some nice example code here, and finish documenting FontStash API.
433  */
434 module arsd.nanovega;
435 
436 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class.
437 unittest {
438 	import arsd.simpledisplay;
439 
440 	import arsd.nanovega;
441 
442 	void main () {
443 	  NVGContext nvg; // our NanoVega context
444 
445 	  // we need at least OpenGL3 with GLSL to use NanoVega,
446 	  // so let's tell simpledisplay about that
447 	  setOpenGLContextVersion(3, 0);
448 
449 	  // now create OpenGL window
450 	  auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing);
451 
452 	  // we need to destroy NanoVega context on window close
453 	  // stricly speaking, it is not necessary, as nothing fatal
454 	  // will happen if you'll forget it, but let's be polite.
455 	  // note that we cannot do that *after* our window was closed,
456 	  // as we need alive OpenGL context to do proper cleanup.
457 	  sdmain.onClosing = delegate () {
458 	    nvg.kill();
459 	  };
460 
461 	  // this is called just before our window will be shown for the first time.
462 	  // we must create NanoVega context here, as it needs to initialize
463 	  // internal OpenGL subsystem with valid OpenGL context.
464 	  sdmain.visibleForTheFirstTime = delegate () {
465 	    // yes, that's all
466 	    nvg = nvgCreateContext();
467 	    if (nvg is null) assert(0, "cannot initialize NanoVega");
468 	  };
469 
470 	  // this callback will be called when we will need to repaint our window
471 	  sdmain.redrawOpenGlScene = delegate () {
472 	    // fix viewport (we can do this in resize event, or here, it doesn't matter)
473 	    glViewport(0, 0, sdmain.width, sdmain.height);
474 
475 	    // clear window
476 	    glClearColor(0, 0, 0, 0);
477 	    glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call
478 
479 	    {
480 	      nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering
481 	      scope(exit) nvg.endFrame(); // and flush render queue on exit
482 
483 	      nvg.beginPath(); // start new path
484 	      nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation)
485 	      // now set filling mode for our rectangle
486 	      // you can create colors using HTML syntax, or with convenient constants
487 	      nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green);
488 	      // now fill our rect
489 	      nvg.fill();
490 	      // and draw a nice outline
491 	      nvg.strokeColor = NVGColor.white;
492 	      nvg.strokeWidth = 2;
493 	      nvg.stroke();
494 	      // that's all, folks!
495 	    }
496 	  };
497 
498 	  sdmain.eventLoop(0, // no pulse timer required
499 	    delegate (KeyEvent event) {
500 	      if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on
501 	    },
502 	  );
503 
504 	  flushGui(); // let OS do it's cleanup
505 	}
506 }
507 
508 private:
509 
510 version(aliced) {
511   import iv.meta;
512   import iv.vfs;
513 } else {
514   private alias usize = size_t;
515   // i fear phobos!
516   private template Unqual(T) {
517          static if (is(T U ==          immutable U)) alias Unqual = U;
518     else static if (is(T U == shared inout const U)) alias Unqual = U;
519     else static if (is(T U == shared inout       U)) alias Unqual = U;
520     else static if (is(T U == shared       const U)) alias Unqual = U;
521     else static if (is(T U == shared             U)) alias Unqual = U;
522     else static if (is(T U ==        inout const U)) alias Unqual = U;
523     else static if (is(T U ==        inout       U)) alias Unqual = U;
524     else static if (is(T U ==              const U)) alias Unqual = U;
525     else alias Unqual = T;
526   }
527   private template isAnyCharType(T, bool unqual=false) {
528     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
529     enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
530   }
531   private template isWideCharType(T, bool unqual=false) {
532     static if (unqual) private alias UT = Unqual!T; else private alias UT = T;
533     enum isWideCharType = is(UT == wchar) || is(UT == dchar);
534   }
535 }
536 version(nanovg_disable_vfs) {
537   enum NanoVegaHasIVVFS = false;
538 } else {
539   static if (is(typeof((){import iv.vfs;}))) {
540     enum NanoVegaHasIVVFS = true;
541     import iv.vfs;
542   } else {
543     enum NanoVegaHasIVVFS = false;
544   }
545 }
546 
547 // ////////////////////////////////////////////////////////////////////////// //
548 // engine
549 // ////////////////////////////////////////////////////////////////////////// //
550 import core.stdc.stdlib : malloc, realloc, free;
551 import core.stdc.string : memset, memcpy, strlen;
552 import std.math : PI;
553 
554 //version = nanovg_force_stb_ttf;
555 
556 version(Posix) {
557   version = nanovg_use_freetype;
558 } else {
559   version = nanovg_disable_fontconfig;
560 }
561 
562 version (bindbc) {
563   version = nanovg_builtin_fontconfig_bindings;
564   version = nanovg_bindbc_opengl_bindings;
565   version = nanovg_bindbc_freetype_bindings;
566   version(BindFT_Dynamic)
567     static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings");
568   else {
569     version(BindFT_Static) {}
570     else
571       static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler");
572   }
573 } else version(aliced) {
574   version = nanovg_default_no_font_aa;
575   version = nanovg_builtin_fontconfig_bindings;
576   version = nanovg_builtin_freetype_bindings;
577   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
578 } else {
579   version = nanovg_builtin_fontconfig_bindings;
580   version = nanovg_builtin_freetype_bindings;
581   version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings
582 }
583 
584 version(nanovg_disable_fontconfig) {
585   public enum NanoVegaHasFontConfig = false;
586 } else {
587   public enum NanoVegaHasFontConfig = true;
588   version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig;
589 }
590 
591 //version = nanovg_bench_flatten;
592 
593 /++
594 	Annotation to indicate the marked function is compatible with [arsd.script].
595 
596 
597 	Any function that takes a [Color] argument will be passed a string instead.
598 
599 	Scriptable Functions
600 	====================
601 
602 	$(UDA_USES)
603 
604 	$(ALWAYS_DOCUMENT)
605 +/
606 private enum scriptable = "arsd_jsvar_compatible";
607 
608 public:
609 alias NVG_PI = PI;
610 
611 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; })));
612 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; })));
613 
614 static if (NanoVegaHasArsdColor) private import arsd.color;
615 static if (NanoVegaHasArsdImage) {
616   private import arsd.image;
617 } else {
618   void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {}
619   void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {}
620   ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; }
621   ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; }
622   void stbi_image_free (void* retval_from_stbi_load) {}
623 }
624 
625 version(nanovg_default_no_font_aa) {
626   __gshared bool NVG_INVERT_FONT_AA = false;
627 } else {
628   __gshared bool NVG_INVERT_FONT_AA = true;
629 }
630 
631 
632 /// this is branchless for ints on x86, and even for longs on x86_64
633 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
634   static if (__VERSION__ > 2067) pragma(inline, true);
635   static if (T.sizeof == 2 || T.sizeof == 4) {
636     static if (__traits(isUnsigned, T)) {
637       return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
638     } else {
639       n &= -cast(int)(n >= 0);
640       return cast(ubyte)(n|((255-cast(int)n)>>31));
641     }
642   } else static if (T.sizeof == 1) {
643     static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
644     return cast(ubyte)n;
645   } else static if (T.sizeof == 8) {
646     static if (__traits(isUnsigned, T)) {
647       return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
648     } else {
649       n &= -cast(long)(n >= 0);
650       return cast(ubyte)(n|((255-cast(long)n)>>63));
651     }
652   } else {
653     static assert(false, "clampToByte: integer too big");
654   }
655 }
656 
657 
658 /// NanoVega RGBA color
659 /// Group: color_utils
660 public align(1) struct NVGColor {
661 align(1):
662 public:
663   float[4] rgba = 0; /// default color is transparent (a=1 is opaque)
664 
665 public:
666   @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); }
667 
668 public:
669   enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f);
670   enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f);
671 
672   enum aliceblue = NVGColor(240, 248, 255);
673   enum antiquewhite = NVGColor(250, 235, 215);
674   enum aqua = NVGColor(0, 255, 255);
675   enum aquamarine = NVGColor(127, 255, 212);
676   enum azure = NVGColor(240, 255, 255);
677   enum beige = NVGColor(245, 245, 220);
678   enum bisque = NVGColor(255, 228, 196);
679   enum black = NVGColor(0, 0, 0); // basic color
680   enum blanchedalmond = NVGColor(255, 235, 205);
681   enum blue = NVGColor(0, 0, 255); // basic color
682   enum blueviolet = NVGColor(138, 43, 226);
683   enum brown = NVGColor(165, 42, 42);
684   enum burlywood = NVGColor(222, 184, 135);
685   enum cadetblue = NVGColor(95, 158, 160);
686   enum chartreuse = NVGColor(127, 255, 0);
687   enum chocolate = NVGColor(210, 105, 30);
688   enum coral = NVGColor(255, 127, 80);
689   enum cornflowerblue = NVGColor(100, 149, 237);
690   enum cornsilk = NVGColor(255, 248, 220);
691   enum crimson = NVGColor(220, 20, 60);
692   enum cyan = NVGColor(0, 255, 255); // basic color
693   enum darkblue = NVGColor(0, 0, 139);
694   enum darkcyan = NVGColor(0, 139, 139);
695   enum darkgoldenrod = NVGColor(184, 134, 11);
696   enum darkgray = NVGColor(169, 169, 169);
697   enum darkgreen = NVGColor(0, 100, 0);
698   enum darkgrey = NVGColor(169, 169, 169);
699   enum darkkhaki = NVGColor(189, 183, 107);
700   enum darkmagenta = NVGColor(139, 0, 139);
701   enum darkolivegreen = NVGColor(85, 107, 47);
702   enum darkorange = NVGColor(255, 140, 0);
703   enum darkorchid = NVGColor(153, 50, 204);
704   enum darkred = NVGColor(139, 0, 0);
705   enum darksalmon = NVGColor(233, 150, 122);
706   enum darkseagreen = NVGColor(143, 188, 143);
707   enum darkslateblue = NVGColor(72, 61, 139);
708   enum darkslategray = NVGColor(47, 79, 79);
709   enum darkslategrey = NVGColor(47, 79, 79);
710   enum darkturquoise = NVGColor(0, 206, 209);
711   enum darkviolet = NVGColor(148, 0, 211);
712   enum deeppink = NVGColor(255, 20, 147);
713   enum deepskyblue = NVGColor(0, 191, 255);
714   enum dimgray = NVGColor(105, 105, 105);
715   enum dimgrey = NVGColor(105, 105, 105);
716   enum dodgerblue = NVGColor(30, 144, 255);
717   enum firebrick = NVGColor(178, 34, 34);
718   enum floralwhite = NVGColor(255, 250, 240);
719   enum forestgreen = NVGColor(34, 139, 34);
720   enum fuchsia = NVGColor(255, 0, 255);
721   enum gainsboro = NVGColor(220, 220, 220);
722   enum ghostwhite = NVGColor(248, 248, 255);
723   enum gold = NVGColor(255, 215, 0);
724   enum goldenrod = NVGColor(218, 165, 32);
725   enum gray = NVGColor(128, 128, 128); // basic color
726   enum green = NVGColor(0, 128, 0); // basic color
727   enum greenyellow = NVGColor(173, 255, 47);
728   enum grey = NVGColor(128, 128, 128); // basic color
729   enum honeydew = NVGColor(240, 255, 240);
730   enum hotpink = NVGColor(255, 105, 180);
731   enum indianred = NVGColor(205, 92, 92);
732   enum indigo = NVGColor(75, 0, 130);
733   enum ivory = NVGColor(255, 255, 240);
734   enum khaki = NVGColor(240, 230, 140);
735   enum lavender = NVGColor(230, 230, 250);
736   enum lavenderblush = NVGColor(255, 240, 245);
737   enum lawngreen = NVGColor(124, 252, 0);
738   enum lemonchiffon = NVGColor(255, 250, 205);
739   enum lightblue = NVGColor(173, 216, 230);
740   enum lightcoral = NVGColor(240, 128, 128);
741   enum lightcyan = NVGColor(224, 255, 255);
742   enum lightgoldenrodyellow = NVGColor(250, 250, 210);
743   enum lightgray = NVGColor(211, 211, 211);
744   enum lightgreen = NVGColor(144, 238, 144);
745   enum lightgrey = NVGColor(211, 211, 211);
746   enum lightpink = NVGColor(255, 182, 193);
747   enum lightsalmon = NVGColor(255, 160, 122);
748   enum lightseagreen = NVGColor(32, 178, 170);
749   enum lightskyblue = NVGColor(135, 206, 250);
750   enum lightslategray = NVGColor(119, 136, 153);
751   enum lightslategrey = NVGColor(119, 136, 153);
752   enum lightsteelblue = NVGColor(176, 196, 222);
753   enum lightyellow = NVGColor(255, 255, 224);
754   enum lime = NVGColor(0, 255, 0);
755   enum limegreen = NVGColor(50, 205, 50);
756   enum linen = NVGColor(250, 240, 230);
757   enum magenta = NVGColor(255, 0, 255); // basic color
758   enum maroon = NVGColor(128, 0, 0);
759   enum mediumaquamarine = NVGColor(102, 205, 170);
760   enum mediumblue = NVGColor(0, 0, 205);
761   enum mediumorchid = NVGColor(186, 85, 211);
762   enum mediumpurple = NVGColor(147, 112, 219);
763   enum mediumseagreen = NVGColor(60, 179, 113);
764   enum mediumslateblue = NVGColor(123, 104, 238);
765   enum mediumspringgreen = NVGColor(0, 250, 154);
766   enum mediumturquoise = NVGColor(72, 209, 204);
767   enum mediumvioletred = NVGColor(199, 21, 133);
768   enum midnightblue = NVGColor(25, 25, 112);
769   enum mintcream = NVGColor(245, 255, 250);
770   enum mistyrose = NVGColor(255, 228, 225);
771   enum moccasin = NVGColor(255, 228, 181);
772   enum navajowhite = NVGColor(255, 222, 173);
773   enum navy = NVGColor(0, 0, 128);
774   enum oldlace = NVGColor(253, 245, 230);
775   enum olive = NVGColor(128, 128, 0);
776   enum olivedrab = NVGColor(107, 142, 35);
777   enum orange = NVGColor(255, 165, 0);
778   enum orangered = NVGColor(255, 69, 0);
779   enum orchid = NVGColor(218, 112, 214);
780   enum palegoldenrod = NVGColor(238, 232, 170);
781   enum palegreen = NVGColor(152, 251, 152);
782   enum paleturquoise = NVGColor(175, 238, 238);
783   enum palevioletred = NVGColor(219, 112, 147);
784   enum papayawhip = NVGColor(255, 239, 213);
785   enum peachpuff = NVGColor(255, 218, 185);
786   enum peru = NVGColor(205, 133, 63);
787   enum pink = NVGColor(255, 192, 203);
788   enum plum = NVGColor(221, 160, 221);
789   enum powderblue = NVGColor(176, 224, 230);
790   enum purple = NVGColor(128, 0, 128);
791   enum red = NVGColor(255, 0, 0); // basic color
792   enum rosybrown = NVGColor(188, 143, 143);
793   enum royalblue = NVGColor(65, 105, 225);
794   enum saddlebrown = NVGColor(139, 69, 19);
795   enum salmon = NVGColor(250, 128, 114);
796   enum sandybrown = NVGColor(244, 164, 96);
797   enum seagreen = NVGColor(46, 139, 87);
798   enum seashell = NVGColor(255, 245, 238);
799   enum sienna = NVGColor(160, 82, 45);
800   enum silver = NVGColor(192, 192, 192);
801   enum skyblue = NVGColor(135, 206, 235);
802   enum slateblue = NVGColor(106, 90, 205);
803   enum slategray = NVGColor(112, 128, 144);
804   enum slategrey = NVGColor(112, 128, 144);
805   enum snow = NVGColor(255, 250, 250);
806   enum springgreen = NVGColor(0, 255, 127);
807   enum steelblue = NVGColor(70, 130, 180);
808   enum tan = NVGColor(210, 180, 140);
809   enum teal = NVGColor(0, 128, 128);
810   enum thistle = NVGColor(216, 191, 216);
811   enum tomato = NVGColor(255, 99, 71);
812   enum turquoise = NVGColor(64, 224, 208);
813   enum violet = NVGColor(238, 130, 238);
814   enum wheat = NVGColor(245, 222, 179);
815   enum white = NVGColor(255, 255, 255); // basic color
816   enum whitesmoke = NVGColor(245, 245, 245);
817   enum yellow = NVGColor(255, 255, 0); // basic color
818   enum yellowgreen = NVGColor(154, 205, 50);
819 
820 nothrow @safe @nogc:
821 public:
822   ///
823   this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure {
824     pragma(inline, true);
825     r = ar/255.0f;
826     g = ag/255.0f;
827     b = ab/255.0f;
828     a = aa/255.0f;
829   }
830 
831   ///
832   this (float ar, float ag, float ab, float aa=1.0f) pure {
833     pragma(inline, true);
834     r = ar;
835     g = ag;
836     b = ab;
837     a = aa;
838   }
839 
840   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
841   this (uint c) pure {
842     pragma(inline, true);
843     r = (c&0xff)/255.0f;
844     g = ((c>>8)&0xff)/255.0f;
845     b = ((c>>16)&0xff)/255.0f;
846     a = ((c>>24)&0xff)/255.0f;
847   }
848 
849   /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
850   this (const(char)[] srgb) {
851     static int c2d (char ch) pure nothrow @safe @nogc {
852       pragma(inline, true);
853       return
854         ch >= '0' && ch <= '9' ? ch-'0' :
855         ch >= 'A' && ch <= 'F' ? ch-'A'+10 :
856         ch >= 'a' && ch <= 'f' ? ch-'a'+10 :
857         -1;
858     }
859     int[8] digs;
860     int dc = -1;
861     foreach (immutable char ch; srgb) {
862       if (ch <= ' ') continue;
863       if (ch == '#') {
864         if (dc != -1) { dc = -1; break; }
865         dc = 0;
866       } else {
867         if (dc >= digs.length) { dc = -1; break; }
868         if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; }
869       }
870     }
871     switch (dc) {
872       case 3: // rgb
873         a = 1.0f;
874         r = digs[0]/15.0f;
875         g = digs[1]/15.0f;
876         b = digs[2]/15.0f;
877         break;
878       case 4: // argb
879         a = digs[0]/15.0f;
880         r = digs[1]/15.0f;
881         g = digs[2]/15.0f;
882         b = digs[3]/15.0f;
883         break;
884       case 6: // rrggbb
885         a = 1.0f;
886         r = (digs[0]*16+digs[1])/255.0f;
887         g = (digs[2]*16+digs[3])/255.0f;
888         b = (digs[4]*16+digs[5])/255.0f;
889         break;
890       case 8: // aarrggbb
891         a = (digs[0]*16+digs[1])/255.0f;
892         r = (digs[2]*16+digs[3])/255.0f;
893         g = (digs[4]*16+digs[5])/255.0f;
894         b = (digs[6]*16+digs[7])/255.0f;
895         break;
896       default:
897         break;
898     }
899   }
900 
901   /// Is this color completely opaque?
902   @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); }
903   /// Is this color completely transparent?
904   @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); }
905 
906   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
907   @property uint asUint () const pure {
908     pragma(inline, true);
909     return
910       cast(uint)(r*255)|
911       (cast(uint)(g*255)<<8)|
912       (cast(uint)(b*255)<<16)|
913       (cast(uint)(a*255)<<24);
914   }
915 
916   alias asUintABGR = asUint; /// Ditto.
917 
918   /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color)
919   static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); }
920 
921   alias fromUintABGR = fromUint; /// Ditto.
922 
923   /// AARRGGBB
924   @property uint asUintARGB () const pure {
925     pragma(inline, true);
926     return
927       cast(uint)(b*255)|
928       (cast(uint)(g*255)<<8)|
929       (cast(uint)(r*255)<<16)|
930       (cast(uint)(a*255)<<24);
931   }
932 
933   /// AARRGGBB
934   static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); }
935 
936   @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } ///
937   @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } ///
938   @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } ///
939   @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } ///
940 
941   ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc {
942     if (tint.a == 0) return this;
943     foreach (immutable idx, ref float v; rgba[0..4]) {
944       v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f);
945     }
946     return this;
947   }
948 
949   NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } ///
950   static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } ///
951 
952   static if (NanoVegaHasArsdColor) {
953     Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } ///
954     static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } ///
955     ///
956     this (in Color c) {
957       version(aliced) pragma(inline, true);
958       r = c.r/255.0f;
959       g = c.g/255.0f;
960       b = c.b/255.0f;
961       a = c.a/255.0f;
962     }
963   }
964 }
965 
966 
967 /// NanoVega A-HSL color
968 /// Group: color_utils
969 public align(1) struct NVGHSL {
970 align(1):
971   float h=0, s=0, l=1, a=1; ///
972 
973   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)); }
974 
975 nothrow @safe @nogc:
976 public:
977   ///
978   this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; }
979 
980   NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } ///
981 
982   // taken from Adam's arsd.color
983   /** Converts an RGB color into an HSL triplet.
984    * [useWeightedLightness] will try to get a better value for luminosity for the human eye,
985    * which is more sensitive to green than red and more to red than blue.
986    * If it is false, it just does average of the rgb. */
987   static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure {
988     NVGHSL res;
989     res.a = c.a;
990     float r1 = c.r;
991     float g1 = c.g;
992     float b1 = c.b;
993 
994     float maxColor = r1;
995     if (g1 > maxColor) maxColor = g1;
996     if (b1 > maxColor) maxColor = b1;
997     float minColor = r1;
998     if (g1 < minColor) minColor = g1;
999     if (b1 < minColor) minColor = b1;
1000 
1001     res.l = (maxColor+minColor)/2;
1002     if (useWeightedLightness) {
1003       // the colors don't affect the eye equally
1004       // this is a little more accurate than plain HSL numbers
1005       res.l = 0.2126*r1+0.7152*g1+0.0722*b1;
1006     }
1007     if (maxColor != minColor) {
1008       if (res.l < 0.5) {
1009         res.s = (maxColor-minColor)/(maxColor+minColor);
1010       } else {
1011         res.s = (maxColor-minColor)/(2.0-maxColor-minColor);
1012       }
1013       if (r1 == maxColor) {
1014         res.h = (g1-b1)/(maxColor-minColor);
1015       } else if(g1 == maxColor) {
1016         res.h = 2.0+(b1-r1)/(maxColor-minColor);
1017       } else {
1018         res.h = 4.0+(r1-g1)/(maxColor-minColor);
1019       }
1020     }
1021 
1022     res.h = res.h*60;
1023     if (res.h < 0) res.h += 360;
1024     res.h /= 360;
1025 
1026     return res;
1027   }
1028 }
1029 
1030 
1031 //version = nanovega_debug_image_manager;
1032 //version = nanovega_debug_image_manager_rc;
1033 
1034 /** NanoVega image handle.
1035  *
1036  * This is refcounted struct, so you don't need to do anything special to free it once it is allocated.
1037  *
1038  * Group: images
1039  */
1040 struct NVGImage {
1041 	enum isOpaqueStruct = true;
1042 private:
1043   NVGContext ctx;
1044   int id; // backend image id
1045 
1046 public:
1047   ///
1048   this() (in auto ref NVGImage src) nothrow @trusted @nogc {
1049     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); }
1050     if (src.id > 0 && src.ctx !is null) {
1051       ctx = cast(NVGContext)src.ctx;
1052       id = src.id;
1053       ctx.nvg__imageIncRef(id);
1054     }
1055   }
1056 
1057   ///
1058   ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); }
1059 
1060   ///
1061   this (this) nothrow @trusted @nogc {
1062     version(aliced) pragma(inline, true);
1063     if (id > 0 && ctx !is null) {
1064       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); }
1065       ctx.nvg__imageIncRef(id);
1066     }
1067   }
1068 
1069   ///
1070   void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc {
1071     if (src.id <= 0 || src.ctx is null) {
1072       clear();
1073     } else {
1074       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); }
1075       if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id);
1076       if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id);
1077       ctx = cast(NVGContext)src.ctx;
1078       id = src.id;
1079     }
1080   }
1081 
1082   /// Free this image.
1083   void clear () nothrow @trusted @nogc {
1084     if (id > 0 && ctx !is null) {
1085       version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); }
1086       ctx.nvg__imageDecRef(id);
1087     }
1088     id = 0;
1089     ctx = null;
1090   }
1091 
1092   /// Is this image valid?
1093   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); }
1094 
1095   /// Is the given image valid and comes from the same context?
1096   @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); }
1097 
1098   /// Returns image width, or zero for invalid image.
1099   int width () const nothrow @trusted @nogc {
1100     int w = 0;
1101     if (valid) {
1102       int h = void;
1103       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1104     }
1105     return w;
1106   }
1107 
1108   /// Returns image height, or zero for invalid image.
1109   int height () const nothrow @trusted @nogc {
1110     int h = 0;
1111     if (valid) {
1112       int w = void;
1113       ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h);
1114     }
1115     return h;
1116   }
1117 }
1118 
1119 
1120 /// Paint parameters for various fills. Don't change anything here!
1121 /// Group: render_styles
1122 public struct NVGPaint {
1123   enum isOpaqueStruct = true;
1124 
1125   NVGMatrix xform;
1126   float[2] extent = 0.0f;
1127   float radius = 0.0f;
1128   float feather = 0.0f;
1129   NVGColor innerColor; /// this can be used to modulate images (fill/font)
1130   NVGColor middleColor;
1131   NVGColor outerColor;
1132   float midp = -1; // middle stop for 3-color gradient
1133   NVGImage image;
1134   bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
1135 
1136   this() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1137     xform = p.xform;
1138     extent[] = p.extent[];
1139     radius = p.radius;
1140     feather = p.feather;
1141     innerColor = p.innerColor;
1142     middleColor = p.middleColor;
1143     midp = p.midp;
1144     outerColor = p.outerColor;
1145     image = p.image;
1146     simpleColor = p.simpleColor;
1147   }
1148 
1149   void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc {
1150     xform = p.xform;
1151     extent[] = p.extent[];
1152     radius = p.radius;
1153     feather = p.feather;
1154     innerColor = p.innerColor;
1155     middleColor = p.middleColor;
1156     midp = p.midp;
1157     outerColor = p.outerColor;
1158     image = p.image;
1159     simpleColor = p.simpleColor;
1160   }
1161 
1162   void clear () nothrow @trusted @nogc {
1163     version(aliced) pragma(inline, true);
1164     import core.stdc.string : memset;
1165     image.clear();
1166     memset(&this, 0, this.sizeof);
1167     simpleColor = true;
1168   }
1169 }
1170 
1171 /// Path winding.
1172 /// Group: paths
1173 public enum NVGWinding {
1174   CCW = 1, /// Winding for solid shapes
1175   CW = 2,  /// Winding for holes
1176 }
1177 
1178 /// Path solidity.
1179 /// Group: paths
1180 public enum NVGSolidity {
1181   Solid = 1, /// Solid shape (CCW winding).
1182   Hole = 2, /// Hole (CW winding).
1183 }
1184 
1185 /// Line cap style.
1186 /// Group: render_styles
1187 public enum NVGLineCap {
1188   Butt, ///
1189   Round, ///
1190   Square, ///
1191   Bevel, ///
1192   Miter, ///
1193 }
1194 
1195 /// Text align.
1196 /// Group: text_api
1197 public align(1) struct NVGTextAlign {
1198 align(1):
1199   /// Horizontal align.
1200   enum H : ubyte {
1201     Left   = 0, /// Default, align text horizontally to left.
1202     Center = 1, /// Align text horizontally to center.
1203     Right  = 2, /// Align text horizontally to right.
1204   }
1205 
1206   /// Vertical align.
1207   enum V : ubyte {
1208     Baseline = 0, /// Default, align text vertically to baseline.
1209     Top      = 1, /// Align text vertically to top.
1210     Middle   = 2, /// Align text vertically to middle.
1211     Bottom   = 3, /// Align text vertically to bottom.
1212   }
1213 
1214 pure nothrow @safe @nogc:
1215 public:
1216   this (H h) { pragma(inline, true); value = h; } ///
1217   this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } ///
1218   this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1219   this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1220   void reset () { pragma(inline, true); value = 0; } ///
1221   void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1222   void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } ///
1223 @property:
1224   bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } ///
1225   void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } ///
1226   bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } ///
1227   void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } ///
1228   bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } ///
1229   void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } ///
1230   //
1231   bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } ///
1232   void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } ///
1233   bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } ///
1234   void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } ///
1235   bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } ///
1236   void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } ///
1237   bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } ///
1238   void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } ///
1239   //
1240   H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } ///
1241   void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } ///
1242   //
1243   V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } ///
1244   void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } ///
1245   //
1246 private:
1247   ubyte value = 0; // low nibble: horizontal; high nibble: vertical
1248 }
1249 
1250 /// Blending type.
1251 /// Group: composite_operation
1252 public enum NVGBlendFactor {
1253   Zero = 1<<0, ///
1254   One = 1<<1, ///
1255   SrcColor = 1<<2, ///
1256   OneMinusSrcColor = 1<<3, ///
1257   DstColor = 1<<4, ///
1258   OneMinusDstColor = 1<<5, ///
1259   SrcAlpha = 1<<6, ///
1260   OneMinusSrcAlpha = 1<<7, ///
1261   DstAlpha = 1<<8, ///
1262   OneMinusDstAlpha = 1<<9, ///
1263   SrcAlphaSaturate = 1<<10, ///
1264 }
1265 
1266 /// Composite operation (HTML5-alike).
1267 /// Group: composite_operation
1268 public enum NVGCompositeOperation {
1269   SourceOver, ///
1270   SourceIn, ///
1271   SourceOut, ///
1272   SourceAtop, ///
1273   DestinationOver, ///
1274   DestinationIn, ///
1275   DestinationOut, ///
1276   DestinationAtop, ///
1277   Lighter, ///
1278   Copy, ///
1279   Xor, ///
1280 }
1281 
1282 /// Composite operation state.
1283 /// Group: composite_operation
1284 public struct NVGCompositeOperationState {
1285   bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()`
1286   NVGBlendFactor srcRGB; ///
1287   NVGBlendFactor dstRGB; ///
1288   NVGBlendFactor srcAlpha; ///
1289   NVGBlendFactor dstAlpha; ///
1290 }
1291 
1292 /// Mask combining more
1293 /// Group: clipping
1294 public enum NVGClipMode {
1295   None, /// normal rendering (i.e. render path instead of modifying clip region)
1296   Union, /// old mask will be masked with the current one; this is the default mode for [clip]
1297   Or, /// new mask will be added to the current one (logical `OR` operation);
1298   Xor, /// new mask will be logically `XOR`ed with the current one
1299   Sub, /// "subtract" current path from mask
1300   Replace, /// replace current mask
1301   Add = Or, /// Synonym
1302 }
1303 
1304 /// Glyph position info.
1305 /// Group: text_api
1306 public struct NVGGlyphPosition {
1307   usize strpos;     /// Position of the glyph in the input string.
1308   float x;          /// The x-coordinate of the logical glyph position.
1309   float minx, maxx; /// The bounds of the glyph shape.
1310 }
1311 
1312 /// Text row storage.
1313 /// Group: text_api
1314 public struct NVGTextRow(CT) if (isAnyCharType!CT) {
1315   alias CharType = CT;
1316   const(CT)[] s;
1317   int start;        /// Index in the input text where the row starts.
1318   int end;          /// Index in the input text where the row ends (one past the last character).
1319   float width;      /// Logical width of the row.
1320   float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
1321   /// Get rest of the string.
1322   @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); }
1323   /// Get current row.
1324   @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; }
1325   @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
1326   @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; }
1327 }
1328 
1329 /// Image creation flags.
1330 /// Group: images
1331 public enum NVGImageFlag : uint {
1332   None            =    0, /// Nothing special.
1333   GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image.
1334   RepeatX         = 1<<1, /// Repeat image in X direction.
1335   RepeatY         = 1<<2, /// Repeat image in Y direction.
1336   FlipY           = 1<<3, /// Flips (inverses) image in Y direction when rendered.
1337   Premultiplied   = 1<<4, /// Image data has premultiplied alpha.
1338   ClampToBorderX  = 1<<5, /// Clamp image to border (instead of clamping to edge by default)
1339   ClampToBorderY  = 1<<6, /// Clamp image to border (instead of clamping to edge by default)
1340   NoFiltering     = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active.
1341   Nearest = NoFiltering,  /// compatibility with original NanoVG
1342   NoDelete        = 1<<16,/// Do not delete GL texture handle.
1343 }
1344 
1345 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag].
1346 
1347 
1348 // ////////////////////////////////////////////////////////////////////////// //
1349 private:
1350 
1351 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc {
1352   import core.stdc.stdlib : malloc;
1353   import core.stdc.string : memcpy;
1354   if (count == 0) return null;
1355   T* res = cast(T*)malloc(T.sizeof*count);
1356   if (res is null) assert(0, "NanoVega: out of memory");
1357   memcpy(res, ptr, T.sizeof*count);
1358   return res;
1359 }
1360 
1361 // Internal Render API
1362 enum NVGtexture {
1363   Alpha = 0x01,
1364   RGBA  = 0x02,
1365 }
1366 
1367 struct NVGscissor {
1368   NVGMatrix xform;
1369   float[2] extent = -1.0f;
1370 }
1371 
1372 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates.
1373 public struct NVGVertex {
1374   float x, y, u, v;
1375 }
1376 
1377 struct NVGpath {
1378   int first;
1379   int count;
1380   bool closed;
1381   int nbevel;
1382   NVGVertex* fill;
1383   int nfill;
1384   NVGVertex* stroke;
1385   int nstroke;
1386   NVGWinding mWinding;
1387   bool convex;
1388   bool cloned;
1389 
1390   @disable this (this); // no copies
1391   void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); }
1392 
1393   void clear () nothrow @trusted @nogc {
1394     import core.stdc.stdlib : free;
1395     import core.stdc.string : memset;
1396     if (cloned) {
1397       if (stroke !is null && stroke !is fill) free(stroke);
1398       if (fill !is null) free(fill);
1399     }
1400     memset(&this, 0, this.sizeof);
1401   }
1402 
1403   // won't clear current path
1404   void copyFrom (const NVGpath* src) nothrow @trusted @nogc {
1405     import core.stdc.string : memcpy;
1406     assert(src !is null);
1407     memcpy(&this, src, NVGpath.sizeof);
1408     this.fill = xdup(src.fill, src.nfill);
1409     if (src.stroke is src.fill) {
1410       this.stroke = this.fill;
1411     } else {
1412       this.stroke = xdup(src.stroke, src.nstroke);
1413     }
1414     this.cloned = true;
1415   }
1416 
1417   public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc {
1418     pragma(inline, true);
1419     return (nfill > 0 ? fill[0..nfill] : null);
1420   }
1421 
1422   public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc {
1423     pragma(inline, true);
1424     return (nstroke > 0 ? stroke[0..nstroke] : null);
1425   }
1426 
1427   public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; }
1428   public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; }
1429 }
1430 
1431 
1432 struct NVGparams {
1433   void* userPtr;
1434   bool edgeAntiAlias;
1435   bool fontAA;
1436   bool function (void* uptr) nothrow @trusted @nogc renderCreate;
1437   int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture;
1438   bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef;
1439   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
1440   bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture;
1441   bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize;
1442   void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame]
1443   void function (void* uptr) nothrow @trusted @nogc renderCancel;
1444   void function (void* uptr) nothrow @trusted @nogc renderFlush;
1445   void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1446   void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements
1447   void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped`
1448   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;
1449   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;
1450   void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles;
1451   void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine;
1452   void function (void* uptr) nothrow @trusted @nogc renderDelete;
1453 }
1454 
1455 // ////////////////////////////////////////////////////////////////////////// //
1456 private:
1457 
1458 enum NVG_INIT_FONTIMAGE_SIZE = 512;
1459 enum NVG_MAX_FONTIMAGE_SIZE  = 2048;
1460 enum NVG_MAX_FONTIMAGES      = 4;
1461 
1462 enum NVG_INIT_COMMANDS_SIZE = 256;
1463 enum NVG_INIT_POINTS_SIZE   = 128;
1464 enum NVG_INIT_PATHS_SIZE    = 16;
1465 enum NVG_INIT_VERTS_SIZE    = 256;
1466 enum NVG_MAX_STATES         = 32;
1467 
1468 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs.
1469 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions
1470 
1471 enum Command {
1472   MoveTo = 0,
1473   LineTo = 1,
1474   BezierTo = 2,
1475   Close = 3,
1476   Winding = 4,
1477 }
1478 
1479 enum PointFlag : int {
1480   Corner = 0x01,
1481   Left = 0x02,
1482   Bevel = 0x04,
1483   InnerBevelPR = 0x08,
1484 }
1485 
1486 struct NVGstate {
1487   NVGCompositeOperationState compositeOperation;
1488   bool shapeAntiAlias = true;
1489   NVGPaint fill;
1490   NVGPaint stroke;
1491   float strokeWidth = 1.0f;
1492   float miterLimit = 10.0f;
1493   NVGLineCap lineJoin = NVGLineCap.Miter;
1494   NVGLineCap lineCap = NVGLineCap.Butt;
1495   float alpha = 1.0f;
1496   NVGMatrix xform;
1497   NVGscissor scissor;
1498   float fontSize = 16.0f;
1499   float letterSpacing = 0.0f;
1500   float lineHeight = 1.0f;
1501   float fontBlur = 0.0f;
1502   NVGTextAlign textAlign;
1503   int fontId = 0;
1504   bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill
1505   // dashing
1506   enum MaxDashes = 32; // max 16 dashes
1507   float[MaxDashes] dashes;
1508   uint dashCount = 0;
1509   uint lastFlattenDashCount = 0;
1510   float dashStart = 0;
1511   float totalDashLen;
1512   bool firstDashIsGap = false;
1513   // dasher state for flattener
1514   bool dasherActive = false;
1515 
1516   void clearPaint () nothrow @trusted @nogc {
1517     fill.clear();
1518     stroke.clear();
1519   }
1520 }
1521 
1522 struct NVGpoint {
1523   float x, y;
1524   float dx, dy;
1525   float len;
1526   float dmx, dmy;
1527   ubyte flags;
1528 }
1529 
1530 struct NVGpathCache {
1531   NVGpoint* points;
1532   int npoints;
1533   int cpoints;
1534   NVGpath* paths;
1535   int npaths;
1536   int cpaths;
1537   NVGVertex* verts;
1538   int nverts;
1539   int cverts;
1540   float[4] bounds;
1541   // this is required for saved paths
1542   bool strokeReady;
1543   bool fillReady;
1544   float strokeAlphaMul;
1545   float strokeWidth;
1546   float fringeWidth;
1547   bool evenOddMode;
1548   NVGClipMode clipmode;
1549   // non-saved path will not have this
1550   float* commands;
1551   int ncommands;
1552 
1553   @disable this (this); // no copies
1554   void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); }
1555 
1556   // won't clear current path
1557   void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc {
1558     import core.stdc.stdlib : malloc;
1559     import core.stdc.string : memcpy, memset;
1560     assert(src !is null);
1561     memcpy(&this, src, NVGpathCache.sizeof);
1562     this.points = xdup(src.points, src.npoints);
1563     this.cpoints = src.npoints;
1564     this.verts = xdup(src.verts, src.nverts);
1565     this.cverts = src.nverts;
1566     this.commands = xdup(src.commands, src.ncommands);
1567     if (src.npaths > 0) {
1568       this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof);
1569       memset(this.paths, 0, npaths*NVGpath.sizeof);
1570       foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]);
1571       this.cpaths = src.npaths;
1572     } else {
1573       this.npaths = this.cpaths = 0;
1574     }
1575   }
1576 
1577   void clear () nothrow @trusted @nogc {
1578     import core.stdc.stdlib : free;
1579     import core.stdc.string : memset;
1580     if (paths !is null) {
1581       foreach (ref p; paths[0..npaths]) p.clear();
1582       free(paths);
1583     }
1584     if (points !is null) free(points);
1585     if (verts !is null) free(verts);
1586     if (commands !is null) free(commands);
1587     memset(&this, 0, this.sizeof);
1588   }
1589 }
1590 
1591 /// Pointer to opaque NanoVega context structure.
1592 /// Group: context_management
1593 public alias NVGContext = NVGcontextinternal*;
1594 
1595 /// FontStash context
1596 /// Group: font_stash
1597 public alias FONSContext = FONScontextInternal*;
1598 
1599 /// Returns FontStash context of the given NanoVega context.
1600 /// Group: font_stash
1601 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); }
1602 
1603 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1)
1604 /// Group: font_stash
1605 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc {
1606   pragma(inline, true);
1607   return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1);
1608 }
1609 
1610 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too.
1611 /// Returns `false` if FontStash or NanoVega context is not active.
1612 /// Group: font_stash
1613 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc {
1614   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1615   NVGstate* state = nvg__getState(ctx);
1616   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1617   stash.size = state.fontSize*scale;
1618   stash.spacing = state.letterSpacing*scale;
1619   stash.blur = state.fontBlur*scale;
1620   stash.textAlign = state.textAlign;
1621   stash.fontId = state.fontId;
1622   return true;
1623 }
1624 
1625 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later.
1626 /// Returns `false` if FontStash or NanoVega context is not active.
1627 /// Group: font_stash
1628 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc {
1629   if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false;
1630   NVGstate* state = nvg__getState(ctx);
1631   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
1632   state.fontSize = stash.size;
1633   state.letterSpacing = stash.spacing;
1634   state.fontBlur = stash.blur;
1635   state.textAlign = stash.textAlign;
1636   state.fontId = stash.fontId;
1637   return true;
1638 }
1639 
1640 /** Bezier curve rasterizer.
1641  *
1642  * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
1643  * It doesn't really matter in practice.
1644  *
1645  * AFD tesselator is somewhat slower, but does cusps better.
1646  *
1647  * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
1648  * never notice any visial difference (and this code is not really debugged), so you probably should
1649  * not use it. It is there for further experiments.
1650  */
1651 public enum NVGTesselation {
1652   DeCasteljau, /// default: standard well-known tesselation algorithm
1653   AFD, /// adaptive forward differencing
1654   DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
1655 }
1656 
1657 /// Default tesselator for Bezier curves.
1658 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau;
1659 
1660 
1661 // some public info
1662 
1663 /// valid only inside [beginFrame]/[endFrame]
1664 /// Group: context_management
1665 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); }
1666 
1667 /// valid only inside [beginFrame]/[endFrame]
1668 /// Group: context_management
1669 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); }
1670 
1671 /// valid only inside [beginFrame]/[endFrame]
1672 /// Group: context_management
1673 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); }
1674 
1675 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were.
1676 /// Group: context_management
1677 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); }
1678 
1679 // path autoregistration
1680 
1681 /// [pickid] to stop autoregistration.
1682 /// Group: context_management
1683 public enum NVGNoPick = -1;
1684 
1685 /// >=0: this pickid will be assigned to all filled/stroked paths
1686 /// Group: context_management
1687 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); }
1688 
1689 /// >=0: this pickid will be assigned to all filled/stroked paths
1690 /// Group: context_management
1691 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; }
1692 
1693 /// pick autoregistration mode; see [NVGPickKind]
1694 /// Group: context_management
1695 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); }
1696 
1697 /// pick autoregistration mode; see [NVGPickKind]
1698 /// Group: context_management
1699 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); }
1700 
1701 // tesselator options
1702 
1703 /// Get current Bezier tesselation mode. See [NVGTesselation].
1704 /// Group: context_management
1705 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); }
1706 
1707 /// Set current Bezier tesselation mode. See [NVGTesselation].
1708 /// Group: context_management
1709 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; }
1710 
1711 
1712 private struct NVGcontextinternal {
1713 private:
1714   NVGparams params;
1715   float* commands;
1716   int ccommands;
1717   int ncommands;
1718   float commandx, commandy;
1719   NVGstate[NVG_MAX_STATES] states;
1720   int nstates;
1721   NVGpathCache* cache;
1722   public float tessTol;
1723   public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
1724   public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
1725   float distTol;
1726   public float fringeWidth;
1727   float devicePxRatio;
1728   FONSContext fs;
1729   NVGImage[NVG_MAX_FONTIMAGES] fontImages;
1730   int fontImageIdx;
1731   int drawCallCount;
1732   int fillTriCount;
1733   int strokeTriCount;
1734   int textTriCount;
1735   NVGTesselation tesselatortype;
1736   // picking API
1737   NVGpickScene* pickScene;
1738   int pathPickId; // >=0: register all paths for picking using this id
1739   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
1740   // path recording
1741   NVGPathSet recset;
1742   int recstart; // used to cancel recording
1743   bool recblockdraw;
1744   // internals
1745   NVGMatrix gpuAffine;
1746   int mWidth, mHeight;
1747   // image manager
1748   shared int imageCount; // number of alive images in this context
1749   bool contextAlive; // context can be dead, but still contain some images
1750 
1751   @disable this (this); // no copies
1752   void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); }
1753 
1754   // debug feature
1755   public @property int getImageCount () nothrow @trusted @nogc {
1756     import core.atomic;
1757     return atomicLoad(imageCount);
1758   }
1759 }
1760 
1761 /** Returns number of tesselated pathes in context.
1762  *
1763  * Tesselated pathes are either triangle strips (for strokes), or
1764  * triangle fans (for fills). Note that NanoVega can generate some
1765  * surprising pathes (like fringe stroke for antialiasing, for example).
1766  *
1767  * One render path can contain vertices both for fill, and for stroke triangles.
1768  */
1769 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc {
1770   pragma(inline, true);
1771   return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0);
1772 }
1773 
1774 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice.
1775  *
1776  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1777  *           (except the calls to render path accessors), and using it in such
1778  *           case is UB. So copy vertices to freshly allocated array if you want
1779  *           to keep them for further processing.)
1780  */
1781 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1782   pragma(inline, true);
1783   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null);
1784 }
1785 
1786 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice.
1787  *
1788  * $(WARNING Returned slice can be invalidated by any other NanoVega API call
1789  *           (except the calls to render path accessors), and using it in such
1790  *           case is UB. So copy vertices to freshly allocated array if you want
1791  *           to keep them for further processing.)
1792  */
1793 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1794   pragma(inline, true);
1795   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null);
1796 
1797 }
1798 
1799 /// Returns winding for the given render path.
1800 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1801   pragma(inline, true);
1802   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW);
1803 
1804 }
1805 
1806 /// Returns "complex path" flag for the given render path.
1807 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc {
1808   pragma(inline, true);
1809   return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false);
1810 
1811 }
1812 
1813 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc {
1814   if (ctx !is null && imgid > 0) {
1815     import core.atomic : atomicOp;
1816     atomicOp!"+="(ctx.imageCount, 1);
1817     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1818     if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid);
1819   }
1820 }
1821 
1822 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc {
1823   if (ctx !is null && imgid > 0) {
1824     import core.atomic : atomicOp;
1825     int icnt = atomicOp!"-="(ctx.imageCount, 1);
1826     if (icnt < 0) assert(0, "NanoVega: internal image refcounting error");
1827     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); }
1828     if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid);
1829     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); }
1830     if (!ctx.contextAlive && icnt == 0) {
1831       // it is finally safe to free context memory
1832       import core.stdc.stdlib : free;
1833       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); }
1834       free(ctx);
1835     }
1836   }
1837 }
1838 
1839 
1840 public import core.stdc.math :
1841   nvg__sqrtf = sqrtf,
1842   nvg__modf = fmodf,
1843   nvg__sinf = sinf,
1844   nvg__cosf = cosf,
1845   nvg__tanf = tanf,
1846   nvg__atan2f = atan2f,
1847   nvg__acosf = acosf,
1848   nvg__ceilf = ceilf;
1849 
1850 version(Windows) {
1851   public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); }
1852 } else {
1853   public import core.stdc.math : nvg__lrintf = lrintf;
1854 }
1855 
1856 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); }
1857 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); }
1858 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); }
1859 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); }
1860 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); }
1861 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); }
1862 
1863 //public import core.stdc.math : nvg__absf = fabsf;
1864 public import core.math : nvg__absf = fabs;
1865 
1866 
1867 float nvg__normalize (float* x, float* y) nothrow @safe @nogc {
1868   float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y));
1869   if (d > 1e-6f) {
1870     immutable float id = 1.0f/d;
1871     *x *= id;
1872     *y *= id;
1873   }
1874   return d;
1875 }
1876 
1877 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc {
1878   if (c !is null) {
1879     c.clear();
1880     free(c);
1881   }
1882 }
1883 
1884 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc {
1885   NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
1886   if (c is null) goto error;
1887   memset(c, 0, NVGpathCache.sizeof);
1888 
1889   c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE);
1890   if (c.points is null) goto error;
1891   assert(c.npoints == 0);
1892   c.cpoints = NVG_INIT_POINTS_SIZE;
1893 
1894   c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE);
1895   if (c.paths is null) goto error;
1896   assert(c.npaths == 0);
1897   c.cpaths = NVG_INIT_PATHS_SIZE;
1898 
1899   c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE);
1900   if (c.verts is null) goto error;
1901   assert(c.nverts == 0);
1902   c.cverts = NVG_INIT_VERTS_SIZE;
1903 
1904   return c;
1905 
1906 error:
1907   nvg__deletePathCache(c);
1908   return null;
1909 }
1910 
1911 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc {
1912   ctx.tessTol = 0.25f/ratio;
1913   ctx.distTol = 0.01f/ratio;
1914   ctx.fringeWidth = 1.0f/ratio;
1915   ctx.devicePxRatio = ratio;
1916 }
1917 
1918 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc {
1919   NVGCompositeOperationState state;
1920   NVGBlendFactor sfactor, dfactor;
1921 
1922        if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;}
1923   else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; }
1924   else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; }
1925   else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1926   else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; }
1927   else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; }
1928   else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; }
1929   else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; }
1930   else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; }
1931   else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero;  }
1932   else if (op == NVGCompositeOperation.Xor) {
1933     state.simple = false;
1934     state.srcRGB = NVGBlendFactor.OneMinusDstColor;
1935     state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha;
1936     state.dstRGB = NVGBlendFactor.OneMinusSrcColor;
1937     state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha;
1938     return state;
1939   }
1940   else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver
1941 
1942   state.simple = true;
1943   state.srcAlpha = sfactor;
1944   state.dstAlpha = dfactor;
1945   return state;
1946 }
1947 
1948 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc {
1949   pragma(inline, true);
1950   if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context");
1951   return &ctx.states.ptr[ctx.nstates-1];
1952 }
1953 
1954 // Constructor called by the render back-end.
1955 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
1956   FONSParams fontParams;
1957   NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof);
1958   if (ctx is null) goto error;
1959   memset(ctx, 0, NVGcontextinternal.sizeof);
1960 
1961   ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
1962   ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
1963 
1964   ctx.contextAlive = true;
1965 
1966   ctx.params = *params;
1967   //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0;
1968 
1969   ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE);
1970   if (ctx.commands is null) goto error;
1971   ctx.ncommands = 0;
1972   ctx.ccommands = NVG_INIT_COMMANDS_SIZE;
1973 
1974   ctx.cache = nvg__allocPathCache();
1975   if (ctx.cache is null) goto error;
1976 
1977   ctx.save();
1978   ctx.reset();
1979 
1980   nvg__setDevicePixelRatio(ctx, 1.0f);
1981   ctx.mWidth = ctx.mHeight = 0;
1982 
1983   if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error;
1984 
1985   // init font rendering
1986   memset(&fontParams, 0, fontParams.sizeof);
1987   fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
1988   fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
1989   fontParams.flags = FONSParams.Flag.ZeroTopLeft;
1990   fontParams.renderCreate = null;
1991   fontParams.renderUpdate = null;
1992   fontParams.renderDelete = null;
1993   fontParams.userPtr = null;
1994   ctx.fs = FONSContext.create(fontParams);
1995   if (ctx.fs is null) goto error;
1996 
1997   // create font texture
1998   ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
1999   if (ctx.fontImages[0].id == 0) goto error;
2000   ctx.fontImages[0].ctx = ctx;
2001   ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount
2002   ctx.fontImageIdx = 0;
2003 
2004   ctx.pathPickId = -1;
2005   ctx.tesselatortype = NVG_DEFAULT_TESSELATOR;
2006 
2007   return ctx;
2008 
2009 error:
2010   ctx.deleteInternal();
2011   return null;
2012 }
2013 
2014 // Called by render backend.
2015 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc {
2016   return &ctx.params;
2017 }
2018 
2019 // Destructor called by the render back-end.
2020 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc {
2021   if (ctx is null) return;
2022   if (ctx.contextAlive) {
2023     if (ctx.commands !is null) free(ctx.commands);
2024     nvg__deletePathCache(ctx.cache);
2025 
2026     if (ctx.fs) ctx.fs.kill();
2027 
2028     foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear();
2029 
2030     if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr);
2031 
2032     if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene);
2033 
2034     ctx.contextAlive = false;
2035 
2036     import core.atomic : atomicLoad;
2037     if (atomicLoad(ctx.imageCount) == 0) {
2038       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); }
2039       free(ctx);
2040     } else {
2041       version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); }
2042     }
2043   }
2044 }
2045 
2046 /// Delete NanoVega context.
2047 /// Group: context_management
2048 public void kill (ref NVGContext ctx) nothrow @trusted @nogc {
2049   if (ctx !is null) {
2050     ctx.deleteInternal();
2051     ctx = null;
2052   }
2053 }
2054 
2055 /// Returns `true` if the given context is not `null` and can be used for painting.
2056 /// Group: context_management
2057 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); }
2058 
2059 
2060 // ////////////////////////////////////////////////////////////////////////// //
2061 // Frame Management
2062 
2063 /** Begin drawing a new frame.
2064  *
2065  * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame]
2066  *
2067  * [beginFrame] defines the size of the window to render to in relation currently
2068  * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
2069  * control the rendering on Hi-DPI devices.
2070  *
2071  * For example, GLFW returns two dimension for an opened window: window size and
2072  * frame buffer size. In that case you would set windowWidth/windowHeight to the window size,
2073  * devicePixelRatio to: `windowWidth/windowHeight`.
2074  *
2075  * Default ratio is `1`.
2076  *
2077  * Note that fractional ratio can (and will) distort your fonts and images.
2078  *
2079  * This call also resets pick marks (see picking API for non-rasterized paths),
2080  * path recording, and GPU affine transformatin matrix.
2081  *
2082  * see also [glNVGClearFlags], which returns necessary flags for [glClear].
2083  *
2084  * Group: frame_management
2085  */
2086 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc {
2087   import std.math : isNaN;
2088   /*
2089   printf("Tris: draws:%d  fill:%d  stroke:%d  text:%d  TOT:%d\n",
2090          ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount,
2091          ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount);
2092   */
2093   if (ctx.nstates > 0) ctx.cancelFrame();
2094 
2095   if (windowWidth < 1) windowWidth = 1;
2096   if (windowHeight < 1) windowHeight = 1;
2097 
2098   if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0);
2099 
2100   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2101   ctx.nstates = 0;
2102   ctx.save();
2103   ctx.reset();
2104 
2105   nvg__setDevicePixelRatio(ctx, devicePixelRatio);
2106 
2107   ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight);
2108   ctx.mWidth = windowWidth;
2109   ctx.mHeight = windowHeight;
2110 
2111   ctx.recset = null;
2112   ctx.recstart = -1;
2113 
2114   ctx.pathPickId = NVGNoPick;
2115   ctx.pathPickRegistered = 0;
2116 
2117   ctx.drawCallCount = 0;
2118   ctx.fillTriCount = 0;
2119   ctx.strokeTriCount = 0;
2120   ctx.textTriCount = 0;
2121 
2122   ctx.ncommands = 0;
2123   ctx.pathPickRegistered = 0;
2124   nvg__clearPathCache(ctx);
2125 
2126   ctx.gpuAffine = NVGMatrix.Identity;
2127 
2128   nvg__pickBeginFrame(ctx, windowWidth, windowHeight);
2129 }
2130 
2131 /// Cancels drawing the current frame. Cancels path recording.
2132 /// Group: frame_management
2133 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc {
2134   ctx.cancelRecording();
2135   //ctx.mWidth = 0;
2136   //ctx.mHeight = 0;
2137   // cancel render queue
2138   ctx.params.renderCancel(ctx.params.userPtr);
2139   // clear saved states (this may free some textures)
2140   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2141   ctx.nstates = 0;
2142 }
2143 
2144 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths.
2145 /// Group: frame_management
2146 public void endFrame (NVGContext ctx) nothrow @trusted @nogc {
2147   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2148   ctx.stopRecording();
2149   //ctx.mWidth = 0;
2150   //ctx.mHeight = 0;
2151   // flush render queue
2152   NVGstate* state = nvg__getState(ctx);
2153   ctx.params.renderFlush(ctx.params.userPtr);
2154   if (ctx.fontImageIdx != 0) {
2155     auto fontImage = ctx.fontImages[ctx.fontImageIdx];
2156     int j = 0, iw, ih;
2157     // delete images that smaller than current one
2158     if (!fontImage.valid) return;
2159     ctx.imageSize(fontImage, iw, ih);
2160     foreach (int i; 0..ctx.fontImageIdx) {
2161       if (ctx.fontImages[i].valid) {
2162         int nw, nh;
2163         ctx.imageSize(ctx.fontImages[i], nw, nh);
2164         if (nw < iw || nh < ih) {
2165           ctx.deleteImage(ctx.fontImages[i]);
2166         } else {
2167           ctx.fontImages[j++] = ctx.fontImages[i];
2168         }
2169       }
2170     }
2171     // make current font image to first
2172     ctx.fontImages[j++] = ctx.fontImages[0];
2173     ctx.fontImages[0] = fontImage;
2174     ctx.fontImageIdx = 0;
2175     // clear all images after j
2176     ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init;
2177   }
2178   // clear saved states (this may free some textures)
2179   foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint();
2180   ctx.nstates = 0;
2181 }
2182 
2183 
2184 // ////////////////////////////////////////////////////////////////////////// //
2185 // Recording and Replaying Pathes
2186 
2187 // Saved path set.
2188 // Group: path_recording
2189 public alias NVGPathSet = NVGPathSetS*;
2190 
2191 
2192 //TODO: save scissor info?
2193 struct NVGPathSetS {
2194 private:
2195   // either path cache, or text item
2196   static struct Node {
2197     NVGPaint paint;
2198     NVGpathCache* path;
2199   }
2200 
2201 private:
2202   Node* nodes;
2203   int nnodes, cnodes;
2204   NVGpickScene* pickscene;
2205   //int npickscenes, cpickscenes;
2206   NVGContext svctx; // used to do some sanity checks, and to free resources
2207 
2208 private:
2209   Node* allocNode () nothrow @trusted @nogc {
2210     import core.stdc.string : memset;
2211     // grow buffer if necessary
2212     if (nnodes+1 > cnodes) {
2213       import core.stdc.stdlib : realloc;
2214       int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024);
2215       nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof);
2216       if (nodes is null) assert(0, "NanoVega: out of memory");
2217       //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof);
2218       cnodes = newsz;
2219     }
2220     assert(nnodes < cnodes);
2221     memset(nodes+nnodes, 0, Node.sizeof);
2222     return &nodes[nnodes++];
2223   }
2224 
2225   Node* allocPathNode () nothrow @trusted @nogc {
2226     import core.stdc.stdlib : malloc;
2227     import core.stdc.string : memset;
2228     auto node = allocNode();
2229     // allocate path cache
2230     auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof);
2231     if (pc is null) assert(0, "NanoVega: out of memory");
2232     node.path = pc;
2233     return node;
2234   }
2235 
2236   void clearNode (int idx) nothrow @trusted @nogc {
2237     if (idx < 0 || idx >= nnodes) return;
2238     Node* node = &nodes[idx];
2239     if (svctx !is null && node.paint.image.valid) node.paint.image.clear();
2240     if (node.path !is null) node.path.clear();
2241   }
2242 
2243 private:
2244   void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc {
2245     NVGpickScene* ps = ctx.pickScene;
2246     if (ps is null) return; // nothing to do
2247     if (ps.npaths == 0) return; // pick scene is empty
2248     ctx.pickScene = null;
2249     pickscene = ps;
2250   }
2251 
2252   void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc {
2253     NVGstate* state = nvg__getState(ctx);
2254     foreach (ref node; nodes[0..nnodes]) {
2255       if (auto cc = node.path) {
2256         if (cc.npaths <= 0) continue;
2257 
2258         if (cc.fillReady) {
2259           NVGPaint fillPaint = node.paint;
2260 
2261           // apply global alpha
2262           fillPaint.innerColor.a *= state.alpha;
2263           fillPaint.middleColor.a *= state.alpha;
2264           fillPaint.outerColor.a *= state.alpha;
2265 
2266           fillPaint.innerColor.applyTint(fillTint);
2267           fillPaint.middleColor.applyTint(fillTint);
2268           fillPaint.outerColor.applyTint(fillTint);
2269 
2270           ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
2271 
2272           // count triangles
2273           foreach (int i; 0..cc.npaths) {
2274             NVGpath* path = &cc.paths[i];
2275             ctx.fillTriCount += path.nfill-2;
2276             ctx.fillTriCount += path.nstroke-2;
2277             ctx.drawCallCount += 2;
2278           }
2279         }
2280 
2281         if (cc.strokeReady) {
2282           NVGPaint strokePaint = node.paint;
2283 
2284           strokePaint.innerColor.a *= cc.strokeAlphaMul;
2285           strokePaint.middleColor.a *= cc.strokeAlphaMul;
2286           strokePaint.outerColor.a *= cc.strokeAlphaMul;
2287 
2288           // apply global alpha
2289           strokePaint.innerColor.a *= state.alpha;
2290           strokePaint.middleColor.a *= state.alpha;
2291           strokePaint.outerColor.a *= state.alpha;
2292 
2293           strokePaint.innerColor.applyTint(strokeTint);
2294           strokePaint.middleColor.applyTint(strokeTint);
2295           strokePaint.outerColor.applyTint(strokeTint);
2296 
2297           ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
2298 
2299           // count triangles
2300           foreach (int i; 0..cc.npaths) {
2301             NVGpath* path = &cc.paths[i];
2302             ctx.strokeTriCount += path.nstroke-2;
2303             ++ctx.drawCallCount;
2304           }
2305         }
2306       }
2307     }
2308   }
2309 
2310 public:
2311   @disable this (this); // no copies
2312   void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); }
2313 
2314   // pick test
2315   // Call delegate [dg] for each path under the specified position (in no particular order).
2316   // Returns the id of the path for which delegate [dg] returned true or -1.
2317   // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
2318   int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) {
2319     if (pickscene is null) return -1;
2320 
2321     NVGpickScene* ps = pickscene;
2322     int levelwidth = 1<<(ps.nlevels-1);
2323     int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
2324     int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
2325     int npicked = 0;
2326 
2327     for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
2328       NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx];
2329       while (pp !is null) {
2330         if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) {
2331           int hit = 0;
2332           if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y);
2333           if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y);
2334           if (hit) {
2335             static if (IsGoodHitTestDG!DG) {
2336               static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
2337                 if (dg(pp.id, cast(int)pp.order)) return pp.id;
2338               } else {
2339                 dg(pp.id, cast(int)pp.order);
2340               }
2341             } else {
2342               static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
2343                 if (dg(pp)) return pp.id;
2344               } else {
2345                 dg(pp);
2346               }
2347             }
2348           }
2349         }
2350         pp = pp.next;
2351       }
2352       cellx >>= 1;
2353       celly >>= 1;
2354       levelwidth >>= 1;
2355     }
2356 
2357     return -1;
2358   }
2359 
2360   // Fills ids with a list of the top most hit ids under the specified position.
2361   // Returns the slice of [ids].
2362   int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
2363     if (pickscene is null || ids.length == 0) return ids[0..0];
2364 
2365     int npicked = 0;
2366     NVGpickScene* ps = pickscene;
2367 
2368     hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2369       if (npicked == ps.cpicked) {
2370         int cpicked = ps.cpicked+ps.cpicked;
2371         NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
2372         if (picked is null) return true; // abort
2373         ps.cpicked = cpicked;
2374         ps.picked = picked;
2375       }
2376       ps.picked[npicked] = pp;
2377       ++npicked;
2378       return false; // go on
2379     });
2380 
2381     qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
2382 
2383     assert(npicked >= 0);
2384     if (npicked > ids.length) npicked = cast(int)ids.length;
2385     foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
2386 
2387     return ids[0..npicked];
2388   }
2389 
2390   // Returns the id of the pickable shape containing x,y or -1 if no shape was found.
2391   int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc {
2392     if (pickscene is null) return -1;
2393 
2394     int bestOrder = -1;
2395     int bestID = -1;
2396 
2397     hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
2398       if (pp.order > bestOrder) {
2399         bestOrder = pp.order;
2400         bestID = pp.id;
2401       }
2402     });
2403 
2404     return bestID;
2405   }
2406 }
2407 
2408 // Append current path to existing path set. Is is safe to call this with `null` [svp].
2409 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc {
2410   if (ctx is null || svp is null) return;
2411   if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts");
2412   if (ctx.ncommands == 0) {
2413     assert(ctx.cache.npaths == 0);
2414     return;
2415   }
2416   if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return;
2417 
2418   // tesselate current path
2419   //if (!ctx.cache.fillReady) nvg__prepareFill(ctx);
2420   //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx);
2421 
2422   auto node = svp.allocPathNode();
2423   NVGpathCache* cc = node.path;
2424   cc.copyFrom(ctx.cache);
2425   node.paint = paint;
2426   // copy path commands (we may need 'em for picking)
2427   version(all) {
2428     cc.ncommands = ctx.ncommands;
2429     if (cc.ncommands) {
2430       import core.stdc.stdlib : malloc;
2431       import core.stdc.string : memcpy;
2432       cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof);
2433       if (cc.commands is null) assert(0, "NanoVega: out of memory");
2434       memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof);
2435     } else {
2436       cc.commands = null;
2437     }
2438   }
2439 }
2440 
2441 // Create new empty path set.
2442 // Group: path_recording
2443 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc {
2444   import core.stdc.stdlib : malloc;
2445   import core.stdc.string : memset;
2446   if (ctx is null) return null;
2447   NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof);
2448   if (res is null) assert(0, "NanoVega: out of memory");
2449   memset(res, 0, NVGPathSetS.sizeof);
2450   res.svctx = ctx;
2451   return res;
2452 }
2453 
2454 // Is the given path set empty? Empty path set can be `null`.
2455 // Group: path_recording
2456 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); }
2457 
2458 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused).
2459 // Group: path_recording
2460 public void clear (NVGPathSet svp) nothrow @trusted @nogc {
2461   if (svp !is null) {
2462     import core.stdc.stdlib : free;
2463     foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx);
2464     svp.nnodes = 0;
2465   }
2466 }
2467 
2468 // Destroy path set (frees all allocated memory).
2469 // Group: path_recording
2470 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc {
2471   if (svp !is null) {
2472     import core.stdc.stdlib : free;
2473     svp.clear();
2474     if (svp.nodes !is null) free(svp.nodes);
2475     free(svp);
2476     if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene);
2477     svp = null;
2478   }
2479 }
2480 
2481 // Start path recording. [svp] should be alive until recording is cancelled or stopped.
2482 // Group: path_recording
2483 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2484   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2485   ctx.stopRecording();
2486   ctx.recset = svp;
2487   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2488   ctx.recblockdraw = false;
2489 }
2490 
2491 /* Start path recording. [svp] should be alive until recording is cancelled or stopped.
2492  *
2493  * This will block all rendering, so you can call your rendering functions to record paths without actual drawing.
2494  * Commiting or cancelling will re-enable rendering.
2495  * You can call this with `null` svp to block rendering without recording any paths.
2496  *
2497  * Group: path_recording
2498  */
2499 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc {
2500   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2501   ctx.stopRecording();
2502   ctx.recset = svp;
2503   ctx.recstart = (svp !is null ? svp.nnodes : -1);
2504   ctx.recblockdraw = true;
2505 }
2506 
2507 // Commit recorded paths. It is safe to call this when recording is not started.
2508 // Group: path_recording
2509 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc {
2510   if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2511   if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx);
2512   ctx.recset = null;
2513   ctx.recstart = -1;
2514   ctx.recblockdraw = false;
2515 }
2516 
2517 // Cancel path recording.
2518 // Group: path_recording
2519 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc {
2520   if (ctx.recset !is null) {
2521     if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2522     assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes);
2523     foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx);
2524     ctx.recset.nnodes = ctx.recstart;
2525     ctx.recset = null;
2526     ctx.recstart = -1;
2527   }
2528   ctx.recblockdraw = false;
2529 }
2530 
2531 /* Replay saved path set.
2532  *
2533  * Replaying record while you're recording another one is undefined behavior.
2534  *
2535  * Group: path_recording
2536  */
2537 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc {
2538   if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts");
2539   svp.replay(ctx, fillTint, strokeTint);
2540 }
2541 
2542 /// Ditto.
2543 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); }
2544 
2545 /// Ditto.
2546 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); }
2547 
2548 
2549 // ////////////////////////////////////////////////////////////////////////// //
2550 // Composite operation
2551 
2552 /// Sets the composite operation.
2553 /// Group: composite_operation
2554 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc {
2555   NVGstate* state = nvg__getState(ctx);
2556   state.compositeOperation = nvg__compositeOperationState(op);
2557 }
2558 
2559 /// Sets the composite operation with custom pixel arithmetic.
2560 /// Group: composite_operation
2561 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc {
2562   ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor);
2563 }
2564 
2565 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
2566 /// Group: composite_operation
2567 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc {
2568   NVGCompositeOperationState op;
2569   op.simple = false;
2570   op.srcRGB = srcRGB;
2571   op.dstRGB = dstRGB;
2572   op.srcAlpha = srcAlpha;
2573   op.dstAlpha = dstAlpha;
2574   NVGstate* state = nvg__getState(ctx);
2575   state.compositeOperation = op;
2576 }
2577 
2578 
2579 // ////////////////////////////////////////////////////////////////////////// //
2580 // Color utils
2581 
2582 /// Returns a color value from string form.
2583 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb"
2584 /// Group: color_utils
2585 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2586 
2587 /// Ditto.
2588 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); }
2589 
2590 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
2591 /// Group: color_utils
2592 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); }
2593 
2594 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
2595 /// Group: color_utils
2596 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); }
2597 
2598 /// Returns a color value from red, green, blue and alpha values.
2599 /// Group: color_utils
2600 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)); }
2601 
2602 /// Returns a color value from red, green, blue and alpha values.
2603 /// Group: color_utils
2604 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); }
2605 
2606 /// Returns new color with transparency (alpha) set to [a].
2607 /// Group: color_utils
2608 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc {
2609   pragma(inline, true);
2610   c.a = a/255.0f;
2611   return c;
2612 }
2613 
2614 /// Ditto.
2615 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc {
2616   pragma(inline, true);
2617   c.a = a;
2618   return c;
2619 }
2620 
2621 /// Linearly interpolates from color c0 to c1, and returns resulting color value.
2622 /// Group: color_utils
2623 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc {
2624   NVGColor cint = void;
2625   u = nvg__clamp(u, 0.0f, 1.0f);
2626   float oneminu = 1.0f-u;
2627   foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u;
2628   return cint;
2629 }
2630 
2631 /* see below
2632 public NVGColor nvgHSL() (float h, float s, float l) {
2633   //pragma(inline, true); // alas
2634   return nvgHSLA(h, s, l, 255);
2635 }
2636 */
2637 
2638 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc {
2639   if (h < 0) h += 1;
2640   if (h > 1) h -= 1;
2641   if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f;
2642   if (h < 3.0f/6.0f) return m2;
2643   if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f;
2644   return m1;
2645 }
2646 
2647 /// Returns color value specified by hue, saturation and lightness.
2648 /// HSL values are all in range [0..1], alpha will be set to 255.
2649 /// Group: color_utils
2650 public alias nvgHSL = nvgHSLA; // trick to allow inlining
2651 
2652 /// Returns color value specified by hue, saturation and lightness and alpha.
2653 /// HSL values are all in range [0..1], alpha in range [0..255].
2654 /// Group: color_utils
2655 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc {
2656   pragma(inline, true);
2657   NVGColor col = void;
2658   h = nvg__modf(h, 1.0f);
2659   if (h < 0.0f) h += 1.0f;
2660   s = nvg__clamp(s, 0.0f, 1.0f);
2661   l = nvg__clamp(l, 0.0f, 1.0f);
2662   immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2663   immutable float m1 = 2*l-m2;
2664   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2665   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2666   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2667   col.a = a/255.0f;
2668   return col;
2669 }
2670 
2671 /// Returns color value specified by hue, saturation and lightness and alpha.
2672 /// HSL values and alpha are all in range [0..1].
2673 /// Group: color_utils
2674 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc {
2675   // sorry for copypasta, it is for inliner
2676   static if (__VERSION__ >= 2072) pragma(inline, true);
2677   NVGColor col = void;
2678   h = nvg__modf(h, 1.0f);
2679   if (h < 0.0f) h += 1.0f;
2680   s = nvg__clamp(s, 0.0f, 1.0f);
2681   l = nvg__clamp(l, 0.0f, 1.0f);
2682   immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s);
2683   immutable m1 = 2*l-m2;
2684   col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2685   col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f);
2686   col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f);
2687   col.a = a;
2688   return col;
2689 }
2690 
2691 
2692 // ////////////////////////////////////////////////////////////////////////// //
2693 // Matrices and Transformations
2694 
2695 /** Matrix class.
2696  *
2697  * Group: matrices
2698  */
2699 public align(1) struct NVGMatrix {
2700 align(1):
2701 private:
2702   static immutable float[6] IdentityMat = [
2703     1.0f, 0.0f,
2704     0.0f, 1.0f,
2705     0.0f, 0.0f,
2706   ];
2707 
2708 public:
2709   /// Matrix values. Initial value is identity matrix.
2710   float[6] mat = [
2711     1.0f, 0.0f,
2712     0.0f, 1.0f,
2713     0.0f, 0.0f,
2714   ];
2715 
2716 public nothrow @trusted @nogc:
2717   /// Create Matrix with the given values.
2718   this (const(float)[] amat...) {
2719     pragma(inline, true);
2720     if (amat.length >= 6) {
2721       mat.ptr[0..6] = amat.ptr[0..6];
2722     } else {
2723       mat.ptr[0..6] = 0;
2724       mat.ptr[0..amat.length] = amat[];
2725     }
2726   }
2727 
2728   /// Can be used to check validity of [inverted] result
2729   @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); }
2730 
2731   /// Returns `true` if this matrix is identity matrix.
2732   @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); }
2733 
2734   /// Returns new inverse matrix.
2735   /// If inverted matrix cannot be calculated, `res.valid` fill be `false`.
2736   NVGMatrix inverted () const {
2737     NVGMatrix res = this;
2738     res.invert;
2739     return res;
2740   }
2741 
2742   /// Inverts this matrix.
2743   /// If inverted matrix cannot be calculated, `this.valid` fill be `false`.
2744   ref NVGMatrix invert () return {
2745     float[6] inv = void;
2746     immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1];
2747     if (det > -1e-6 && det < 1e-6) {
2748       inv[] = float.nan;
2749     } else {
2750       immutable double invdet = 1.0/det;
2751       inv.ptr[0] = cast(float)(mat.ptr[3]*invdet);
2752       inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet);
2753       inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet);
2754       inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet);
2755       inv.ptr[3] = cast(float)(mat.ptr[0]*invdet);
2756       inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet);
2757     }
2758     mat.ptr[0..6] = inv.ptr[0..6];
2759     return this;
2760   }
2761 
2762   /// Sets this matrix to identity matrix.
2763   ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; }
2764 
2765   /// Translate this matrix.
2766   ref NVGMatrix translate (in float tx, in float ty) return {
2767     version(aliced) pragma(inline, true);
2768     return this.mul(Translated(tx, ty));
2769   }
2770 
2771   /// Scale this matrix.
2772   ref NVGMatrix scale (in float sx, in float sy) return {
2773     version(aliced) pragma(inline, true);
2774     return this.mul(Scaled(sx, sy));
2775   }
2776 
2777   /// Rotate this matrix.
2778   ref NVGMatrix rotate (in float a) return {
2779     version(aliced) pragma(inline, true);
2780     return this.mul(Rotated(a));
2781   }
2782 
2783   /// Skew this matrix by X axis.
2784   ref NVGMatrix skewX (in float a) return {
2785     version(aliced) pragma(inline, true);
2786     return this.mul(SkewedX(a));
2787   }
2788 
2789   /// Skew this matrix by Y axis.
2790   ref NVGMatrix skewY (in float a) return {
2791     version(aliced) pragma(inline, true);
2792     return this.mul(SkewedY(a));
2793   }
2794 
2795   /// Skew this matrix by both axes.
2796   ref NVGMatrix skewY (in float ax, in float ay) return {
2797     version(aliced) pragma(inline, true);
2798     return this.mul(SkewedXY(ax, ay));
2799   }
2800 
2801   /// Transform point with this matrix. `null` destinations are allowed.
2802   /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables.
2803   void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc {
2804     version(aliced) pragma(inline, true);
2805     if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4];
2806     if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5];
2807   }
2808 
2809   /// Transform point with this matrix.
2810   void point (ref float x, ref float y) nothrow @trusted @nogc {
2811     version(aliced) pragma(inline, true);
2812     immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4];
2813     immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5];
2814     x = nx;
2815     y = ny;
2816   }
2817 
2818   /// Sets this matrix to the result of multiplication of `this` and [s] (this * S).
2819   ref NVGMatrix mul() (in auto ref NVGMatrix s) {
2820     immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2];
2821     immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2];
2822     immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4];
2823     mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3];
2824     mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3];
2825     mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5];
2826     mat.ptr[0] = t0;
2827     mat.ptr[2] = t2;
2828     mat.ptr[4] = t4;
2829     return this;
2830   }
2831 
2832   /// Sets this matrix to the result of multiplication of [s] and `this` (S * this).
2833   /// Sets the transform to the result of multiplication of two transforms, of A = B*A.
2834   /// Group: matrices
2835   ref NVGMatrix premul() (in auto ref NVGMatrix s) {
2836     NVGMatrix s2 = s;
2837     s2.mul(this);
2838     mat[] = s2.mat[];
2839     return this;
2840   }
2841 
2842   /// Multiply this matrix by [s], return result as new matrix.
2843   /// Performs operations in this left-to-right order.
2844   NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const {
2845     version(aliced) pragma(inline, true);
2846     NVGMatrix res = this;
2847     res.mul(s);
2848     return res;
2849   }
2850 
2851   /// Multiply this matrix by [s].
2852   /// Performs operations in this left-to-right order.
2853   ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) {
2854     version(aliced) pragma(inline, true);
2855     return this.mul(s);
2856   }
2857 
2858   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.
2859   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.
2860   float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix.
2861   float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix.
2862   float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix.
2863 
2864   ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix.
2865   ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix.
2866   ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix.
2867   ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix.
2868   ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix.
2869 
2870   /// Utility function to be used in `setXXX()`.
2871   /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2872   ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return {
2873     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2874     mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn;
2875     mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs;
2876     mat.ptr[4] = tx; mat.ptr[5] = ty;
2877     return this;
2878   }
2879 
2880   /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster
2881   ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return {
2882     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2883     mat.ptr[0] = cs; mat.ptr[1] = sn;
2884     mat.ptr[2] = -sn; mat.ptr[3] = cs;
2885     mat.ptr[4] = tx; mat.ptr[5] = ty;
2886     return this;
2887   }
2888 
2889   /// Returns new identity matrix.
2890   static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; }
2891 
2892   /// Returns new translation matrix.
2893   static NVGMatrix Translated (in float tx, in float ty) {
2894     version(aliced) pragma(inline, true);
2895     NVGMatrix res = void;
2896     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2897     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2898     res.mat.ptr[4] = tx; res.mat.ptr[5] = ty;
2899     return res;
2900   }
2901 
2902   /// Returns new scaling matrix.
2903   static NVGMatrix Scaled (in float sx, in float sy) {
2904     version(aliced) pragma(inline, true);
2905     NVGMatrix res = void;
2906     res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f;
2907     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy;
2908     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2909     return res;
2910   }
2911 
2912   /// Returns new rotation matrix. Angle is specified in radians.
2913   static NVGMatrix Rotated (in float a) {
2914     version(aliced) pragma(inline, true);
2915     immutable float cs = nvg__cosf(a), sn = nvg__sinf(a);
2916     NVGMatrix res = void;
2917     res.mat.ptr[0] = cs; res.mat.ptr[1] = sn;
2918     res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs;
2919     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2920     return res;
2921   }
2922 
2923   /// Returns new x-skewing matrix. Angle is specified in radians.
2924   static NVGMatrix SkewedX (in float a) {
2925     version(aliced) pragma(inline, true);
2926     NVGMatrix res = void;
2927     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f;
2928     res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f;
2929     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2930     return res;
2931   }
2932 
2933   /// Returns new y-skewing matrix. Angle is specified in radians.
2934   static NVGMatrix SkewedY (in float a) {
2935     version(aliced) pragma(inline, true);
2936     NVGMatrix res = void;
2937     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a);
2938     res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f;
2939     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2940     return res;
2941   }
2942 
2943   /// Returns new xy-skewing matrix. Angles are specified in radians.
2944   static NVGMatrix SkewedXY (in float ax, in float ay) {
2945     version(aliced) pragma(inline, true);
2946     NVGMatrix res = void;
2947     res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay);
2948     res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f;
2949     res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f;
2950     return res;
2951   }
2952 
2953   /// Utility function to be used in `setXXX()`.
2954   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster
2955   static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) {
2956     NVGMatrix res = void;
2957     res.scaleRotateTransform(xscale, yscale, a, tx, ty);
2958     return res;
2959   }
2960 
2961   /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster
2962   static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) {
2963     NVGMatrix res = void;
2964     res.rotateTransform(a, tx, ty);
2965     return res;
2966   }
2967 }
2968 
2969 
2970 /// Converts degrees to radians.
2971 /// Group: matrices
2972 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; }
2973 
2974 /// Converts radians to degrees.
2975 /// Group: matrices
2976 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; }
2977 
2978 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees`
2979 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians`
2980 
2981 
2982 // ////////////////////////////////////////////////////////////////////////// //
2983 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc {
2984   p.clear();
2985   p.xform.identity;
2986   p.radius = 0.0f;
2987   p.feather = 1.0f;
2988   p.innerColor = p.middleColor = p.outerColor = color;
2989   p.midp = -1;
2990   p.simpleColor = true;
2991 }
2992 
2993 
2994 // ////////////////////////////////////////////////////////////////////////// //
2995 // State handling
2996 
2997 version(nanovega_debug_clipping) {
2998   public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); }
2999   public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); }
3000 }
3001 
3002 /** Pushes and saves the current render state into a state stack.
3003  * A matching [restore] must be used to restore the state.
3004  * Returns `false` if state stack overflowed.
3005  *
3006  * Group: state_handling
3007  */
3008 public bool save (NVGContext ctx) nothrow @trusted @nogc {
3009   if (ctx.nstates >= NVG_MAX_STATES) return false;
3010   if (ctx.nstates > 0) {
3011     //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof);
3012     ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1];
3013     ctx.params.renderPushClip(ctx.params.userPtr);
3014   }
3015   ++ctx.nstates;
3016   return true;
3017 }
3018 
3019 /// Pops and restores current render state.
3020 /// Group: state_handling
3021 public bool restore (NVGContext ctx) nothrow @trusted @nogc {
3022   if (ctx.nstates <= 1) return false;
3023   ctx.states[ctx.nstates-1].clearPaint();
3024   ctx.params.renderPopClip(ctx.params.userPtr);
3025   --ctx.nstates;
3026   return true;
3027 }
3028 
3029 /// Resets current render state to default values. Does not affect the render state stack.
3030 /// Group: state_handling
3031 public void reset (NVGContext ctx) nothrow @trusted @nogc {
3032   NVGstate* state = nvg__getState(ctx);
3033   state.clearPaint();
3034 
3035   nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255));
3036   nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255));
3037   state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver);
3038   state.shapeAntiAlias = true;
3039   state.strokeWidth = 1.0f;
3040   state.miterLimit = 10.0f;
3041   state.lineCap = NVGLineCap.Butt;
3042   state.lineJoin = NVGLineCap.Miter;
3043   state.alpha = 1.0f;
3044   state.xform.identity;
3045 
3046   state.scissor.extent[] = -1.0f;
3047 
3048   state.fontSize = 16.0f;
3049   state.letterSpacing = 0.0f;
3050   state.lineHeight = 1.0f;
3051   state.fontBlur = 0.0f;
3052   state.textAlign.reset;
3053   state.fontId = 0;
3054   state.evenOddMode = false;
3055   state.dashCount = 0;
3056   state.lastFlattenDashCount = 0;
3057   state.dashStart = 0;
3058   state.firstDashIsGap = false;
3059   state.dasherActive = false;
3060 
3061   ctx.params.renderResetClip(ctx.params.userPtr);
3062 }
3063 
3064 /** Returns `true` if we have any room in state stack.
3065  * It is guaranteed to have at least 32 stack slots.
3066  *
3067  * Group: state_handling
3068  */
3069 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); }
3070 
3071 /** Returns `true` if we have any saved state.
3072  *
3073  * Group: state_handling
3074  */
3075 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); }
3076 
3077 /// Returns `true` if rendering is currently blocked.
3078 /// Group: state_handling
3079 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); }
3080 
3081 /// Blocks/unblocks rendering
3082 /// Group: state_handling
3083 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; }
3084 
3085 /// Blocks/unblocks rendering; returns previous state.
3086 /// Group: state_handling
3087 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; }
3088 
3089 
3090 // ////////////////////////////////////////////////////////////////////////// //
3091 // Render styles
3092 
3093 /// Sets filling mode to "even-odd".
3094 /// Group: render_styles
3095 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc {
3096   NVGstate* state = nvg__getState(ctx);
3097   state.evenOddMode = true;
3098 }
3099 
3100 /// Sets filling mode to "non-zero" (this is default mode).
3101 /// Group: render_styles
3102 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc {
3103   NVGstate* state = nvg__getState(ctx);
3104   state.evenOddMode = false;
3105 }
3106 
3107 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default.
3108 /// Group: render_styles
3109 public void shapeAntiAlias (NVGContext ctx, bool enabled) {
3110   NVGstate* state = nvg__getState(ctx);
3111   state.shapeAntiAlias = enabled;
3112 }
3113 
3114 /// Sets the stroke width of the stroke style.
3115 /// Group: render_styles
3116 @scriptable
3117 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc {
3118   NVGstate* state = nvg__getState(ctx);
3119   state.strokeWidth = width;
3120 }
3121 
3122 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled.
3123 /// Group: render_styles
3124 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc {
3125   NVGstate* state = nvg__getState(ctx);
3126   state.miterLimit = limit;
3127 }
3128 
3129 /// Sets how the end of the line (cap) is drawn,
3130 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square.
3131 /// Group: render_styles
3132 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc {
3133   NVGstate* state = nvg__getState(ctx);
3134   state.lineCap = cap;
3135 }
3136 
3137 /// Sets how sharp path corners are drawn.
3138 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel.
3139 /// Group: render_styles
3140 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc {
3141   NVGstate* state = nvg__getState(ctx);
3142   state.lineJoin = join;
3143 }
3144 
3145 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3146 /// Current limit is 16 pairs.
3147 /// Resets dash start to zero.
3148 /// Group: render_styles
3149 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc {
3150   NVGstate* state = nvg__getState(ctx);
3151   state.dashCount = 0;
3152   state.dashStart = 0;
3153   state.firstDashIsGap = false;
3154   if (dashdata.length >= 2) {
3155     bool curFIsGap = true; // trick
3156     foreach (immutable idx, float f; dashdata) {
3157       curFIsGap = !curFIsGap;
3158       if (f < 0.01f) continue; // skip it
3159       if (idx == 0) {
3160         // register first dash
3161         state.firstDashIsGap = curFIsGap;
3162         state.dashes.ptr[state.dashCount++] = f;
3163       } else {
3164         if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) {
3165           // oops, continuation
3166           state.dashes[state.dashCount-1] += f;
3167         } else {
3168           if (state.dashCount == state.dashes.length) break;
3169           state.dashes[state.dashCount++] = f;
3170         }
3171       }
3172     }
3173     if (state.dashCount&1) {
3174       if (state.dashCount == 1) {
3175         state.dashCount = 0;
3176       } else {
3177         assert(state.dashCount < state.dashes.length);
3178         state.dashes[state.dashCount++] = 0;
3179       }
3180     }
3181     // calculate total dash path length
3182     state.totalDashLen = 0;
3183     foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f;
3184     if (state.totalDashLen < 0.01f) {
3185       state.dashCount = 0; // nothing to do
3186     } else {
3187       if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening
3188     }
3189   }
3190 }
3191 
3192 public alias lineDash = setLineDash; /// Ditto.
3193 
3194 /// Sets stroke dashing, using (dash_length, gap_length) pairs.
3195 /// Current limit is 16 pairs.
3196 /// Group: render_styles
3197 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc {
3198   NVGstate* state = nvg__getState(ctx);
3199   if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) {
3200     state.lastFlattenDashCount = uint.max; // force re-flattening
3201   }
3202   state.dashStart = dashStart;
3203 }
3204 
3205 public alias lineDashStart = setLineDashStart; /// Ditto.
3206 
3207 /// Sets the transparency applied to all rendered shapes.
3208 /// Already transparent paths will get proportionally more transparent as well.
3209 /// Group: render_styles
3210 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc {
3211   NVGstate* state = nvg__getState(ctx);
3212   state.alpha = alpha;
3213 }
3214 
3215 private void strokeColor() {}
3216 
3217 static if (NanoVegaHasArsdColor) {
3218 /// Sets current stroke style to a solid color.
3219 /// Group: render_styles
3220 @scriptable
3221 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3222   NVGstate* state = nvg__getState(ctx);
3223   nvg__setPaintColor(state.stroke, NVGColor(color));
3224 }
3225 }
3226 
3227 /// Sets current stroke style to a solid color.
3228 /// Group: render_styles
3229 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3230   NVGstate* state = nvg__getState(ctx);
3231   nvg__setPaintColor(state.stroke, color);
3232 }
3233 
3234 @scriptable
3235 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3236 	strokePaint(ctx, *paint);
3237 }
3238 
3239 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
3240 /// Group: render_styles
3241 @scriptable
3242 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3243   NVGstate* state = nvg__getState(ctx);
3244   state.stroke = paint;
3245   //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
3246   state.stroke.xform.mul(state.xform);
3247 }
3248 
3249 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206
3250 // for scriptable reflection. it just needs to be declared first among the overloads
3251 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { }
3252 
3253 static if (NanoVegaHasArsdColor) {
3254 /// Sets current fill style to a solid color.
3255 /// Group: render_styles
3256 @scriptable
3257 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
3258   NVGstate* state = nvg__getState(ctx);
3259   nvg__setPaintColor(state.fill, NVGColor(color));
3260 }
3261 }
3262 
3263 /// Sets current fill style to a solid color.
3264 /// Group: render_styles
3265 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
3266   NVGstate* state = nvg__getState(ctx);
3267   nvg__setPaintColor(state.fill, color);
3268 
3269 }
3270 
3271 @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)
3272 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc {
3273 	fillPaint(ctx, *paint);
3274 }
3275 
3276 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
3277 /// Group: render_styles
3278 @scriptable
3279 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
3280   NVGstate* state = nvg__getState(ctx);
3281   state.fill = paint;
3282   //nvgTransformMultiply(state.fill.xform[], state.xform[]);
3283   state.fill.xform.mul(state.xform);
3284 }
3285 
3286 /// Sets current fill style to a multistop linear gradient.
3287 /// Group: render_styles
3288 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3289   if (!lgs.valid) {
3290     NVGPaint p = void;
3291     memset(&p, 0, p.sizeof);
3292     nvg__setPaintColor(p, NVGColor.red);
3293     ctx.fillPaint = p;
3294   } else if (lgs.midp >= -1) {
3295     //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
3296     ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3297   } else {
3298     ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3299   }
3300 }
3301 
3302 /// Returns current transformation matrix.
3303 /// Group: render_transformations
3304 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
3305   NVGstate* state = nvg__getState(ctx);
3306   return state.xform;
3307 }
3308 
3309 /// Sets current transformation matrix.
3310 /// Group: render_transformations
3311 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc {
3312   NVGstate* state = nvg__getState(ctx);
3313   state.xform = m;
3314 }
3315 
3316 /// Resets current transform to an identity matrix.
3317 /// Group: render_transformations
3318 @scriptable
3319 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc {
3320   NVGstate* state = nvg__getState(ctx);
3321   state.xform.identity;
3322 }
3323 
3324 /// Premultiplies current coordinate system by specified matrix.
3325 /// Group: render_transformations
3326 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc {
3327   NVGstate* state = nvg__getState(ctx);
3328   //nvgTransformPremultiply(state.xform[], t[]);
3329   state.xform *= mt;
3330 }
3331 
3332 /// Translates current coordinate system.
3333 /// Group: render_transformations
3334 @scriptable
3335 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3336   NVGstate* state = nvg__getState(ctx);
3337   //NVGMatrix t = void;
3338   //nvgTransformTranslate(t[], x, y);
3339   //nvgTransformPremultiply(state.xform[], t[]);
3340   state.xform.premul(NVGMatrix.Translated(x, y));
3341 }
3342 
3343 /// Rotates current coordinate system. Angle is specified in radians.
3344 /// Group: render_transformations
3345 @scriptable
3346 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3347   NVGstate* state = nvg__getState(ctx);
3348   //NVGMatrix t = void;
3349   //nvgTransformRotate(t[], angle);
3350   //nvgTransformPremultiply(state.xform[], t[]);
3351   state.xform.premul(NVGMatrix.Rotated(angle));
3352 }
3353 
3354 /// Skews the current coordinate system along X axis. Angle is specified in radians.
3355 /// Group: render_transformations
3356 @scriptable
3357 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3358   NVGstate* state = nvg__getState(ctx);
3359   //NVGMatrix t = void;
3360   //nvgTransformSkewX(t[], angle);
3361   //nvgTransformPremultiply(state.xform[], t[]);
3362   state.xform.premul(NVGMatrix.SkewedX(angle));
3363 }
3364 
3365 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
3366 /// Group: render_transformations
3367 @scriptable
3368 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc {
3369   NVGstate* state = nvg__getState(ctx);
3370   //NVGMatrix t = void;
3371   //nvgTransformSkewY(t[], angle);
3372   //nvgTransformPremultiply(state.xform[], t[]);
3373   state.xform.premul(NVGMatrix.SkewedY(angle));
3374 }
3375 
3376 /// Scales the current coordinate system.
3377 /// Group: render_transformations
3378 @scriptable
3379 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
3380   NVGstate* state = nvg__getState(ctx);
3381   //NVGMatrix t = void;
3382   //nvgTransformScale(t[], x, y);
3383   //nvgTransformPremultiply(state.xform[], t[]);
3384   state.xform.premul(NVGMatrix.Scaled(x, y));
3385 }
3386 
3387 
3388 // ////////////////////////////////////////////////////////////////////////// //
3389 // Images
3390 
3391 /// Creates image by loading it from the disk from specified file name.
3392 /// Returns handle to the image or 0 on error.
3393 /// Group: images
3394 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) {
3395   static if (NanoVegaHasArsdImage) {
3396     import arsd.image;
3397     // do we have new arsd API to load images?
3398     static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) {
3399       static assert(0, "Sorry, your ARSD is too old. Please, update it.");
3400     }
3401     try {
3402       auto oimg = MemoryImage.fromImageFile(filename);
3403       if (auto img = cast(TrueColorImage)oimg) {
3404         scope(exit) oimg.clearInternal();
3405         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3406       } else {
3407         TrueColorImage img = oimg.getAsTrueColorImage;
3408         scope(exit) img.clearInternal();
3409         oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here
3410         oimg = null;
3411         return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList);
3412       }
3413     } catch (Exception) {}
3414     return NVGImage.init;
3415   } else {
3416     import std.internal.cstring;
3417     ubyte* img;
3418     int w, h, n;
3419     stbi_set_unpremultiply_on_load(1);
3420     stbi_convert_iphone_png_to_rgb(1);
3421     img = stbi_load(filename.tempCString, &w, &h, &n, 4);
3422     if (img is null) {
3423       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3424       return NVGImage.init;
3425     }
3426     auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3427     stbi_image_free(img);
3428     return image;
3429   }
3430 }
3431 
3432 static if (NanoVegaHasArsdImage) {
3433   /// Creates image by loading it from the specified memory image.
3434   /// Returns handle to the image or 0 on error.
3435   /// Group: images
3436   public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) {
3437     if (img is null) return NVGImage.init;
3438     if (auto tc = cast(TrueColorImage)img) {
3439       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3440     } else {
3441       auto tc = img.getAsTrueColorImage;
3442       scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it
3443       return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList);
3444     }
3445   }
3446 } else {
3447   /// Creates image by loading it from the specified chunk of memory.
3448   /// Returns handle to the image or 0 on error.
3449   /// Group: images
3450   public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) {
3451     int w, h, n, image;
3452     ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
3453     if (img is null) {
3454       //printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
3455       return NVGImage.init;
3456     }
3457     image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList);
3458     stbi_image_free(img);
3459     return image;
3460   }
3461 }
3462 
3463 /// Creates image from specified image data.
3464 /// Returns handle to the image or 0 on error.
3465 /// Group: images
3466 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc {
3467   if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init;
3468   uint imageFlags = 0;
3469   foreach (immutable uint flag; imageFlagsList) imageFlags |= flag;
3470   NVGImage res;
3471   res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr);
3472   if (res.id > 0) {
3473     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
3474     res.ctx = ctx;
3475     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
3476   }
3477   return res;
3478 }
3479 
3480 /// Updates image data specified by image handle.
3481 /// Group: images
3482 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc {
3483   if (image.valid) {
3484     int w, h;
3485     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3486     ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h);
3487     ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr);
3488   }
3489 }
3490 
3491 /// Returns the dimensions of a created image.
3492 /// Group: images
3493 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc {
3494   if (image.valid) {
3495     if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3496     ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h);
3497   }
3498 }
3499 
3500 /// Deletes created image.
3501 /// Group: images
3502 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc {
3503   if (ctx is null || !image.valid) return;
3504   if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context");
3505   image.clear();
3506 }
3507 
3508 
3509 // ////////////////////////////////////////////////////////////////////////// //
3510 // Paints
3511 
3512 private void linearGradient() {} // hack for dmd bug
3513 
3514 static if (NanoVegaHasArsdColor) {
3515 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3516  * of the linear gradient, icol specifies the start color and ocol the end color.
3517  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3518  *
3519  * Group: paints
3520  */
3521 @scriptable
3522 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 {
3523   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
3524 }
3525 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3526  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3527  * range `(0..1)`, and ocol the end color.
3528  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3529  *
3530  * Group: paints
3531  */
3532 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 {
3533   return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
3534 }
3535 }
3536 
3537 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
3538  * of the linear gradient, icol specifies the start color and ocol the end color.
3539  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3540  *
3541  * Group: paints
3542  */
3543 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 {
3544   enum large = 1e5f;
3545 
3546   NVGPaint p = void;
3547   memset(&p, 0, p.sizeof);
3548   p.simpleColor = false;
3549 
3550   // Calculate transform aligned to the line
3551   float dx = ex-sx;
3552   float dy = ey-sy;
3553   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3554   if (d > 0.0001f) {
3555     dx /= d;
3556     dy /= d;
3557   } else {
3558     dx = 0;
3559     dy = 1;
3560   }
3561 
3562   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3563   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3564   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3565 
3566   p.extent.ptr[0] = large;
3567   p.extent.ptr[1] = large+d*0.5f;
3568 
3569   p.radius = 0.0f;
3570 
3571   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3572 
3573   p.innerColor = p.middleColor = icol;
3574   p.outerColor = ocol;
3575   p.midp = -1;
3576 
3577   return p;
3578 }
3579 
3580 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
3581  * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
3582  * range `(0..1)`, and ocol the end color.
3583  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3584  *
3585  * Group: paints
3586  */
3587 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 {
3588   enum large = 1e5f;
3589 
3590   NVGPaint p = void;
3591   memset(&p, 0, p.sizeof);
3592   p.simpleColor = false;
3593 
3594   // Calculate transform aligned to the line
3595   float dx = ex-sx;
3596   float dy = ey-sy;
3597   immutable float d = nvg__sqrtf(dx*dx+dy*dy);
3598   if (d > 0.0001f) {
3599     dx /= d;
3600     dy /= d;
3601   } else {
3602     dx = 0;
3603     dy = 1;
3604   }
3605 
3606   p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
3607   p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
3608   p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
3609 
3610   p.extent.ptr[0] = large;
3611   p.extent.ptr[1] = large+d*0.5f;
3612 
3613   p.radius = 0.0f;
3614 
3615   p.feather = nvg__max(NVG_MIN_FEATHER, d);
3616 
3617   if (midp <= 0) {
3618     p.innerColor = p.middleColor = mcol;
3619     p.midp = -1;
3620   } else if (midp > 1) {
3621     p.innerColor = p.middleColor = icol;
3622     p.midp = -1;
3623   } else {
3624     p.innerColor = icol;
3625     p.middleColor = mcol;
3626     p.midp = midp;
3627   }
3628   p.outerColor = ocol;
3629 
3630   return p;
3631 }
3632 
3633 static if (NanoVegaHasArsdColor) {
3634 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3635  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3636  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3637  *
3638  * Group: paints
3639  */
3640 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 {
3641   return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol));
3642 }
3643 }
3644 
3645 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify
3646  * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
3647  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3648  *
3649  * Group: paints
3650  */
3651 public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
3652   immutable float r = (inr+outr)*0.5f;
3653   immutable float f = (outr-inr);
3654 
3655   NVGPaint p = void;
3656   memset(&p, 0, p.sizeof);
3657   p.simpleColor = false;
3658 
3659   p.xform.identity;
3660   p.xform.mat.ptr[4] = cx;
3661   p.xform.mat.ptr[5] = cy;
3662 
3663   p.extent.ptr[0] = r;
3664   p.extent.ptr[1] = r;
3665 
3666   p.radius = r;
3667 
3668   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3669 
3670   p.innerColor = p.middleColor = icol;
3671   p.outerColor = ocol;
3672   p.midp = -1;
3673 
3674   return p;
3675 }
3676 
3677 static if (NanoVegaHasArsdColor) {
3678 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3679  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3680  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3681  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3682  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3683  *
3684  * Group: paints
3685  */
3686 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 {
3687   return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol));
3688 }
3689 }
3690 
3691 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
3692  * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle,
3693  * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
3694  * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
3695  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3696  *
3697  * Group: paints
3698  */
3699 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 {
3700   NVGPaint p = void;
3701   memset(&p, 0, p.sizeof);
3702   p.simpleColor = false;
3703 
3704   p.xform.identity;
3705   p.xform.mat.ptr[4] = x+w*0.5f;
3706   p.xform.mat.ptr[5] = y+h*0.5f;
3707 
3708   p.extent.ptr[0] = w*0.5f;
3709   p.extent.ptr[1] = h*0.5f;
3710 
3711   p.radius = r;
3712 
3713   p.feather = nvg__max(NVG_MIN_FEATHER, f);
3714 
3715   p.innerColor = p.middleColor = icol;
3716   p.outerColor = ocol;
3717   p.midp = -1;
3718 
3719   return p;
3720 }
3721 
3722 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern,
3723  * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render.
3724  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3725  *
3726  * Group: paints
3727  */
3728 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 {
3729   NVGPaint p = void;
3730   memset(&p, 0, p.sizeof);
3731   p.simpleColor = false;
3732 
3733   p.xform.identity.rotate(angle);
3734   p.xform.mat.ptr[4] = cx;
3735   p.xform.mat.ptr[5] = cy;
3736 
3737   p.extent.ptr[0] = w;
3738   p.extent.ptr[1] = h;
3739 
3740   p.image = image;
3741 
3742   p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
3743   p.midp = -1;
3744 
3745   return p;
3746 }
3747 
3748 /// Linear gradient with multiple stops.
3749 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3750 /// Group: paints
3751 public struct NVGLGS {
3752 private:
3753   NVGColor ic, mc, oc; // inner, middle, out
3754   float midp;
3755   NVGImage imgid;
3756   // [imagePattern] arguments
3757   float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
3758   public float angle; ///
3759 
3760 public:
3761   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
3762   void clear ()  nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
3763 }
3764 
3765 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
3766  * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
3767  *
3768  * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3769  * Group: paints
3770  */
3771 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
3772   if (!lgs.valid) {
3773     NVGPaint p = void;
3774     memset(&p, 0, p.sizeof);
3775     nvg__setPaintColor(p, NVGColor.red);
3776     return p;
3777   } else if (lgs.midp >= -1) {
3778     return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
3779   } else {
3780     return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
3781   }
3782 }
3783 
3784 /// Gradient Stop Point.
3785 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3786 /// Group: paints
3787 public struct NVGGradientStop {
3788   float offset = 0; /// [0..1]
3789   NVGColor color; ///
3790 
3791   this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
3792   static if (NanoVegaHasArsdColor) {
3793     this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
3794   }
3795 }
3796 
3797 /// Create linear gradient data suitable to use with `linearGradient(res)`.
3798 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`.
3799 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!)
3800 /// Group: paints
3801 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc {
3802   // based on the code by Jorge Acereda <jacereda@gmail.com>
3803   enum NVG_GRADIENT_SAMPLES = 1024;
3804   static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc {
3805     immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f);
3806     immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f);
3807     uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES);
3808     uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES);
3809     uint sc = 0xffffffffU;
3810     uint sh = 24;
3811     uint r = cast(uint)(s0.color.rgba[0]*sc);
3812     uint g = cast(uint)(s0.color.rgba[1]*sc);
3813     uint b = cast(uint)(s0.color.rgba[2]*sc);
3814     uint a = cast(uint)(s0.color.rgba[3]*sc);
3815     uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s));
3816     uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s));
3817     uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s));
3818     uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s));
3819     dst += s;
3820     foreach (immutable _; s..e) {
3821       version(BigEndian) {
3822         *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0);
3823       } else {
3824         *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0);
3825       }
3826       r += dr;
3827       g += dg;
3828       b += db;
3829       a += da;
3830     }
3831   }
3832 
3833   NVGLGS res;
3834   res.cx = sx;
3835   res.cy = sy;
3836 
3837   if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
3838     // create simple linear gradient
3839     res.ic = res.mc = stops.ptr[0].color;
3840     res.oc = stops.ptr[1].color;
3841     res.midp = -1;
3842     res.dimx = ex;
3843     res.dimy = ey;
3844   } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
3845     // create simple linear gradient with middle stop
3846     res.ic = stops.ptr[0].color;
3847     res.mc = stops.ptr[1].color;
3848     res.oc = stops.ptr[2].color;
3849     res.midp = stops.ptr[1].offset;
3850     res.dimx = ex;
3851     res.dimy = ey;
3852   } else {
3853     // create image gradient
3854     uint[NVG_GRADIENT_SAMPLES] data = void;
3855     immutable float w = ex-sx;
3856     immutable float h = ey-sy;
3857     res.dimx = nvg__sqrtf(w*w+h*h);
3858     res.dimy = 1; //???
3859 
3860     res.angle =
3861       (/*nvg__absf(h) < 0.0001 ? 0 :
3862        nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
3863        nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/));
3864 
3865     if (stops.length > 0) {
3866       auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1));
3867       auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1));
3868       if (stops.length > 64) stops = stops[0..64];
3869       if (stops.length) {
3870         s0.color = stops[0].color;
3871         s1.color = stops[$-1].color;
3872       }
3873       gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
3874       foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
3875       gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
3876       res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
3877     }
3878   }
3879   return res;
3880 }
3881 
3882 
3883 // ////////////////////////////////////////////////////////////////////////// //
3884 // Scissoring
3885 
3886 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3887 /// Group: scissoring
3888 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc {
3889   NVGstate* state = nvg__getState(ctx);
3890 
3891   w = nvg__max(0.0f, w);
3892   h = nvg__max(0.0f, h);
3893 
3894   state.scissor.xform.identity;
3895   state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3896   state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3897   //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3898   state.scissor.xform.mul(state.xform);
3899 
3900   state.scissor.extent.ptr[0] = w*0.5f;
3901   state.scissor.extent.ptr[1] = h*0.5f;
3902 }
3903 
3904 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform.
3905 /// Arguments: [x, y, w, h]*
3906 /// Group: scissoring
3907 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3908   enum ArgC = 4;
3909   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call");
3910   if (args.length < ArgC) return;
3911   NVGstate* state = nvg__getState(ctx);
3912   const(float)* aptr = args.ptr;
3913   foreach (immutable idx; 0..args.length/ArgC) {
3914     immutable x = *aptr++;
3915     immutable y = *aptr++;
3916     immutable w = nvg__max(0.0f, *aptr++);
3917     immutable h = nvg__max(0.0f, *aptr++);
3918 
3919     state.scissor.xform.identity;
3920     state.scissor.xform.mat.ptr[4] = x+w*0.5f;
3921     state.scissor.xform.mat.ptr[5] = y+h*0.5f;
3922     //nvgTransformMultiply(state.scissor.xform[], state.xform[]);
3923     state.scissor.xform.mul(state.xform);
3924 
3925     state.scissor.extent.ptr[0] = w*0.5f;
3926     state.scissor.extent.ptr[1] = h*0.5f;
3927   }
3928 }
3929 
3930 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc {
3931   immutable float minx = nvg__max(ax, bx);
3932   immutable float miny = nvg__max(ay, by);
3933   immutable float maxx = nvg__min(ax+aw, bx+bw);
3934   immutable float maxy = nvg__min(ay+ah, by+bh);
3935   dst[0] = minx;
3936   dst[1] = miny;
3937   dst[2] = nvg__max(0.0f, maxx-minx);
3938   dst[3] = nvg__max(0.0f, maxy-miny);
3939 }
3940 
3941 /** Intersects current scissor rectangle with the specified rectangle.
3942  * The scissor rectangle is transformed by the current transform.
3943  * Note: in case the rotation of previous scissor rect differs from
3944  * the current one, the intersection will be done between the specified
3945  * rectangle and the previous scissor rectangle transformed in the current
3946  * transform space. The resulting shape is always rectangle.
3947  *
3948  * Group: scissoring
3949  */
3950 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
3951   NVGstate* state = nvg__getState(ctx);
3952 
3953   // If no previous scissor has been set, set the scissor as current scissor.
3954   if (state.scissor.extent.ptr[0] < 0) {
3955     ctx.scissor(x, y, w, h);
3956     return;
3957   }
3958 
3959   NVGMatrix pxform = void;
3960   NVGMatrix invxorm = void;
3961   float[4] rect = void;
3962 
3963   // Transform the current scissor rect into current transform space.
3964   // If there is difference in rotation, this will be approximation.
3965   //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6);
3966   pxform = state.scissor.xform;
3967   immutable float ex = state.scissor.extent.ptr[0];
3968   immutable float ey = state.scissor.extent.ptr[1];
3969   //nvgTransformInverse(invxorm[], state.xform[]);
3970   invxorm = state.xform.inverted;
3971   //nvgTransformMultiply(pxform[], invxorm[]);
3972   pxform.mul(invxorm);
3973   immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]);
3974   immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]);
3975 
3976   // Intersect rects.
3977   nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h);
3978 
3979   //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]);
3980   ctx.scissor(rect.ptr[0..4]);
3981 }
3982 
3983 /** Intersects current scissor rectangle with the specified rectangle.
3984  * The scissor rectangle is transformed by the current transform.
3985  * Note: in case the rotation of previous scissor rect differs from
3986  * the current one, the intersection will be done between the specified
3987  * rectangle and the previous scissor rectangle transformed in the current
3988  * transform space. The resulting shape is always rectangle.
3989  *
3990  * Arguments: [x, y, w, h]*
3991  *
3992  * Group: scissoring
3993  */
3994 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
3995   enum ArgC = 4;
3996   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call");
3997   if (args.length < ArgC) return;
3998   const(float)* aptr = args.ptr;
3999   foreach (immutable idx; 0..args.length/ArgC) {
4000     immutable x = *aptr++;
4001     immutable y = *aptr++;
4002     immutable w = *aptr++;
4003     immutable h = *aptr++;
4004     ctx.intersectScissor(x, y, w, h);
4005   }
4006 }
4007 
4008 /// Reset and disables scissoring.
4009 /// Group: scissoring
4010 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc {
4011   NVGstate* state = nvg__getState(ctx);
4012   state.scissor.xform.mat[] = 0.0f;
4013   state.scissor.extent[] = -1.0f;
4014 }
4015 
4016 
4017 // ////////////////////////////////////////////////////////////////////////// //
4018 // Render-Time Affine Transformations
4019 
4020 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here.
4021 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state.
4022 /// Group: gpu_affine
4023 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc {
4024   ctx.gpuAffine = mat;
4025   ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine);
4026 }
4027 
4028 /// Get current GPU affine transformatin matrix.
4029 /// Group: gpu_affine
4030 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc {
4031   pragma(inline, true);
4032   return ctx.gpuAffine;
4033 }
4034 
4035 /// "Untransform" point using current GPU affine matrix.
4036 /// Group: gpu_affine
4037 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc {
4038   if (ctx.gpuAffine.isIdentity) {
4039     if (dx !is null) *dx = x;
4040     if (dy !is null) *dy = y;
4041   } else {
4042     // inverse GPU transformation
4043     NVGMatrix igpu = ctx.gpuAffine.inverted;
4044     igpu.point(dx, dy, x, y);
4045   }
4046 }
4047 
4048 
4049 // ////////////////////////////////////////////////////////////////////////// //
4050 // rasterization (tesselation) code
4051 
4052 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc {
4053   //pragma(inline, true);
4054   immutable float dx = x2-x1;
4055   immutable float dy = y2-y1;
4056   return dx*dx+dy*dy < tol*tol;
4057 }
4058 
4059 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc {
4060   immutable float pqx = qx-px;
4061   immutable float pqy = qy-py;
4062   float dx = x-px;
4063   float dy = y-py;
4064   immutable float d = pqx*pqx+pqy*pqy;
4065   float t = pqx*dx+pqy*dy;
4066   if (d > 0) t /= d;
4067   if (t < 0) t = 0; else if (t > 1) t = 1;
4068   dx = px+t*pqx-x;
4069   dy = py+t*pqy-y;
4070   return dx*dx+dy*dy;
4071 }
4072 
4073 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc {
4074   int nvals = cast(int)vals.length;
4075   static if (useCommand) {
4076     enum addon = 1;
4077   } else {
4078     enum addon = 0;
4079     if (nvals == 0) return; // nothing to do
4080   }
4081 
4082   NVGstate* state = nvg__getState(ctx);
4083 
4084   if (ctx.ncommands+nvals+addon > ctx.ccommands) {
4085     //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2;
4086     int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1;
4087     float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands);
4088     if (commands is null) assert(0, "NanoVega: out of memory");
4089     ctx.commands = commands;
4090     ctx.ccommands = ccommands;
4091     assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands);
4092   }
4093 
4094   static if (!useCommand) acmd = cast(Command)vals.ptr[0];
4095 
4096   if (acmd != Command.Close && acmd != Command.Winding) {
4097     //assert(nvals+addon >= 3);
4098     ctx.commandx = vals.ptr[nvals-2];
4099     ctx.commandy = vals.ptr[nvals-1];
4100   }
4101 
4102   // copy commands
4103   float* vp = ctx.commands+ctx.ncommands;
4104   static if (useCommand) {
4105     vp[0] = cast(float)acmd;
4106     if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof);
4107   } else {
4108     memcpy(vp, vals.ptr, nvals*float.sizeof);
4109   }
4110   ctx.ncommands += nvals+addon;
4111 
4112   // transform commands
4113   int i = nvals+addon;
4114   while (i > 0) {
4115     int nlen = 1;
4116     final switch (cast(Command)(*vp)) {
4117       case Command.MoveTo:
4118       case Command.LineTo:
4119         assert(i >= 3);
4120         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4121         nlen = 3;
4122         break;
4123       case Command.BezierTo:
4124         assert(i >= 7);
4125         state.xform.point(vp+1, vp+2, vp[1], vp[2]);
4126         state.xform.point(vp+3, vp+4, vp[3], vp[4]);
4127         state.xform.point(vp+5, vp+6, vp[5], vp[6]);
4128         nlen = 7;
4129         break;
4130       case Command.Close:
4131         nlen = 1;
4132         break;
4133       case Command.Winding:
4134         nlen = 2;
4135         break;
4136     }
4137     assert(nlen > 0 && nlen <= i);
4138     i -= nlen;
4139     vp += nlen;
4140   }
4141 }
4142 
4143 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc {
4144   // no need to clear paths, as data is not copied there
4145   //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear();
4146   ctx.cache.npoints = 0;
4147   ctx.cache.npaths = 0;
4148   ctx.cache.fillReady = ctx.cache.strokeReady = false;
4149   ctx.cache.clipmode = NVGClipMode.None;
4150 }
4151 
4152 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc {
4153   return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null);
4154 }
4155 
4156 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc {
4157   import core.stdc.stdlib : realloc;
4158   import core.stdc.string : memset;
4159 
4160   if (ctx.cache.npaths+1 > ctx.cache.cpaths) {
4161     int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2;
4162     NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths);
4163     if (paths is null) assert(0, "NanoVega: out of memory");
4164     ctx.cache.paths = paths;
4165     ctx.cache.cpaths = cpaths;
4166   }
4167 
4168   NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++];
4169   memset(path, 0, NVGpath.sizeof);
4170   path.first = ctx.cache.npoints;
4171   path.mWinding = NVGWinding.CCW;
4172 }
4173 
4174 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc {
4175   return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null);
4176 }
4177 
4178 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc {
4179   NVGpath* path = nvg__lastPath(ctx);
4180   if (path is null) return;
4181 
4182   if (path.count > 0 && ctx.cache.npoints > 0) {
4183     NVGpoint* pt = nvg__lastPoint(ctx);
4184     if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) {
4185       pt.flags |= flags;
4186       return;
4187     }
4188   }
4189 
4190   if (ctx.cache.npoints+1 > ctx.cache.cpoints) {
4191     int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2;
4192     NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints);
4193     if (points is null) return;
4194     ctx.cache.points = points;
4195     ctx.cache.cpoints = cpoints;
4196   }
4197 
4198   NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints];
4199   memset(pt, 0, (*pt).sizeof);
4200   pt.x = x;
4201   pt.y = y;
4202   pt.flags = cast(ubyte)flags;
4203 
4204   ++ctx.cache.npoints;
4205   ++path.count;
4206 }
4207 
4208 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc {
4209   NVGpath* path = nvg__lastPath(ctx);
4210   if (path is null) return;
4211   path.closed = true;
4212 }
4213 
4214 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc {
4215   NVGpath* path = nvg__lastPath(ctx);
4216   if (path is null) return;
4217   path.mWinding = winding;
4218 }
4219 
4220 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc {
4221   immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]);
4222   immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]);
4223   return (sx+sy)*0.5f;
4224 }
4225 
4226 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc {
4227   if (nverts > ctx.cache.cverts) {
4228     int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly.
4229     NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts);
4230     if (verts is null) return null;
4231     ctx.cache.verts = verts;
4232     ctx.cache.cverts = cverts;
4233   }
4234 
4235   return ctx.cache.verts;
4236 }
4237 
4238 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc {
4239   immutable float abx = bx-ax;
4240   immutable float aby = by-ay;
4241   immutable float acx = cx-ax;
4242   immutable float acy = cy-ay;
4243   return acx*aby-abx*acy;
4244 }
4245 
4246 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4247   float area = 0;
4248   foreach (int i; 2..npts) {
4249     NVGpoint* a = &pts[0];
4250     NVGpoint* b = &pts[i-1];
4251     NVGpoint* c = &pts[i];
4252     area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y);
4253   }
4254   return area*0.5f;
4255 }
4256 
4257 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc {
4258   NVGpoint tmp = void;
4259   int i = 0, j = npts-1;
4260   while (i < j) {
4261     tmp = pts[i];
4262     pts[i] = pts[j];
4263     pts[j] = tmp;
4264     ++i;
4265     --j;
4266   }
4267 }
4268 
4269 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
4270   vtx.x = x;
4271   vtx.y = y;
4272   vtx.u = u;
4273   vtx.v = v;
4274 }
4275 
4276 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 {
4277   if (level > 10) return;
4278 
4279   // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case)
4280   /*
4281   if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) {
4282     static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc {
4283       immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y);
4284       return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output
4285     }
4286     if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) {
4287       //{ import core.stdc.stdio; printf("AFD fallback!\n"); }
4288       ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type);
4289       return;
4290     }
4291   }
4292   */
4293 
4294   immutable float x12 = (x1+x2)*0.5f;
4295   immutable float y12 = (y1+y2)*0.5f;
4296   immutable float x23 = (x2+x3)*0.5f;
4297   immutable float y23 = (y2+y3)*0.5f;
4298   immutable float x34 = (x3+x4)*0.5f;
4299   immutable float y34 = (y3+y4)*0.5f;
4300   immutable float x123 = (x12+x23)*0.5f;
4301   immutable float y123 = (y12+y23)*0.5f;
4302 
4303   immutable float dx = x4-x1;
4304   immutable float dy = y4-y1;
4305   immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4306   immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4307 
4308   if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) {
4309     nvg__addPoint(ctx, x4, y4, type);
4310     return;
4311   }
4312 
4313   immutable float x234 = (x23+x34)*0.5f;
4314   immutable float y234 = (y23+y34)*0.5f;
4315   immutable float x1234 = (x123+x234)*0.5f;
4316   immutable float y1234 = (y123+y234)*0.5f;
4317 
4318   // "taxicab" / "manhattan" check for flat curves
4319   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) {
4320     nvg__addPoint(ctx, x1234, y1234, type);
4321     return;
4322   }
4323 
4324   nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4325   nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4326 }
4327 
4328 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
4329 // see http://www.antigrain.com/research/adaptive_bezier/index.html
4330 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 {
4331   enum CollinearEPS = 0.00000001f; // 0.00001f;
4332   enum AngleTolEPS = 0.01f;
4333 
4334   static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
4335     pragma(inline, true);
4336     immutable float dx = x2-x1;
4337     immutable float dy = y2-y1;
4338     return dx*dx+dy*dy;
4339   }
4340 
4341   if (level == 0) {
4342     nvg__addPoint(ctx, x1, y1, 0);
4343     nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
4344     nvg__addPoint(ctx, x4, y4, type);
4345     return;
4346   }
4347 
4348   if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
4349 
4350   // calculate all the mid-points of the line segments
4351   immutable float x12 = (x1+x2)*0.5f;
4352   immutable float y12 = (y1+y2)*0.5f;
4353   immutable float x23 = (x2+x3)*0.5f;
4354   immutable float y23 = (y2+y3)*0.5f;
4355   immutable float x34 = (x3+x4)*0.5f;
4356   immutable float y34 = (y3+y4)*0.5f;
4357   immutable float x123 = (x12+x23)*0.5f;
4358   immutable float y123 = (y12+y23)*0.5f;
4359   immutable float x234 = (x23+x34)*0.5f;
4360   immutable float y234 = (y23+y34)*0.5f;
4361   immutable float x1234 = (x123+x234)*0.5f;
4362   immutable float y1234 = (y123+y234)*0.5f;
4363 
4364   // try to approximate the full cubic curve by a single straight line
4365   immutable float dx = x4-x1;
4366   immutable float dy = y4-y1;
4367 
4368   float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
4369   float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
4370   //immutable float da1, da2, k;
4371 
4372   final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
4373     case 0:
4374       // all collinear or p1 == p4
4375       float k = dx*dx+dy*dy;
4376       if (k == 0) {
4377         d2 = distSquared(x1, y1, x2, y2);
4378         d3 = distSquared(x4, y4, x3, y3);
4379       } else {
4380         k = 1.0f/k;
4381         float da1 = x2-x1;
4382         float da2 = y2-y1;
4383         d2 = k*(da1*dx+da2*dy);
4384         da1 = x3-x1;
4385         da2 = y3-y1;
4386         d3 = k*(da1*dx+da2*dy);
4387         if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
4388           // Simple collinear case, 1---2---3---4
4389           // We can leave just two endpoints
4390           return;
4391         }
4392              if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
4393         else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
4394         else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
4395 
4396              if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
4397         else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
4398         else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
4399       }
4400       if (d2 > d3) {
4401         if (d2 < ctx.tessTol) {
4402           nvg__addPoint(ctx, x2, y2, type);
4403           return;
4404         }
4405       } if (d3 < ctx.tessTol) {
4406         nvg__addPoint(ctx, x3, y3, type);
4407         return;
4408       }
4409       break;
4410     case 1:
4411       // p1,p2,p4 are collinear, p3 is significant
4412       if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
4413         if (ctx.angleTol < AngleTolEPS) {
4414           nvg__addPoint(ctx, x23, y23, type);
4415           return;
4416         } else {
4417           // angle condition
4418           float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
4419           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4420           if (da1 < ctx.angleTol) {
4421             nvg__addPoint(ctx, x2, y2, type);
4422             nvg__addPoint(ctx, x3, y3, type);
4423             return;
4424           }
4425           if (ctx.cuspLimit != 0.0) {
4426             if (da1 > ctx.cuspLimit) {
4427               nvg__addPoint(ctx, x3, y3, type);
4428               return;
4429             }
4430           }
4431         }
4432       }
4433       break;
4434     case 2:
4435       // p1,p3,p4 are collinear, p2 is significant
4436       if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
4437         if (ctx.angleTol < AngleTolEPS) {
4438           nvg__addPoint(ctx, x23, y23, type);
4439           return;
4440         } else {
4441           // angle condition
4442           float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
4443           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4444           if (da1 < ctx.angleTol) {
4445             nvg__addPoint(ctx, x2, y2, type);
4446             nvg__addPoint(ctx, x3, y3, type);
4447             return;
4448           }
4449           if (ctx.cuspLimit != 0.0) {
4450             if (da1 > ctx.cuspLimit) {
4451               nvg__addPoint(ctx, x2, y2, type);
4452               return;
4453             }
4454           }
4455         }
4456       }
4457       break;
4458     case 3:
4459       // regular case
4460       if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
4461         // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
4462         if (ctx.angleTol < AngleTolEPS) {
4463           nvg__addPoint(ctx, x23, y23, type);
4464           return;
4465         } else {
4466           // angle and cusp condition
4467           immutable float k = nvg__atan2f(y3-y2, x3-x2);
4468           float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
4469           float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
4470           if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
4471           if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
4472           if (da1+da2 < ctx.angleTol) {
4473             // finally we can stop the recursion
4474             nvg__addPoint(ctx, x23, y23, type);
4475             return;
4476           }
4477           if (ctx.cuspLimit != 0.0) {
4478             if (da1 > ctx.cuspLimit) {
4479               nvg__addPoint(ctx, x2, y2, type);
4480               return;
4481             }
4482             if (da2 > ctx.cuspLimit) {
4483               nvg__addPoint(ctx, x3, y3, type);
4484               return;
4485             }
4486           }
4487         }
4488       }
4489       break;
4490   }
4491 
4492   // continue subdivision
4493   nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
4494   nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
4495 }
4496 
4497 
4498 // Adaptive forward differencing for bezier tesselation.
4499 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
4500 // "Adaptive forward differencing for rendering curves and surfaces."
4501 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987.
4502 // original code by Taylor Holliday <taylor@audulus.com>
4503 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 {
4504   enum AFD_ONE = (1<<10);
4505 
4506   // power basis
4507   immutable float ax = -x1+3*x2-3*x3+x4;
4508   immutable float ay = -y1+3*y2-3*y3+y4;
4509   immutable float bx = 3*x1-6*x2+3*x3;
4510   immutable float by = 3*y1-6*y2+3*y3;
4511   immutable float cx = -3*x1+3*x2;
4512   immutable float cy = -3*y1+3*y2;
4513 
4514   // Transform to forward difference basis (stepsize 1)
4515   float px = x1;
4516   float py = y1;
4517   float dx = ax+bx+cx;
4518   float dy = ay+by+cy;
4519   float ddx = 6*ax+2*bx;
4520   float ddy = 6*ay+2*by;
4521   float dddx = 6*ax;
4522   float dddy = 6*ay;
4523 
4524   //printf("dx: %f, dy: %f\n", dx, dy);
4525   //printf("ddx: %f, ddy: %f\n", ddx, ddy);
4526   //printf("dddx: %f, dddy: %f\n", dddx, dddy);
4527 
4528   int t = 0;
4529   int dt = AFD_ONE;
4530 
4531   immutable float tol = ctx.tessTol*4;
4532 
4533   while (t < AFD_ONE) {
4534     // Flatness measure.
4535     float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4536 
4537     // printf("d: %f, th: %f\n", d, th);
4538 
4539     // Go to higher resolution if we're moving a lot or overshooting the end.
4540     while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) {
4541       // printf("up\n");
4542 
4543       // Apply L to the curve. Increase curve resolution.
4544       dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx;
4545       dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy;
4546       ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx;
4547       ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy;
4548       dddx = (1.0f/8.0f)*dddx;
4549       dddy = (1.0f/8.0f)*dddy;
4550 
4551       // Half the stepsize.
4552       dt >>= 1;
4553 
4554       // Recompute d
4555       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4556     }
4557 
4558     // Go to lower resolution if we're really flat
4559     // and we aren't going to overshoot the end.
4560     // XXX: tol/32 is just a guess for when we are too flat.
4561     while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) {
4562       // printf("down\n");
4563 
4564       // Apply L^(-1) to the curve. Decrease curve resolution.
4565       dx = 2*dx+ddx;
4566       dy = 2*dy+ddy;
4567       ddx = 4*ddx+4*dddx;
4568       ddy = 4*ddy+4*dddy;
4569       dddx = 8*dddx;
4570       dddy = 8*dddy;
4571 
4572       // Double the stepsize.
4573       dt <<= 1;
4574 
4575       // Recompute d
4576       d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy;
4577     }
4578 
4579     // Forward differencing.
4580     px += dx;
4581     py += dy;
4582     dx += ddx;
4583     dy += ddy;
4584     ddx += dddx;
4585     ddy += dddy;
4586 
4587     // Output a point.
4588     nvg__addPoint(ctx, px, py, (t > 0 ? type : 0));
4589 
4590     // Advance along the curve.
4591     t += dt;
4592 
4593     // Ensure we don't overshoot.
4594     assert(t <= AFD_ONE);
4595   }
4596 }
4597 
4598 
4599 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc {
4600   import core.stdc.stdlib : realloc;
4601   import core.stdc.string : memcpy;
4602 
4603   NVGpathCache* cache = ctx.cache;
4604   if (cache.npaths == 0) return;
4605 
4606   NVGpath* path = nvg__lastPath(ctx);
4607   if (path is null) return;
4608 
4609   NVGstate* state = nvg__getState(ctx);
4610   if (!state.dasherActive) return;
4611 
4612   static NVGpoint* pts = null;
4613   static uint ptsCount = 0;
4614   static uint ptsSize = 0;
4615 
4616   if (path.count < 2) return; // just in case
4617 
4618   // copy path points (reserve one point for closed pathes)
4619   if (ptsSize < path.count+1) {
4620     ptsSize = cast(uint)(path.count+1);
4621     pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof);
4622     if (pts is null) assert(0, "NanoVega: out of memory");
4623   }
4624   ptsCount = cast(uint)path.count;
4625   memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof);
4626   // add closing point for closed pathes
4627   if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) {
4628     pts[ptsCount++] = pts[0];
4629   }
4630 
4631   // remove last path (with its points)
4632   --cache.npaths;
4633   cache.npoints -= path.count;
4634 
4635   // add stroked pathes
4636   const(float)* dashes = state.dashes.ptr;
4637   immutable uint dashCount = state.dashCount;
4638   float currDashStart = 0;
4639   uint currDashIdx = 0;
4640   immutable bool firstIsGap = state.firstDashIsGap;
4641 
4642   // calculate lengthes
4643   {
4644     NVGpoint* v1 = &pts[0];
4645     NVGpoint* v2 = &pts[1];
4646     foreach (immutable _; 0..ptsCount) {
4647       float dx = v2.x-v1.x;
4648       float dy = v2.y-v1.y;
4649       v1.len = nvg__normalize(&dx, &dy);
4650       v1 = v2++;
4651     }
4652   }
4653 
4654   void calcDashStart (float ds) {
4655     if (ds < 0) {
4656       ds = ds%state.totalDashLen;
4657       while (ds < 0) ds += state.totalDashLen;
4658     }
4659     currDashIdx = 0;
4660     currDashStart = 0;
4661     while (ds > 0) {
4662       if (ds > dashes[currDashIdx]) {
4663         ds -= dashes[currDashIdx];
4664         ++currDashIdx;
4665         currDashStart = 0;
4666         if (currDashIdx >= dashCount) currDashIdx = 0;
4667       } else {
4668         currDashStart = ds;
4669         ds = 0;
4670       }
4671     }
4672   }
4673 
4674   calcDashStart(state.dashStart);
4675 
4676   uint srcPointIdx = 1;
4677   const(NVGpoint)* v1 = &pts[0];
4678   const(NVGpoint)* v2 = &pts[1];
4679   float currRest = v1.len;
4680   nvg__addPath(ctx);
4681   nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner);
4682 
4683   void fixLastPoint () {
4684     auto lpt = nvg__lastPath(ctx);
4685     if (lpt !is null && lpt.count > 0) {
4686       // fix last point
4687       if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner;
4688       // fix first point
4689       NVGpathCache* cache = ctx.cache;
4690       cache.points[lpt.first].flags = PointFlag.Corner;
4691     }
4692   }
4693 
4694   for (;;) {
4695     immutable float dlen = dashes[currDashIdx];
4696     if (dlen == 0) {
4697       ++currDashIdx;
4698       if (currDashIdx >= dashCount) currDashIdx = 0;
4699       continue;
4700     }
4701     immutable float dashRest = dlen-currDashStart;
4702     if ((currDashIdx&1) != firstIsGap) {
4703       // this is "moveto" command, so create new path
4704       fixLastPoint();
4705       nvg__addPath(ctx);
4706     }
4707     if (currRest > dashRest) {
4708       currRest -= dashRest;
4709       ++currDashIdx;
4710       if (currDashIdx >= dashCount) currDashIdx = 0;
4711       currDashStart = 0;
4712       nvg__addPoint(ctx,
4713         v2.x-(v2.x-v1.x)*currRest/v1.len,
4714         v2.y-(v2.y-v1.y)*currRest/v1.len,
4715         PointFlag.Corner
4716       );
4717     } else {
4718       currDashStart += currRest;
4719       nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here?
4720       ++srcPointIdx;
4721       v1 = v2;
4722       currRest = v1.len;
4723       if (srcPointIdx >= ptsCount) break;
4724       v2 = &pts[srcPointIdx];
4725     }
4726   }
4727   fixLastPoint();
4728 }
4729 
4730 
4731 version(nanovg_bench_flatten) import iv.timer : Timer;
4732 
4733 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc {
4734   version(nanovg_bench_flatten) {
4735     Timer timer;
4736     char[128] tmbuf;
4737     int bzcount;
4738   }
4739   NVGpathCache* cache = ctx.cache;
4740   NVGstate* state = nvg__getState(ctx);
4741 
4742   // check if we already did flattening
4743   static if (asStroke) {
4744     if (state.dashCount >= 2) {
4745       if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened
4746       state.dasherActive = true;
4747       state.lastFlattenDashCount = state.dashCount;
4748     } else {
4749       if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4750       state.dasherActive = false;
4751       state.lastFlattenDashCount = 0;
4752     }
4753   } else {
4754     if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened
4755     state.lastFlattenDashCount = 0; // so next stroke flattening will redo it
4756     state.dasherActive = false;
4757   }
4758 
4759   // clear path cache
4760   cache.npaths = 0;
4761   cache.npoints = 0;
4762 
4763   // flatten
4764   version(nanovg_bench_flatten) timer.restart();
4765   int i = 0;
4766   while (i < ctx.ncommands) {
4767     final switch (cast(Command)ctx.commands[i]) {
4768       case Command.MoveTo:
4769         //assert(i+3 <= ctx.ncommands);
4770         static if (asStroke) {
4771           if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4772         }
4773         nvg__addPath(ctx);
4774         const p = &ctx.commands[i+1];
4775         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4776         i += 3;
4777         break;
4778       case Command.LineTo:
4779         //assert(i+3 <= ctx.ncommands);
4780         const p = &ctx.commands[i+1];
4781         nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner);
4782         i += 3;
4783         break;
4784       case Command.BezierTo:
4785         //assert(i+7 <= ctx.ncommands);
4786         const last = nvg__lastPoint(ctx);
4787         if (last !is null) {
4788           const cp1 = &ctx.commands[i+1];
4789           const cp2 = &ctx.commands[i+3];
4790           const p = &ctx.commands[i+5];
4791           if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
4792             nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4793           } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
4794             nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
4795           } else {
4796             nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
4797           }
4798           version(nanovg_bench_flatten) ++bzcount;
4799         }
4800         i += 7;
4801         break;
4802       case Command.Close:
4803         //assert(i+1 <= ctx.ncommands);
4804         nvg__closePath(ctx);
4805         i += 1;
4806         break;
4807       case Command.Winding:
4808         //assert(i+2 <= ctx.ncommands);
4809         nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]);
4810         i += 2;
4811         break;
4812     }
4813   }
4814   static if (asStroke) {
4815     if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx);
4816   }
4817   version(nanovg_bench_flatten) {{
4818     timer.stop();
4819     auto xb = timer.toBuffer(tmbuf[]);
4820     import core.stdc.stdio : printf;
4821     printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount);
4822   }}
4823 
4824   cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max;
4825   cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max;
4826 
4827   // calculate the direction and length of line segments
4828   version(nanovg_bench_flatten) timer.restart();
4829   foreach (int j; 0..cache.npaths) {
4830     NVGpath* path = &cache.paths[j];
4831     NVGpoint* pts = &cache.points[path.first];
4832 
4833     // if the first and last points are the same, remove the last, mark as closed path
4834     NVGpoint* p0 = &pts[path.count-1];
4835     NVGpoint* p1 = &pts[0];
4836     if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) {
4837       --path.count;
4838       p0 = &pts[path.count-1];
4839       path.closed = true;
4840     }
4841 
4842     // enforce winding
4843     if (path.count > 2) {
4844       immutable float area = nvg__polyArea(pts, path.count);
4845       if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count);
4846       if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count);
4847     }
4848 
4849     foreach (immutable _; 0..path.count) {
4850       // calculate segment direction and length
4851       p0.dx = p1.x-p0.x;
4852       p0.dy = p1.y-p0.y;
4853       p0.len = nvg__normalize(&p0.dx, &p0.dy);
4854       // update bounds
4855       cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x);
4856       cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y);
4857       cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x);
4858       cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y);
4859       // advance
4860       p0 = p1++;
4861     }
4862   }
4863   version(nanovg_bench_flatten) {{
4864     timer.stop();
4865     auto xb = timer.toBuffer(tmbuf[]);
4866     import core.stdc.stdio : printf;
4867     printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr);
4868   }}
4869 }
4870 
4871 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc {
4872   immutable float da = nvg__acosf(r/(r+tol))*2.0f;
4873   return nvg__max(2, cast(int)nvg__ceilf(arc/da));
4874 }
4875 
4876 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc {
4877   if (bevel) {
4878     *x0 = p1.x+p0.dy*w;
4879     *y0 = p1.y-p0.dx*w;
4880     *x1 = p1.x+p1.dy*w;
4881     *y1 = p1.y-p1.dx*w;
4882   } else {
4883     *x0 = p1.x+p1.dmx*w;
4884     *y0 = p1.y+p1.dmy*w;
4885     *x1 = p1.x+p1.dmx*w;
4886     *y1 = p1.y+p1.dmy*w;
4887   }
4888 }
4889 
4890 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc {
4891   float dlx0 = p0.dy;
4892   float dly0 = -p0.dx;
4893   float dlx1 = p1.dy;
4894   float dly1 = -p1.dx;
4895   //NVG_NOTUSED(fringe);
4896 
4897   if (p1.flags&PointFlag.Left) {
4898     float lx0 = void, ly0 = void, lx1 = void, ly1 = void;
4899     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4900     immutable float a0 = nvg__atan2f(-dly0, -dlx0);
4901     float a1 = nvg__atan2f(-dly1, -dlx1);
4902     if (a1 > a0) a1 -= NVG_PI*2;
4903 
4904     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4905     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4906 
4907     int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap);
4908     for (int i = 0; i < n; ++i) {
4909       float u = i/cast(float)(n-1);
4910       float a = a0+u*(a1-a0);
4911       float rx = p1.x+nvg__cosf(a)*rw;
4912       float ry = p1.y+nvg__sinf(a)*rw;
4913       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4914       nvg__vset(dst, rx, ry, ru, 1); ++dst;
4915     }
4916 
4917     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4918     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4919 
4920   } else {
4921     float rx0 = void, ry0 = void, rx1 = void, ry1 = void;
4922     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4923     immutable float a0 = nvg__atan2f(dly0, dlx0);
4924     float a1 = nvg__atan2f(dly1, dlx1);
4925     if (a1 < a0) a1 += NVG_PI*2;
4926 
4927     nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst;
4928     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4929 
4930     int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap);
4931     for (int i = 0; i < n; i++) {
4932       float u = i/cast(float)(n-1);
4933       float a = a0+u*(a1-a0);
4934       float lx = p1.x+nvg__cosf(a)*lw;
4935       float ly = p1.y+nvg__sinf(a)*lw;
4936       nvg__vset(dst, lx, ly, lu, 1); ++dst;
4937       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4938     }
4939 
4940     nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst;
4941     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4942 
4943   }
4944   return dst;
4945 }
4946 
4947 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc {
4948   float rx0, ry0, rx1, ry1;
4949   float lx0, ly0, lx1, ly1;
4950   float dlx0 = p0.dy;
4951   float dly0 = -p0.dx;
4952   float dlx1 = p1.dy;
4953   float dly1 = -p1.dx;
4954   //NVG_NOTUSED(fringe);
4955 
4956   if (p1.flags&PointFlag.Left) {
4957     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1);
4958 
4959     nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4960     nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4961 
4962     if (p1.flags&PointFlag.Bevel) {
4963       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
4964       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4965 
4966       nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4967       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4968     } else {
4969       rx0 = p1.x-p1.dmx*rw;
4970       ry0 = p1.y-p1.dmy*rw;
4971 
4972       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4973       nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst;
4974 
4975       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4976       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4977 
4978       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
4979       nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4980     }
4981 
4982     nvg__vset(dst, lx1, ly1, lu, 1); ++dst;
4983     nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst;
4984 
4985   } else {
4986     nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1);
4987 
4988     nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4989     nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4990 
4991     if (p1.flags&PointFlag.Bevel) {
4992       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
4993       nvg__vset(dst, rx0, ry0, ru, 1); ++dst;
4994 
4995       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
4996       nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
4997     } else {
4998       lx0 = p1.x+p1.dmx*lw;
4999       ly0 = p1.y+p1.dmy*lw;
5000 
5001       nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst;
5002       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5003 
5004       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5005       nvg__vset(dst, lx0, ly0, lu, 1); ++dst;
5006 
5007       nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5008       nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst;
5009     }
5010 
5011     nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst;
5012     nvg__vset(dst, rx1, ry1, ru, 1); ++dst;
5013   }
5014 
5015   return dst;
5016 }
5017 
5018 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5019   immutable float px = p.x-dx*d;
5020   immutable float py = p.y-dy*d;
5021   immutable float dlx = dy;
5022   immutable float dly = -dx;
5023   nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst;
5024   nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst;
5025   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5026   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5027   return dst;
5028 }
5029 
5030 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc {
5031   immutable float px = p.x+dx*d;
5032   immutable float py = p.y+dy*d;
5033   immutable float dlx = dy;
5034   immutable float dly = -dx;
5035   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5036   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5037   nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst;
5038   nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst;
5039   return dst;
5040 }
5041 
5042 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5043   immutable float px = p.x;
5044   immutable float py = p.y;
5045   immutable float dlx = dy;
5046   immutable float dly = -dx;
5047   //NVG_NOTUSED(aa);
5048   immutable float ncpf = cast(float)(ncap-1);
5049   foreach (int i; 0..ncap) {
5050     float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI;
5051     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5052     nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst;
5053     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5054   }
5055   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5056   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5057   return dst;
5058 }
5059 
5060 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc {
5061   immutable float px = p.x;
5062   immutable float py = p.y;
5063   immutable float dlx = dy;
5064   immutable float dly = -dx;
5065   //NVG_NOTUSED(aa);
5066   nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst;
5067   nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst;
5068   immutable float ncpf = cast(float)(ncap-1);
5069   foreach (int i; 0..ncap) {
5070     float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI;
5071     float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w;
5072     nvg__vset(dst, px, py, 0.5f, 1); ++dst;
5073     nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst;
5074   }
5075   return dst;
5076 }
5077 
5078 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5079   NVGpathCache* cache = ctx.cache;
5080   float iw = 0.0f;
5081 
5082   if (w > 0.0f) iw = 1.0f/w;
5083 
5084   // Calculate which joins needs extra vertices to append, and gather vertex count.
5085   foreach (int i; 0..cache.npaths) {
5086     NVGpath* path = &cache.paths[i];
5087     NVGpoint* pts = &cache.points[path.first];
5088     NVGpoint* p0 = &pts[path.count-1];
5089     NVGpoint* p1 = &pts[0];
5090     int nleft = 0;
5091 
5092     path.nbevel = 0;
5093 
5094     foreach (int j; 0..path.count) {
5095       //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
5096       immutable float dlx0 = p0.dy;
5097       immutable float dly0 = -p0.dx;
5098       immutable float dlx1 = p1.dy;
5099       immutable float dly1 = -p1.dx;
5100       // Calculate extrusions
5101       p1.dmx = (dlx0+dlx1)*0.5f;
5102       p1.dmy = (dly0+dly1)*0.5f;
5103       immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy;
5104       if (dmr2 > 0.000001f) {
5105         float scale = 1.0f/dmr2;
5106         if (scale > 600.0f) scale = 600.0f;
5107         p1.dmx *= scale;
5108         p1.dmy *= scale;
5109       }
5110 
5111       // Clear flags, but keep the corner.
5112       p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0;
5113 
5114       // Keep track of left turns.
5115       immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy;
5116       if (cross > 0.0f) {
5117         nleft++;
5118         p1.flags |= PointFlag.Left;
5119       }
5120 
5121       // Calculate if we should use bevel or miter for inner join.
5122       immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw);
5123       if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR;
5124 
5125       // Check to see if the corner needs to be beveled.
5126       if (p1.flags&PointFlag.Corner) {
5127         if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) {
5128           p1.flags |= PointFlag.Bevel;
5129         }
5130       }
5131 
5132       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++;
5133 
5134       p0 = p1++;
5135     }
5136 
5137     path.convex = (nleft == path.count);
5138   }
5139 }
5140 
5141 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5142   NVGpathCache* cache = ctx.cache;
5143   immutable float aa = ctx.fringeWidth;
5144   int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle.
5145 
5146   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5147 
5148   // Calculate max vertex usage.
5149   int cverts = 0;
5150   foreach (int i; 0..cache.npaths) {
5151     NVGpath* path = &cache.paths[i];
5152     immutable bool loop = path.closed;
5153     if (lineJoin == NVGLineCap.Round) {
5154       cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop
5155     } else {
5156       cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5157     }
5158     if (!loop) {
5159       // space for caps
5160       if (lineCap == NVGLineCap.Round) {
5161         cverts += (ncap*2+2)*2;
5162       } else {
5163         cverts += (3+3)*2;
5164       }
5165     }
5166   }
5167 
5168   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5169   if (verts is null) return;
5170 
5171   foreach (int i; 0..cache.npaths) {
5172     NVGpath* path = &cache.paths[i];
5173     NVGpoint* pts = &cache.points[path.first];
5174     NVGpoint* p0;
5175     NVGpoint* p1;
5176     int s, e;
5177 
5178     path.fill = null;
5179     path.nfill = 0;
5180 
5181     // Calculate fringe or stroke
5182     immutable bool loop = path.closed;
5183     NVGVertex* dst = verts;
5184     path.stroke = dst;
5185 
5186     if (loop) {
5187       // Looping
5188       p0 = &pts[path.count-1];
5189       p1 = &pts[0];
5190       s = 0;
5191       e = path.count;
5192     } else {
5193       // Add cap
5194       p0 = &pts[0];
5195       p1 = &pts[1];
5196       s = 1;
5197       e = path.count-1;
5198     }
5199 
5200     if (!loop) {
5201       // Add cap
5202       float dx = p1.x-p0.x;
5203       float dy = p1.y-p0.y;
5204       nvg__normalize(&dx, &dy);
5205            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);
5206       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);
5207       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);
5208     }
5209 
5210     foreach (int j; s..e) {
5211       if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5212         if (lineJoin == NVGLineCap.Round) {
5213           dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);
5214         } else {
5215           dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);
5216         }
5217       } else {
5218         nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst;
5219         nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst;
5220       }
5221       p0 = p1++;
5222     }
5223 
5224     if (loop) {
5225       // Loop it
5226       nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst;
5227       nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst;
5228     } else {
5229       // Add cap
5230       float dx = p1.x-p0.x;
5231       float dy = p1.y-p0.y;
5232       nvg__normalize(&dx, &dy);
5233            if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);
5234       else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);
5235       else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);
5236     }
5237 
5238     path.nstroke = cast(int)(dst-verts);
5239 
5240     verts = dst;
5241   }
5242 }
5243 
5244 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc {
5245   NVGpathCache* cache = ctx.cache;
5246   immutable float aa = ctx.fringeWidth;
5247   bool fringe = (w > 0.0f);
5248 
5249   nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
5250 
5251   // Calculate max vertex usage.
5252   int cverts = 0;
5253   foreach (int i; 0..cache.npaths) {
5254     NVGpath* path = &cache.paths[i];
5255     cverts += path.count+path.nbevel+1;
5256     if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop
5257   }
5258 
5259   NVGVertex* verts = nvg__allocTempVerts(ctx, cverts);
5260   if (verts is null) return;
5261 
5262   bool convex = (cache.npaths == 1 && cache.paths[0].convex);
5263 
5264   foreach (int i; 0..cache.npaths) {
5265     NVGpath* path = &cache.paths[i];
5266     NVGpoint* pts = &cache.points[path.first];
5267 
5268     // Calculate shape vertices.
5269     immutable float woff = 0.5f*aa;
5270     NVGVertex* dst = verts;
5271     path.fill = dst;
5272 
5273     if (fringe) {
5274       // Looping
5275       NVGpoint* p0 = &pts[path.count-1];
5276       NVGpoint* p1 = &pts[0];
5277       foreach (int j; 0..path.count) {
5278         if (p1.flags&PointFlag.Bevel) {
5279           immutable float dlx0 = p0.dy;
5280           immutable float dly0 = -p0.dx;
5281           immutable float dlx1 = p1.dy;
5282           immutable float dly1 = -p1.dx;
5283           if (p1.flags&PointFlag.Left) {
5284             immutable float lx = p1.x+p1.dmx*woff;
5285             immutable float ly = p1.y+p1.dmy*woff;
5286             nvg__vset(dst, lx, ly, 0.5f, 1); ++dst;
5287           } else {
5288             immutable float lx0 = p1.x+dlx0*woff;
5289             immutable float ly0 = p1.y+dly0*woff;
5290             immutable float lx1 = p1.x+dlx1*woff;
5291             immutable float ly1 = p1.y+dly1*woff;
5292             nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst;
5293             nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst;
5294           }
5295         } else {
5296           nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst;
5297         }
5298         p0 = p1++;
5299       }
5300     } else {
5301       foreach (int j; 0..path.count) {
5302         nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1);
5303         ++dst;
5304       }
5305     }
5306 
5307     path.nfill = cast(int)(dst-verts);
5308     verts = dst;
5309 
5310     // Calculate fringe
5311     if (fringe) {
5312       float lw = w+woff;
5313       immutable float rw = w-woff;
5314       float lu = 0;
5315       immutable float ru = 1;
5316       dst = verts;
5317       path.stroke = dst;
5318 
5319       // Create only half a fringe for convex shapes so that
5320       // the shape can be rendered without stenciling.
5321       if (convex) {
5322         lw = woff; // This should generate the same vertex as fill inset above.
5323         lu = 0.5f; // Set outline fade at middle.
5324       }
5325 
5326       // Looping
5327       NVGpoint* p0 = &pts[path.count-1];
5328       NVGpoint* p1 = &pts[0];
5329 
5330       foreach (int j; 0..path.count) {
5331         if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) {
5332           dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth);
5333         } else {
5334           nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst;
5335           nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst;
5336         }
5337         p0 = p1++;
5338       }
5339 
5340       // Loop it
5341       nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst;
5342       nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst;
5343 
5344       path.nstroke = cast(int)(dst-verts);
5345       verts = dst;
5346     } else {
5347       path.stroke = null;
5348       path.nstroke = 0;
5349     }
5350   }
5351 }
5352 
5353 
5354 // ////////////////////////////////////////////////////////////////////////// //
5355 // Paths
5356 
5357 /// Clears the current path and sub-paths.
5358 /// Group: paths
5359 @scriptable
5360 public void beginPath (NVGContext ctx) nothrow @trusted @nogc {
5361   ctx.ncommands = 0;
5362   ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags
5363   nvg__clearPathCache(ctx);
5364 }
5365 
5366 public alias newPath = beginPath; /// Ditto.
5367 
5368 /// Starts new sub-path with specified point as first point.
5369 /// Group: paths
5370 @scriptable
5371 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5372   nvg__appendCommands(ctx, Command.MoveTo, x, y);
5373 }
5374 
5375 /// Starts new sub-path with specified point as first point.
5376 /// Arguments: [x, y]*
5377 /// Group: paths
5378 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5379   enum ArgC = 2;
5380   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call");
5381   if (args.length < ArgC) return;
5382   nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]);
5383 }
5384 
5385 /// Adds line segment from the last point in the path to the specified point.
5386 /// Group: paths
5387 @scriptable
5388 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
5389   nvg__appendCommands(ctx, Command.LineTo, x, y);
5390 }
5391 
5392 /// Adds line segment from the last point in the path to the specified point.
5393 /// Arguments: [x, y]*
5394 /// Group: paths
5395 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5396   enum ArgC = 2;
5397   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call");
5398   if (args.length < ArgC) return;
5399   foreach (immutable idx; 0..args.length/ArgC) {
5400     nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5401   }
5402 }
5403 
5404 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5405 /// Group: paths
5406 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 {
5407   nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y);
5408 }
5409 
5410 /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
5411 /// Arguments: [c1x, c1y, c2x, c2y, x, y]*
5412 /// Group: paths
5413 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5414   enum ArgC = 6;
5415   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call");
5416   if (args.length < ArgC) return;
5417   foreach (immutable idx; 0..args.length/ArgC) {
5418     nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]);
5419   }
5420 }
5421 
5422 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5423 /// Group: paths
5424 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc {
5425   immutable float x0 = ctx.commandx;
5426   immutable float y0 = ctx.commandy;
5427   nvg__appendCommands(ctx,
5428     Command.BezierTo,
5429     x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5430     x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5431     x, y,
5432   );
5433 }
5434 
5435 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
5436 /// Arguments: [cx, cy, x, y]*
5437 /// Group: paths
5438 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5439   enum ArgC = 4;
5440   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call");
5441   if (args.length < ArgC) return;
5442   const(float)* aptr = args.ptr;
5443   foreach (immutable idx; 0..args.length/ArgC) {
5444     immutable float x0 = ctx.commandx;
5445     immutable float y0 = ctx.commandy;
5446     immutable float cx = *aptr++;
5447     immutable float cy = *aptr++;
5448     immutable float x = *aptr++;
5449     immutable float y = *aptr++;
5450     nvg__appendCommands(ctx,
5451       Command.BezierTo,
5452       x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
5453       x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
5454       x, y,
5455     );
5456   }
5457 }
5458 
5459 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5460 /// Group: paths
5461 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc {
5462   if (ctx.ncommands == 0) return;
5463 
5464   immutable float x0 = ctx.commandx;
5465   immutable float y0 = ctx.commandy;
5466 
5467   // handle degenerate cases
5468   if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) ||
5469       nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) ||
5470       nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol ||
5471       radius < ctx.distTol)
5472   {
5473     ctx.lineTo(x1, y1);
5474     return;
5475   }
5476 
5477   // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2)
5478   float dx0 = x0-x1;
5479   float dy0 = y0-y1;
5480   float dx1 = x2-x1;
5481   float dy1 = y2-y1;
5482   nvg__normalize(&dx0, &dy0);
5483   nvg__normalize(&dx1, &dy1);
5484   immutable float a = nvg__acosf(dx0*dx1+dy0*dy1);
5485   immutable float d = radius/nvg__tanf(a/2.0f);
5486 
5487   //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
5488 
5489   if (d > 10000.0f) {
5490     ctx.lineTo(x1, y1);
5491     return;
5492   }
5493 
5494   float cx = void, cy = void, a0 = void, a1 = void;
5495   NVGWinding dir;
5496   if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) {
5497     cx = x1+dx0*d+dy0*radius;
5498     cy = y1+dy0*d+-dx0*radius;
5499     a0 = nvg__atan2f(dx0, -dy0);
5500     a1 = nvg__atan2f(-dx1, dy1);
5501     dir = NVGWinding.CW;
5502     //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5503   } else {
5504     cx = x1+dx0*d+-dy0*radius;
5505     cy = y1+dy0*d+dx0*radius;
5506     a0 = nvg__atan2f(-dx0, dy0);
5507     a1 = nvg__atan2f(dx1, -dy1);
5508     dir = NVGWinding.CCW;
5509     //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
5510   }
5511 
5512   ctx.arc(dir, cx, cy, radius, a0, a1); // first is line
5513 }
5514 
5515 
5516 /// Adds an arc segment at the corner defined by the last path point, and two specified points.
5517 /// Arguments: [x1, y1, x2, y2, radius]*
5518 /// Group: paths
5519 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5520   enum ArgC = 5;
5521   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call");
5522   if (args.length < ArgC) return;
5523   if (ctx.ncommands == 0) return;
5524   const(float)* aptr = args.ptr;
5525   foreach (immutable idx; 0..args.length/ArgC) {
5526     immutable float x0 = ctx.commandx;
5527     immutable float y0 = ctx.commandy;
5528     immutable float x1 = *aptr++;
5529     immutable float y1 = *aptr++;
5530     immutable float x2 = *aptr++;
5531     immutable float y2 = *aptr++;
5532     immutable float radius = *aptr++;
5533     ctx.arcTo(x1, y1, x2, y2, radius);
5534   }
5535 }
5536 
5537 /// Closes current sub-path with a line segment.
5538 /// Group: paths
5539 @scriptable
5540 public void closePath (NVGContext ctx) nothrow @trusted @nogc {
5541   nvg__appendCommands(ctx, Command.Close);
5542 }
5543 
5544 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity.
5545 /// Group: paths
5546 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc {
5547   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5548 }
5549 
5550 /// Ditto.
5551 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc {
5552   nvg__appendCommands(ctx, Command.Winding, cast(float)dir);
5553 }
5554 
5555 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5556  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5557  * Angles are specified in radians.
5558  *
5559  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5560  *
5561  * Group: paths
5562  */
5563 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 {
5564   static assert(mode == "original" || mode == "move" || mode == "line");
5565 
5566   float[3+5*7+100] vals = void;
5567   //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5568   static if (mode == "original") {
5569     immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo);
5570   } else static if (mode == "move") {
5571     enum move = Command.MoveTo;
5572   } else static if (mode == "line") {
5573     enum move = Command.LineTo;
5574   } else {
5575     static assert(0, "wtf?!");
5576   }
5577 
5578   // Clamp angles
5579   float da = a1-a0;
5580   if (dir == NVGWinding.CW) {
5581     if (nvg__absf(da) >= NVG_PI*2) {
5582       da = NVG_PI*2;
5583     } else {
5584       while (da < 0.0f) da += NVG_PI*2;
5585     }
5586   } else {
5587     if (nvg__absf(da) >= NVG_PI*2) {
5588       da = -NVG_PI*2;
5589     } else {
5590       while (da > 0.0f) da -= NVG_PI*2;
5591     }
5592   }
5593 
5594   // Split arc into max 90 degree segments.
5595   immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5));
5596   immutable float hda = (da/cast(float)ndivs)/2.0f;
5597   float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda));
5598 
5599   if (dir == NVGWinding.CCW) kappa = -kappa;
5600 
5601   int nvals = 0;
5602   float px = 0, py = 0, ptanx = 0, ptany = 0;
5603   foreach (int i; 0..ndivs+1) {
5604     immutable float a = a0+da*(i/cast(float)ndivs);
5605     immutable float dx = nvg__cosf(a);
5606     immutable float dy = nvg__sinf(a);
5607     immutable float x = cx+dx*r;
5608     immutable float y = cy+dy*r;
5609     immutable float tanx = -dy*r*kappa;
5610     immutable float tany = dx*r*kappa;
5611 
5612     if (i == 0) {
5613       if (vals.length-nvals < 3) {
5614         // flush
5615         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5616         nvals = 0;
5617       }
5618       vals.ptr[nvals++] = cast(float)move;
5619       vals.ptr[nvals++] = x;
5620       vals.ptr[nvals++] = y;
5621     } else {
5622       if (vals.length-nvals < 7) {
5623         // flush
5624         nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5625         nvals = 0;
5626       }
5627       vals.ptr[nvals++] = Command.BezierTo;
5628       vals.ptr[nvals++] = px+ptanx;
5629       vals.ptr[nvals++] = py+ptany;
5630       vals.ptr[nvals++] = x-tanx;
5631       vals.ptr[nvals++] = y-tany;
5632       vals.ptr[nvals++] = x;
5633       vals.ptr[nvals++] = y;
5634     }
5635     px = x;
5636     py = y;
5637     ptanx = tanx;
5638     ptany = tany;
5639   }
5640 
5641   nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command
5642 }
5643 
5644 
5645 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r,
5646  * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW).
5647  * Angles are specified in radians.
5648  *
5649  * Arguments: [cx, cy, r, a0, a1]*
5650  *
5651  * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo
5652  *
5653  * Group: paths
5654  */
5655 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc {
5656   static assert(mode == "original" || mode == "move" || mode == "line");
5657   enum ArgC = 5;
5658   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call");
5659   if (args.length < ArgC) return;
5660   const(float)* aptr = args.ptr;
5661   foreach (immutable idx; 0..args.length/ArgC) {
5662     immutable cx = *aptr++;
5663     immutable cy = *aptr++;
5664     immutable r = *aptr++;
5665     immutable a0 = *aptr++;
5666     immutable a1 = *aptr++;
5667     ctx.arc!mode(dir, cx, cy, r, a0, a1);
5668   }
5669 }
5670 
5671 /// Creates new rectangle shaped sub-path.
5672 /// Group: paths
5673 @scriptable
5674 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc {
5675   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5676     Command.MoveTo, x, y,
5677     Command.LineTo, x, y+h,
5678     Command.LineTo, x+w, y+h,
5679     Command.LineTo, x+w, y,
5680     Command.Close,
5681   );
5682 }
5683 
5684 /// Creates new rectangle shaped sub-path.
5685 /// Arguments: [x, y, w, h]*
5686 /// Group: paths
5687 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5688   enum ArgC = 4;
5689   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call");
5690   if (args.length < ArgC) return;
5691   const(float)* aptr = args.ptr;
5692   foreach (immutable idx; 0..args.length/ArgC) {
5693     immutable x = *aptr++;
5694     immutable y = *aptr++;
5695     immutable w = *aptr++;
5696     immutable h = *aptr++;
5697     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5698       Command.MoveTo, x, y,
5699       Command.LineTo, x, y+h,
5700       Command.LineTo, x+w, y+h,
5701       Command.LineTo, x+w, y,
5702       Command.Close,
5703     );
5704   }
5705 }
5706 
5707 /// Creates new rounded rectangle shaped sub-path.
5708 /// Group: paths
5709 @scriptable
5710 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc {
5711   ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius);
5712 }
5713 
5714 /// Creates new rounded rectangle shaped sub-path.
5715 /// Arguments: [x, y, w, h, radius]*
5716 /// Group: paths
5717 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5718   enum ArgC = 5;
5719   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call");
5720   if (args.length < ArgC) return;
5721   const(float)* aptr = args.ptr;
5722   foreach (immutable idx; 0..args.length/ArgC) {
5723     immutable x = *aptr++;
5724     immutable y = *aptr++;
5725     immutable w = *aptr++;
5726     immutable h = *aptr++;
5727     immutable r = *aptr++;
5728     ctx.roundedRectVarying(x, y, w, h, r, r, r, r);
5729   }
5730 }
5731 
5732 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5733 /// Group: paths
5734 @scriptable
5735 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 {
5736   if (rw < 0.1f || rh < 0.1f) {
5737     rect(ctx, x, y, w, h);
5738   } else {
5739     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5740       Command.MoveTo, x+rw, y,
5741       Command.LineTo, x+w-rw, y,
5742       Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5743       Command.LineTo, x+w, y+h-rh,
5744       Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5745       Command.LineTo, x+rw, y+h,
5746       Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5747       Command.LineTo, x, y+rh,
5748       Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5749       Command.Close,
5750     );
5751   }
5752 }
5753 
5754 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it.
5755 /// Arguments: [x, y, w, h, rw, rh]*
5756 /// Group: paths
5757 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5758   enum ArgC = 6;
5759   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call");
5760   if (args.length < ArgC) return;
5761   const(float)* aptr = args.ptr;
5762   foreach (immutable idx; 0..args.length/ArgC) {
5763     immutable x = *aptr++;
5764     immutable y = *aptr++;
5765     immutable w = *aptr++;
5766     immutable h = *aptr++;
5767     immutable rw = *aptr++;
5768     immutable rh = *aptr++;
5769     if (rw < 0.1f || rh < 0.1f) {
5770       rect(ctx, x, y, w, h);
5771     } else {
5772       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5773         Command.MoveTo, x+rw, y,
5774         Command.LineTo, x+w-rw, y,
5775         Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh,
5776         Command.LineTo, x+w, y+h-rh,
5777         Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h,
5778         Command.LineTo, x+rw, y+h,
5779         Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh,
5780         Command.LineTo, x, y+rh,
5781         Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y,
5782         Command.Close,
5783       );
5784     }
5785   }
5786 }
5787 
5788 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5789 /// Group: paths
5790 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 {
5791   if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5792     ctx.rect(x, y, w, h);
5793   } else {
5794     immutable float halfw = nvg__absf(w)*0.5f;
5795     immutable float halfh = nvg__absf(h)*0.5f;
5796     immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5797     immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5798     immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5799     immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5800     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5801       Command.MoveTo, x, y+ryTL,
5802       Command.LineTo, x, y+h-ryBL,
5803       Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5804       Command.LineTo, x+w-rxBR, y+h,
5805       Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5806       Command.LineTo, x+w, y+ryTR,
5807       Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5808       Command.LineTo, x+rxTL, y,
5809       Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5810       Command.Close,
5811     );
5812   }
5813 }
5814 
5815 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner.
5816 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]*
5817 /// Group: paths
5818 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5819   enum ArgC = 8;
5820   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call");
5821   if (args.length < ArgC) return;
5822   const(float)* aptr = args.ptr;
5823   foreach (immutable idx; 0..args.length/ArgC) {
5824     immutable x = *aptr++;
5825     immutable y = *aptr++;
5826     immutable w = *aptr++;
5827     immutable h = *aptr++;
5828     immutable radTopLeft = *aptr++;
5829     immutable radTopRight = *aptr++;
5830     immutable radBottomRight = *aptr++;
5831     immutable radBottomLeft = *aptr++;
5832     if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
5833       ctx.rect(x, y, w, h);
5834     } else {
5835       immutable float halfw = nvg__absf(w)*0.5f;
5836       immutable float halfh = nvg__absf(h)*0.5f;
5837       immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h);
5838       immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h);
5839       immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h);
5840       immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h);
5841       nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5842         Command.MoveTo, x, y+ryTL,
5843         Command.LineTo, x, y+h-ryBL,
5844         Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h,
5845         Command.LineTo, x+w-rxBR, y+h,
5846         Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR,
5847         Command.LineTo, x+w, y+ryTR,
5848         Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y,
5849         Command.LineTo, x+rxTL, y,
5850         Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL,
5851         Command.Close,
5852       );
5853     }
5854   }
5855 }
5856 
5857 /// Creates new ellipse shaped sub-path.
5858 /// Group: paths
5859 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc {
5860   nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5861     Command.MoveTo, cx-rx, cy,
5862     Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5863     Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5864     Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5865     Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5866     Command.Close,
5867   );
5868 }
5869 
5870 /// Creates new ellipse shaped sub-path.
5871 /// Arguments: [cx, cy, rx, ry]*
5872 /// Group: paths
5873 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5874   enum ArgC = 4;
5875   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call");
5876   if (args.length < ArgC) return;
5877   const(float)* aptr = args.ptr;
5878   foreach (immutable idx; 0..args.length/ArgC) {
5879     immutable cx = *aptr++;
5880     immutable cy = *aptr++;
5881     immutable rx = *aptr++;
5882     immutable ry = *aptr++;
5883     nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command
5884       Command.MoveTo, cx-rx, cy,
5885       Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
5886       Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
5887       Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
5888       Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
5889       Command.Close,
5890     );
5891   }
5892 }
5893 
5894 /// Creates new circle shaped sub-path.
5895 /// Group: paths
5896 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc {
5897   ctx.ellipse(cx, cy, r, r);
5898 }
5899 
5900 /// Creates new circle shaped sub-path.
5901 /// Arguments: [cx, cy, r]*
5902 /// Group: paths
5903 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc {
5904   enum ArgC = 3;
5905   if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call");
5906   if (args.length < ArgC) return;
5907   const(float)* aptr = args.ptr;
5908   foreach (immutable idx; 0..args.length/ArgC) {
5909     immutable cx = *aptr++;
5910     immutable cy = *aptr++;
5911     immutable r = *aptr++;
5912     ctx.ellipse(cx, cy, r, r);
5913   }
5914 }
5915 
5916 // Debug function to dump cached path data.
5917 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc {
5918   import core.stdc.stdio : printf;
5919   const(NVGpath)* path;
5920   printf("Dumping %d cached paths\n", ctx.cache.npaths);
5921   for (int i = 0; i < ctx.cache.npaths; ++i) {
5922     path = &ctx.cache.paths[i];
5923     printf("-Path %d\n", i);
5924     if (path.nfill) {
5925       printf("-fill: %d\n", path.nfill);
5926       for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y);
5927     }
5928     if (path.nstroke) {
5929       printf("-stroke: %d\n", path.nstroke);
5930       for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y);
5931     }
5932   }
5933 }
5934 
5935 // Flatten path, prepare it for fill operation.
5936 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc {
5937   NVGpathCache* cache = ctx.cache;
5938   NVGstate* state = nvg__getState(ctx);
5939 
5940   nvg__flattenPaths!false(ctx);
5941 
5942   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5943     nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f);
5944   } else {
5945     nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f);
5946   }
5947 
5948   cache.evenOddMode = state.evenOddMode;
5949   cache.fringeWidth = ctx.fringeWidth;
5950   cache.fillReady = true;
5951   cache.strokeReady = false;
5952   cache.clipmode = NVGClipMode.None;
5953 }
5954 
5955 // Flatten path, prepare it for stroke operation.
5956 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc {
5957   NVGstate* state = nvg__getState(ctx);
5958   NVGpathCache* cache = ctx.cache;
5959 
5960   nvg__flattenPaths!true(ctx);
5961 
5962   immutable float scale = nvg__getAverageScale(state.xform);
5963   float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f);
5964 
5965   if (strokeWidth < ctx.fringeWidth) {
5966     // If the stroke width is less than pixel size, use alpha to emulate coverage.
5967     // Since coverage is area, scale by alpha*alpha.
5968     immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f);
5969     cache.strokeAlphaMul = alpha*alpha;
5970     strokeWidth = ctx.fringeWidth;
5971   } else {
5972     cache.strokeAlphaMul = 1.0f;
5973   }
5974   cache.strokeWidth = strokeWidth;
5975 
5976   if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) {
5977     nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5978   } else {
5979     nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit);
5980   }
5981 
5982   cache.fringeWidth = ctx.fringeWidth;
5983   cache.fillReady = false;
5984   cache.strokeReady = true;
5985   cache.clipmode = NVGClipMode.None;
5986 }
5987 
5988 /// Fills the current path with current fill style.
5989 /// Group: paths
5990 @scriptable
5991 public void fill (NVGContext ctx) nothrow @trusted @nogc {
5992   NVGstate* state = nvg__getState(ctx);
5993 
5994   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
5995     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
5996     ctx.currFillHitId = ctx.pathPickId;
5997   }
5998 
5999   nvg__prepareFill(ctx);
6000 
6001   // apply global alpha
6002   NVGPaint fillPaint = state.fill;
6003   fillPaint.innerColor.a *= state.alpha;
6004   fillPaint.middleColor.a *= state.alpha;
6005   fillPaint.outerColor.a *= state.alpha;
6006 
6007   ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6008 
6009   if (ctx.recblockdraw) return;
6010 
6011   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);
6012 
6013   // count triangles
6014   foreach (int i; 0..ctx.cache.npaths) {
6015     NVGpath* path = &ctx.cache.paths[i];
6016     ctx.fillTriCount += path.nfill-2;
6017     ctx.fillTriCount += path.nstroke-2;
6018     ctx.drawCallCount += 2;
6019   }
6020 }
6021 
6022 /// Fills the current path with current stroke style.
6023 /// Group: paths
6024 @scriptable
6025 public void stroke (NVGContext ctx) nothrow @trusted @nogc {
6026   NVGstate* state = nvg__getState(ctx);
6027 
6028   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6029     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6030     ctx.currStrokeHitId = ctx.pathPickId;
6031   }
6032 
6033   nvg__prepareStroke(ctx);
6034 
6035   NVGpathCache* cache = ctx.cache;
6036 
6037   NVGPaint strokePaint = state.stroke;
6038   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6039   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6040   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6041 
6042   // apply global alpha
6043   strokePaint.innerColor.a *= state.alpha;
6044   strokePaint.middleColor.a *= state.alpha;
6045   strokePaint.outerColor.a *= state.alpha;
6046 
6047   ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6048 
6049   if (ctx.recblockdraw) return;
6050 
6051   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6052 
6053   // count triangles
6054   foreach (int i; 0..ctx.cache.npaths) {
6055     NVGpath* path = &ctx.cache.paths[i];
6056     ctx.strokeTriCount += path.nstroke-2;
6057     ++ctx.drawCallCount;
6058   }
6059 }
6060 
6061 /// Sets current path as clipping region.
6062 /// Group: clipping
6063 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6064   NVGstate* state = nvg__getState(ctx);
6065 
6066   if (aclipmode == NVGClipMode.None) return;
6067   if (ctx.recblockdraw) return; //???
6068 
6069   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6070 
6071   /*
6072   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) {
6073     ctx.pathPickRegistered |= NVGPickKind.Fill<<16;
6074     ctx.currFillHitId = ctx.pathPickId;
6075   }
6076   */
6077 
6078   nvg__prepareFill(ctx);
6079 
6080   // apply global alpha
6081   NVGPaint fillPaint = state.fill;
6082   fillPaint.innerColor.a *= state.alpha;
6083   fillPaint.middleColor.a *= state.alpha;
6084   fillPaint.outerColor.a *= state.alpha;
6085 
6086   //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
6087 
6088   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);
6089 
6090   // count triangles
6091   foreach (int i; 0..ctx.cache.npaths) {
6092     NVGpath* path = &ctx.cache.paths[i];
6093     ctx.fillTriCount += path.nfill-2;
6094     ctx.fillTriCount += path.nstroke-2;
6095     ctx.drawCallCount += 2;
6096   }
6097 }
6098 
6099 /// Sets current path as clipping region.
6100 /// Group: clipping
6101 public alias clipFill = clip;
6102 
6103 /// Sets current path' stroke as clipping region.
6104 /// Group: clipping
6105 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc {
6106   NVGstate* state = nvg__getState(ctx);
6107 
6108   if (aclipmode == NVGClipMode.None) return;
6109   if (ctx.recblockdraw) return; //???
6110 
6111   if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr);
6112 
6113   /*
6114   if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) {
6115     ctx.pathPickRegistered |= NVGPickKind.Stroke<<16;
6116     ctx.currStrokeHitId = ctx.pathPickId;
6117   }
6118   */
6119 
6120   nvg__prepareStroke(ctx);
6121 
6122   NVGpathCache* cache = ctx.cache;
6123 
6124   NVGPaint strokePaint = state.stroke;
6125   strokePaint.innerColor.a *= cache.strokeAlphaMul;
6126   strokePaint.middleColor.a *= cache.strokeAlphaMul;
6127   strokePaint.outerColor.a *= cache.strokeAlphaMul;
6128 
6129   // apply global alpha
6130   strokePaint.innerColor.a *= state.alpha;
6131   strokePaint.middleColor.a *= state.alpha;
6132   strokePaint.outerColor.a *= state.alpha;
6133 
6134   //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
6135 
6136   ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths);
6137 
6138   // count triangles
6139   foreach (int i; 0..ctx.cache.npaths) {
6140     NVGpath* path = &ctx.cache.paths[i];
6141     ctx.strokeTriCount += path.nstroke-2;
6142     ++ctx.drawCallCount;
6143   }
6144 }
6145 
6146 
6147 // ////////////////////////////////////////////////////////////////////////// //
6148 // Picking API
6149 
6150 // most of the code is by Michael Wynne <mike@mikesspace.net>
6151 // https://github.com/memononen/nanovg/pull/230
6152 // https://github.com/MikeWW/nanovg
6153 
6154 /// Pick type query. Used in [hitTest] and [hitTestAll].
6155 /// Group: picking_api
6156 public enum NVGPickKind : ubyte {
6157   Fill = 0x01, ///
6158   Stroke = 0x02, ///
6159   All = 0x03, ///
6160 }
6161 
6162 /// Marks the fill of the current path as pickable with the specified id.
6163 /// Note that you can create and mark path without rasterizing it.
6164 /// Group: picking_api
6165 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6166   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6167   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false);
6168   nvg__pickSceneInsert(ps, pp);
6169 }
6170 
6171 public alias currFillPickId = currFillHitId; /// Ditto.
6172 
6173 /// Marks the stroke of the current path as pickable with the specified id.
6174 /// Note that you can create and mark path without rasterizing it.
6175 /// Group: picking_api
6176 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc {
6177   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6178   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true);
6179   nvg__pickSceneInsert(ps, pp);
6180 }
6181 
6182 public alias currStrokePickId = currStrokeHitId; /// Ditto.
6183 
6184 // Marks the saved path set (fill) as pickable with the specified id.
6185 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6186 // Group: picking_api
6187 /+
6188 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6189   if (svp is null) return;
6190   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6191   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6192     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6193     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false);
6194     nvg__pickSceneInsert(ps, pp);
6195   }
6196 }
6197 +/
6198 
6199 // Marks the saved path set (stroke) as pickable with the specified id.
6200 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!)
6201 // Group: picking_api
6202 /+
6203 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc {
6204   if (svp is null) return;
6205   if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context");
6206   foreach (ref cp; svp.caches[0..svp.ncaches]) {
6207     NVGpickScene* ps = nvg__pickSceneGet(ctx);
6208     NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true);
6209     nvg__pickSceneInsert(ps, pp);
6210   }
6211 }
6212 +/
6213 
6214 private template IsGoodHitTestDG(DG) {
6215   enum IsGoodHitTestDG =
6216     __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) ||
6217     __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); });
6218 }
6219 
6220 private template IsGoodHitTestInternalDG(DG) {
6221   enum IsGoodHitTestInternalDG =
6222     __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) ||
6223     __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); });
6224 }
6225 
6226 /// Call delegate [dg] for each path under the specified position (in no particular order).
6227 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick].
6228 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending).
6229 /// Group: picking_api
6230 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) {
6231   if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1;
6232 
6233   NVGpickScene* ps = ctx.pickScene;
6234   int levelwidth = 1<<(ps.nlevels-1);
6235   int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth);
6236   int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth);
6237   int npicked = 0;
6238 
6239   // if we are interested only in most-toplevel path, there is no reason to check paths with worser order.
6240   // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds
6241   // checking, so path walking order is not guaranteed.
6242   static if (bestOrder) {
6243     int lastBestOrder = int.min;
6244   }
6245 
6246   //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); }
6247   for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) {
6248     for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) {
6249       //{ 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); }
6250       static if (bestOrder) {
6251         // reject earlier paths
6252         if (pp.order <= lastBestOrder) continue; // not interesting
6253       }
6254       immutable uint kpx = kind&pp.flags&3;
6255       if (kpx == 0) continue; // not interesting
6256       if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting
6257       //{ import core.stdc.stdio; printf("in bounds!\n"); }
6258       int hit = 0;
6259       if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y);
6260       if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y);
6261       if (!hit) continue;
6262       //{ import core.stdc.stdio; printf("  HIT!\n"); }
6263       static if (bestOrder) lastBestOrder = pp.order;
6264       static if (IsGoodHitTestDG!DG) {
6265         static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) {
6266           if (dg(pp.id, cast(int)pp.order)) return pp.id;
6267         } else {
6268           dg(pp.id, cast(int)pp.order);
6269         }
6270       } else {
6271         static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) {
6272           if (dg(pp)) return pp.id;
6273         } else {
6274           dg(pp);
6275         }
6276       }
6277     }
6278     cellx >>= 1;
6279     celly >>= 1;
6280     levelwidth >>= 1;
6281   }
6282 
6283   return -1;
6284 }
6285 
6286 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position.
6287 /// Returns the slice of [ids].
6288 /// Group: picking_api
6289 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc {
6290   if (ctx.pickScene is null || ids.length == 0) return ids[0..0];
6291 
6292   int npicked = 0;
6293   NVGpickScene* ps = ctx.pickScene;
6294 
6295   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc {
6296     if (npicked == ps.cpicked) {
6297       int cpicked = ps.cpicked+ps.cpicked;
6298       NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked);
6299       if (picked is null) return true; // abort
6300       ps.cpicked = cpicked;
6301       ps.picked = picked;
6302     }
6303     ps.picked[npicked] = pp;
6304     ++npicked;
6305     return false; // go on
6306   });
6307 
6308   qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths);
6309 
6310   assert(npicked >= 0);
6311   if (npicked > ids.length) npicked = cast(int)ids.length;
6312   foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id;
6313 
6314   return ids[0..npicked];
6315 }
6316 
6317 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found.
6318 /// Group: picking_api
6319 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6320   if (ctx.pickScene is null) return -1;
6321 
6322   int bestOrder = int.min;
6323   int bestID = -1;
6324 
6325   ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) {
6326     if (pp.order > bestOrder) {
6327       bestOrder = pp.order;
6328       bestID = pp.id;
6329     }
6330   });
6331 
6332   return bestID;
6333 }
6334 
6335 /// Returns `true` if the path with the given id contains x,y.
6336 /// Group: picking_api
6337 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc {
6338   if (ctx.pickScene is null || id == NVGNoPick) return false;
6339 
6340   bool res = false;
6341 
6342   ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) {
6343     if (pp.id == id) {
6344       res = true;
6345       return true; // stop
6346     }
6347     return false; // continue
6348   });
6349 
6350   return res;
6351 }
6352 
6353 /// Returns `true` if the given point is within the fill of the currently defined path.
6354 /// This operation can be done before rasterizing the current path.
6355 /// Group: picking_api
6356 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6357   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6358   int oldnpoints = ps.npoints;
6359   int oldnsegments = ps.nsegments;
6360   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false);
6361   if (pp is null) return false; // oops
6362   scope(exit) {
6363     nvg__freePickPath(ps, pp);
6364     ps.npoints = oldnpoints;
6365     ps.nsegments = oldnsegments;
6366   }
6367   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false);
6368 }
6369 
6370 public alias isPointInPath = hitTestCurrFill; /// Ditto.
6371 
6372 /// Returns `true` if the given point is within the stroke of the currently defined path.
6373 /// This operation can be done before rasterizing the current path.
6374 /// Group: picking_api
6375 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc {
6376   NVGpickScene* ps = nvg__pickSceneGet(ctx);
6377   int oldnpoints = ps.npoints;
6378   int oldnsegments = ps.nsegments;
6379   NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true);
6380   if (pp is null) return false; // oops
6381   scope(exit) {
6382     nvg__freePickPath(ps, pp);
6383     ps.npoints = oldnpoints;
6384     ps.nsegments = oldnsegments;
6385   }
6386   return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false);
6387 }
6388 
6389 
6390 nothrow @trusted @nogc {
6391 extern(C) {
6392   private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc;
6393   private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
6394 
6395   extern(C) int nvg__comparePaths (const void* a, const void* b) {
6396     return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order;
6397   }
6398 }
6399 
6400 enum NVGPickEPS = 0.0001f;
6401 
6402 // Segment flags
6403 enum NVGSegmentFlags {
6404   Corner = 1,
6405   Bevel = 2,
6406   InnerBevel = 4,
6407   Cap = 8,
6408   Endcap = 16,
6409 }
6410 
6411 // Path flags
6412 enum NVGPathFlags : ushort {
6413   Fill = NVGPickKind.Fill,
6414   Stroke = NVGPickKind.Stroke,
6415   Scissor = 0x80,
6416 }
6417 
6418 struct NVGsegment {
6419   int firstPoint; // Index into NVGpickScene.points
6420   short type; // NVG_LINETO or NVG_BEZIERTO
6421   short flags; // Flags relate to the corner between the prev segment and this one.
6422   float[4] bounds;
6423   float[2] startDir; // Direction at t == 0
6424   float[2] endDir; // Direction at t == 1
6425   float[2] miterDir; // Direction of miter of corner between the prev segment and this one.
6426 }
6427 
6428 struct NVGpickSubPath {
6429   short winding; // TODO: Merge to flag field
6430   bool closed; // TODO: Merge to flag field
6431 
6432   int firstSegment; // Index into NVGpickScene.segments
6433   int nsegments;
6434 
6435   float[4] bounds;
6436 
6437   NVGpickSubPath* next;
6438 }
6439 
6440 struct NVGpickPath {
6441   int id;
6442   short flags;
6443   short order;
6444   float strokeWidth;
6445   float miterLimit;
6446   short lineCap;
6447   short lineJoin;
6448   bool evenOddMode;
6449 
6450   float[4] bounds;
6451   int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center
6452 
6453   NVGpickSubPath* subPaths;
6454   NVGpickPath* next;
6455   NVGpickPath* cellnext;
6456 }
6457 
6458 struct NVGpickScene {
6459   int npaths;
6460 
6461   NVGpickPath* paths; // Linked list of paths
6462   NVGpickPath* lastPath; // The last path in the paths linked list (the first path added)
6463   NVGpickPath* freePaths; // Linked list of free paths
6464 
6465   NVGpickSubPath* freeSubPaths; // Linked list of free sub paths
6466 
6467   int width;
6468   int height;
6469 
6470   // Points for all path sub paths.
6471   float* points;
6472   int npoints;
6473   int cpoints;
6474 
6475   // Segments for all path sub paths
6476   NVGsegment* segments;
6477   int nsegments;
6478   int csegments;
6479 
6480   // Implicit quadtree
6481   float xdim; // Width / (1 << nlevels)
6482   float ydim; // Height / (1 << nlevels)
6483   int ncells; // Total number of cells in all levels
6484   int nlevels;
6485   NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths
6486 
6487   // Temp storage for picking
6488   int cpicked;
6489   NVGpickPath** picked;
6490 }
6491 
6492 
6493 // bounds utilities
6494 void nvg__initBounds (ref float[4] bounds) {
6495   bounds.ptr[0] = bounds.ptr[1] = float.max;
6496   bounds.ptr[2] = bounds.ptr[3] = -float.max;
6497 }
6498 
6499 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) {
6500   npoints *= 2;
6501   for (int i = 0; i < npoints; i += 2) {
6502     bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]);
6503     bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]);
6504     bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]);
6505     bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]);
6506   }
6507 }
6508 
6509 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) {
6510   bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]);
6511   bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]);
6512   bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]);
6513   bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]);
6514 }
6515 
6516 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) {
6517   bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]);
6518   bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]);
6519   bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]);
6520   bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]);
6521 
6522   bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]);
6523   bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]);
6524 }
6525 
6526 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) {
6527   pragma(inline, true);
6528   return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]);
6529 }
6530 
6531 // building paths & sub paths
6532 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) {
6533   import core.stdc.string : memcpy;
6534   if (ps.npoints+n > ps.cpoints) {
6535     import core.stdc.stdlib : realloc;
6536     int cpoints = ps.npoints+n+(ps.cpoints<<1);
6537     float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints);
6538     if (points is null) assert(0, "NanoVega: out of memory");
6539     ps.points = points;
6540     ps.cpoints = cpoints;
6541   }
6542   int i = ps.npoints;
6543   if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n);
6544   ps.npoints += n;
6545   return i;
6546 }
6547 
6548 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) {
6549   NVGsegment* seg = null;
6550   if (ps.nsegments == ps.csegments) {
6551     int csegments = 1+ps.csegments+(ps.csegments<<1);
6552     NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments);
6553     if (segments is null) assert(0, "NanoVega: out of memory");
6554     ps.segments = segments;
6555     ps.csegments = csegments;
6556   }
6557 
6558   if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments;
6559 
6560   seg = &ps.segments[ps.nsegments];
6561   ++ps.nsegments;
6562   seg.firstPoint = firstPoint;
6563   seg.type = cast(short)type;
6564   seg.flags = flags;
6565   ++psp.nsegments;
6566 
6567   nvg__segmentDir(ps, psp, seg, 0, seg.startDir);
6568   nvg__segmentDir(ps, psp, seg, 1, seg.endDir);
6569 }
6570 
6571 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) {
6572   const(float)* points = &ps.points[seg.firstPoint*2];
6573   immutable float x0 = points[0*2+0], x1 = points[1*2+0];
6574   immutable float y0 = points[0*2+1], y1 = points[1*2+1];
6575   switch (seg.type) {
6576     case Command.LineTo:
6577       d.ptr[0] = x1-x0;
6578       d.ptr[1] = y1-y0;
6579       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6580       break;
6581     case Command.BezierTo:
6582       immutable float x2 = points[2*2+0];
6583       immutable float y2 = points[2*2+1];
6584       immutable float x3 = points[3*2+0];
6585       immutable float y3 = points[3*2+1];
6586 
6587       immutable float omt = 1.0f-t;
6588       immutable float omt2 = omt*omt;
6589       immutable float t2 = t*t;
6590 
6591       d.ptr[0] =
6592         3.0f*omt2*(x1-x0)+
6593         6.0f*omt*t*(x2-x1)+
6594         3.0f*t2*(x3-x2);
6595 
6596       d.ptr[1] =
6597         3.0f*omt2*(y1-y0)+
6598         6.0f*omt*t*(y2-y1)+
6599         3.0f*t2*(y3-y2);
6600 
6601       nvg__normalize(&d.ptr[0], &d.ptr[1]);
6602       break;
6603     default:
6604       break;
6605   }
6606 }
6607 
6608 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) {
6609   if (psp.firstSegment == -1) return;
6610   NVGsegment* segments = &ps.segments[psp.firstSegment];
6611   for (int s = 0; s < psp.nsegments; ++s) {
6612     NVGsegment* seg = &segments[s];
6613     const(float)* points = &ps.points[seg.firstPoint*2];
6614     if (seg.type == Command.LineTo) {
6615       nvg__initBounds(seg.bounds);
6616       nvg__expandBounds(seg.bounds, points, 2);
6617     } else {
6618       nvg__bezierBounds(points, seg.bounds);
6619     }
6620   }
6621 }
6622 
6623 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) {
6624   if (psp.firstSegment == -1) return;
6625   immutable bool closed = psp.closed;
6626   const(float)* points = ps.points;
6627   NVGsegment* seg = null;
6628   NVGsegment* segments = &ps.segments[psp.firstSegment];
6629   int nsegments = psp.nsegments;
6630   NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null);
6631 
6632   int ns = 0; // nsupports
6633   float[32] supportingPoints = void;
6634   int firstPoint, lastPoint;
6635 
6636   if (!closed) {
6637     segments[0].flags |= NVGSegmentFlags.Cap;
6638     segments[nsegments-1].flags |= NVGSegmentFlags.Endcap;
6639   }
6640 
6641   for (int s = 0; s < nsegments; ++s) {
6642     seg = &segments[s];
6643     nvg__initBounds(seg.bounds);
6644 
6645     firstPoint = seg.firstPoint*2;
6646     lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6);
6647 
6648     ns = 0;
6649 
6650     // First two supporting points are either side of the start point
6651     supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth;
6652     supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth;
6653 
6654     supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth;
6655     supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth;
6656 
6657     // Second two supporting points are either side of the end point
6658     supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth;
6659     supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth;
6660 
6661     supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth;
6662     supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth;
6663 
6664     if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) {
6665       seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]);
6666       seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]);
6667 
6668       immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1];
6669 
6670       if (M2 > 0.000001f) {
6671         float scale = 1.0f/M2;
6672         if (scale > 600.0f) scale = 600.0f;
6673         seg.miterDir.ptr[0] *= scale;
6674         seg.miterDir.ptr[1] *= scale;
6675       }
6676 
6677       //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10);
6678 
6679       // Add an additional support at the corner on the other line
6680       supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth;
6681       supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth;
6682 
6683       if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) {
6684         // Set a corner as beveled if the join type is bevel or mitered and
6685         // miterLimit is hit.
6686         if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) {
6687           seg.flags |= NVGSegmentFlags.Bevel;
6688         } else {
6689           // Corner is mitered - add miter point as a support
6690           supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth;
6691           supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth;
6692         }
6693       } else if (lineJoin == NVGLineCap.Round) {
6694         // ... and at the midpoint of the corner arc
6695         float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ];
6696         nvg__normalize(&vertexN[0], &vertexN[1]);
6697 
6698         supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth;
6699         supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth;
6700       }
6701     }
6702 
6703     if (seg.flags&NVGSegmentFlags.Cap) {
6704       switch (lineCap) {
6705         case NVGLineCap.Butt:
6706           // supports for butt already added
6707           break;
6708         case NVGLineCap.Square:
6709           // square cap supports are just the original two supports moved out along the direction
6710           supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth;
6711           supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth;
6712           supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth;
6713           supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth;
6714           break;
6715         case NVGLineCap.Round:
6716           // add one additional support for the round cap along the dir
6717           supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth;
6718           supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth;
6719           break;
6720         default:
6721           break;
6722       }
6723     }
6724 
6725     if (seg.flags&NVGSegmentFlags.Endcap) {
6726       // end supporting points, either side of line
6727       int end = 4;
6728       switch(lineCap) {
6729         case NVGLineCap.Butt:
6730           // supports for butt already added
6731           break;
6732         case NVGLineCap.Square:
6733           // square cap supports are just the original two supports moved out along the direction
6734           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth;
6735           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth;
6736           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth;
6737           supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth;
6738           break;
6739         case NVGLineCap.Round:
6740           // add one additional support for the round cap along the dir
6741           supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth;
6742           supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth;
6743           break;
6744         default:
6745           break;
6746       }
6747     }
6748 
6749     nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2);
6750 
6751     prevseg = seg;
6752   }
6753 }
6754 
6755 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) {
6756   NVGpickScene* ps = nvg__pickSceneGet(context);
6757   if (ps is null) return null;
6758 
6759   int i = 0;
6760 
6761   int ncommands = cast(int)acommands.length;
6762   const(float)* commands = acommands.ptr;
6763 
6764   NVGpickPath* pp = null;
6765   NVGpickSubPath* psp = null;
6766   float[2] start = void;
6767   int firstPoint;
6768 
6769   //bool hasHoles = false;
6770   NVGpickSubPath* prev = null;
6771 
6772   float[8] points = void;
6773   float[2] inflections = void;
6774   int ninflections = 0;
6775 
6776   NVGstate* state = nvg__getState(context);
6777   float[4] totalBounds = void;
6778   NVGsegment* segments = null;
6779   const(NVGsegment)* seg = null;
6780   NVGpickSubPath *curpsp;
6781 
6782   pp = nvg__allocPickPath(ps);
6783   if (pp is null) return null;
6784 
6785   pp.id = id;
6786 
6787   bool hasPoints = false;
6788 
6789   void closeIt () {
6790     if (psp is null || !hasPoints) return;
6791     if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) {
6792       firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1);
6793       nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner);
6794     }
6795     psp.closed = true;
6796   }
6797 
6798   while (i < ncommands) {
6799     int cmd = cast(int)commands[i++];
6800     switch (cmd) {
6801       case Command.MoveTo: // one coordinate pair
6802         const(float)* tfxy = commands+i;
6803         i += 2;
6804 
6805         // new starting point
6806         start.ptr[0..2] = tfxy[0..2];
6807 
6808         // start a new path for each sub path to handle sub paths that intersect other sub paths
6809         prev = psp;
6810         psp = nvg__allocPickSubPath(ps);
6811         if (psp is null) { psp = prev; break; }
6812         psp.firstSegment = -1;
6813         psp.winding = NVGSolidity.Solid;
6814         psp.next = prev;
6815 
6816         nvg__pickSceneAddPoints(ps, tfxy, 1);
6817         hasPoints = true;
6818         break;
6819       case Command.LineTo: // one coordinate pair
6820         const(float)* tfxy = commands+i;
6821         i += 2;
6822         firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1);
6823         nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6824         hasPoints = true;
6825         break;
6826       case Command.BezierTo: // three coordinate pairs
6827         const(float)* tfxy = commands+i;
6828         i += 3*2;
6829 
6830         // Split the curve at it's dx==0 or dy==0 inflection points.
6831         // Thus:
6832         //    A horizontal line only ever interects the curves once.
6833         //  and
6834         //    Finding the closest point on any curve converges more reliably.
6835 
6836         // NOTE: We could just split on dy==0 here.
6837 
6838         memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2);
6839         memcpy(&points.ptr[2], tfxy, float.sizeof*2*3);
6840 
6841         ninflections = 0;
6842         nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr);
6843         nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr);
6844 
6845         if (ninflections) {
6846           float previnfl = 0;
6847           float[8] pointsA = void, pointsB = void;
6848 
6849           nvg__smallsort(inflections.ptr, ninflections);
6850 
6851           for (int infl = 0; infl < ninflections; ++infl) {
6852             if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue;
6853 
6854             immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl));
6855 
6856             previnfl = inflections.ptr[infl];
6857 
6858             nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr);
6859 
6860             firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3);
6861             nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0);
6862 
6863             memcpy(points.ptr, pointsB.ptr, float.sizeof*8);
6864           }
6865 
6866           firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3);
6867           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0);
6868         } else {
6869           firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3);
6870           nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner);
6871         }
6872         hasPoints = true;
6873         break;
6874       case Command.Close:
6875         closeIt();
6876         break;
6877       case Command.Winding:
6878         psp.winding = cast(short)cast(int)commands[i];
6879         //if (psp.winding == NVGSolidity.Hole) hasHoles = true;
6880         i += 1;
6881         break;
6882       default:
6883         break;
6884     }
6885   }
6886 
6887   // force-close filled paths
6888   if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt();
6889 
6890   pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill);
6891   pp.subPaths = psp;
6892   pp.strokeWidth = state.strokeWidth*0.5f;
6893   pp.miterLimit = state.miterLimit;
6894   pp.lineCap = cast(short)state.lineCap;
6895   pp.lineJoin = cast(short)state.lineJoin;
6896   pp.evenOddMode = nvg__getState(context).evenOddMode;
6897 
6898   nvg__initBounds(totalBounds);
6899 
6900   for (curpsp = psp; curpsp; curpsp = curpsp.next) {
6901     if (forStroke) {
6902       nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit);
6903     } else {
6904       nvg__pickSubPathAddFillSupports(ps, curpsp);
6905     }
6906 
6907     if (curpsp.firstSegment == -1) continue;
6908     segments = &ps.segments[curpsp.firstSegment];
6909     nvg__initBounds(curpsp.bounds);
6910     for (int s = 0; s < curpsp.nsegments; ++s) {
6911       seg = &segments[s];
6912       //NVG_PICK_DEBUG_BOUNDS(seg.bounds);
6913       nvg__unionBounds(curpsp.bounds, seg.bounds);
6914     }
6915 
6916     nvg__unionBounds(totalBounds, curpsp.bounds);
6917   }
6918 
6919   // Store the scissor rect if present.
6920   if (state.scissor.extent.ptr[0] != -1.0f) {
6921     // Use points storage to store the scissor data
6922     pp.scissor = nvg__pickSceneAddPoints(ps, null, 4);
6923     float* scissor = &ps.points[pp.scissor*2];
6924 
6925     //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof);
6926     scissor[0..6] = state.scissor.xform.mat[];
6927     memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof);
6928 
6929     pp.flags |= NVGPathFlags.Scissor;
6930   }
6931 
6932   memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4);
6933 
6934   return pp;
6935 }
6936 
6937 
6938 // Struct management
6939 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) {
6940   NVGpickPath* pp = ps.freePaths;
6941   if (pp !is null) {
6942     ps.freePaths = pp.next;
6943   } else {
6944     pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof);
6945   }
6946   memset(pp, 0, NVGpickPath.sizeof);
6947   return pp;
6948 }
6949 
6950 // Put a pick path and any sub paths (back) to the free lists.
6951 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) {
6952   // Add all sub paths to the sub path free list.
6953   // Finds the end of the path sub paths, links that to the current
6954   // sub path free list head and replaces the head ptr with the
6955   // head path sub path entry.
6956   NVGpickSubPath* psp = null;
6957   for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {}
6958 
6959   if (psp) {
6960     psp.next = ps.freeSubPaths;
6961     ps.freeSubPaths = pp.subPaths;
6962   }
6963   pp.subPaths = null;
6964 
6965   // Add the path to the path freelist
6966   pp.next = ps.freePaths;
6967   ps.freePaths = pp;
6968   if (pp.next is null) ps.lastPath = pp;
6969 }
6970 
6971 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) {
6972   NVGpickSubPath* psp = ps.freeSubPaths;
6973   if (psp !is null) {
6974     ps.freeSubPaths = psp.next;
6975   } else {
6976     psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof);
6977     if (psp is null) return null;
6978   }
6979   memset(psp, 0, NVGpickSubPath.sizeof);
6980   return psp;
6981 }
6982 
6983 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) {
6984   psp.next = ps.freeSubPaths;
6985   ps.freeSubPaths = psp;
6986 }
6987 
6988 NVGpickScene* nvg__allocPickScene () {
6989   NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof);
6990   if (ps is null) return null;
6991   memset(ps, 0, NVGpickScene.sizeof);
6992   ps.nlevels = 5;
6993   return ps;
6994 }
6995 
6996 void nvg__deletePickScene (NVGpickScene* ps) {
6997   NVGpickPath* pp;
6998   NVGpickSubPath* psp;
6999 
7000   // Add all paths (and thus sub paths) to the free list(s).
7001   while (ps.paths !is null) {
7002     pp = ps.paths.next;
7003     nvg__freePickPath(ps, ps.paths);
7004     ps.paths = pp;
7005   }
7006 
7007   // Delete all paths
7008   while (ps.freePaths !is null) {
7009     pp = ps.freePaths;
7010     ps.freePaths = pp.next;
7011     while (pp.subPaths !is null) {
7012       psp = pp.subPaths;
7013       pp.subPaths = psp.next;
7014       free(psp);
7015     }
7016     free(pp);
7017   }
7018 
7019   // Delete all sub paths
7020   while (ps.freeSubPaths !is null) {
7021     psp = ps.freeSubPaths.next;
7022     free(ps.freeSubPaths);
7023     ps.freeSubPaths = psp;
7024   }
7025 
7026   ps.npoints = 0;
7027   ps.nsegments = 0;
7028 
7029   if (ps.levels !is null) {
7030     free(ps.levels[0]);
7031     free(ps.levels);
7032   }
7033 
7034   if (ps.picked !is null) free(ps.picked);
7035   if (ps.points !is null) free(ps.points);
7036   if (ps.segments !is null) free(ps.segments);
7037 
7038   free(ps);
7039 }
7040 
7041 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) {
7042   if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene();
7043   return ctx.pickScene;
7044 }
7045 
7046 
7047 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t
7048 // points is 4 points (8 floats)
7049 // lvl1 is 3 points (6 floats)
7050 // lvl2 is 2 points (4 floats)
7051 // lvl3 is 1 point (2 floats)
7052 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) {
7053   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7054   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7055 
7056   // Level 1
7057   lvl1[x0] = (points[x1]-points[x0])*t+points[x0];
7058   lvl1[y0] = (points[y1]-points[y0])*t+points[y0];
7059 
7060   lvl1[x1] = (points[x2]-points[x1])*t+points[x1];
7061   lvl1[y1] = (points[y2]-points[y1])*t+points[y1];
7062 
7063   lvl1[x2] = (points[x3]-points[x2])*t+points[x2];
7064   lvl1[y2] = (points[y3]-points[y2])*t+points[y2];
7065 
7066   // Level 2
7067   lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0];
7068   lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0];
7069 
7070   lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1];
7071   lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1];
7072 
7073   // Level 3
7074   lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0];
7075   lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0];
7076 }
7077 
7078 // Calculates a point on a bezier at point t.
7079 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) {
7080   immutable float omt = 1-t;
7081   immutable float omt3 = omt*omt*omt;
7082   immutable float omt2 = omt*omt;
7083   immutable float t3 = t*t*t;
7084   immutable float t2 = t*t;
7085 
7086   tpoint.ptr[0] =
7087     points[0]*omt3+
7088     points[2]*3.0f*omt2*t+
7089     points[4]*3.0f*omt*t2+
7090     points[6]*t3;
7091 
7092   tpoint.ptr[1] =
7093     points[1]*omt3+
7094     points[3]*3.0f*omt2*t+
7095     points[5]*3.0f*omt*t2+
7096     points[7]*t3;
7097 }
7098 
7099 // Splits a cubic bezier curve into two parts at point t.
7100 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) {
7101   enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0;
7102   enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1;
7103 
7104   float[6] lvl1 = void;
7105   float[4] lvl2 = void;
7106   float[2] lvl3 = void;
7107 
7108   nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr);
7109 
7110   // First half
7111   pointsA[x0] = points[x0];
7112   pointsA[y0] = points[y0];
7113 
7114   pointsA[x1] = lvl1.ptr[x0];
7115   pointsA[y1] = lvl1.ptr[y0];
7116 
7117   pointsA[x2] = lvl2.ptr[x0];
7118   pointsA[y2] = lvl2.ptr[y0];
7119 
7120   pointsA[x3] = lvl3.ptr[x0];
7121   pointsA[y3] = lvl3.ptr[y0];
7122 
7123   // Second half
7124   pointsB[x0] = lvl3.ptr[x0];
7125   pointsB[y0] = lvl3.ptr[y0];
7126 
7127   pointsB[x1] = lvl2.ptr[x1];
7128   pointsB[y1] = lvl2.ptr[y1];
7129 
7130   pointsB[x2] = lvl1.ptr[x2];
7131   pointsB[y2] = lvl1.ptr[y2];
7132 
7133   pointsB[x3] = points[x3];
7134   pointsB[y3] = points[y3];
7135 }
7136 
7137 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier.
7138 // Appends any found inflection points to the array inflections and increments *ninflections.
7139 // So finds the parameters where dx/dt or dy/dt is 0
7140 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) {
7141   immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord];
7142   float[2] t = void;
7143   int nvalid = *ninflections;
7144 
7145   immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 );
7146   immutable float b = 6.0f*( v0-2.0f*v1+v2 );
7147   immutable float c = 3.0f*( v1-v0 );
7148 
7149   float d = b*b-4.0f*a*c;
7150   if (nvg__absf(d-0.0f) < NVGPickEPS) {
7151     // Zero or one root
7152     t.ptr[0] = -b/2.0f*a;
7153     if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) {
7154       inflections[nvalid] = t.ptr[0];
7155       ++nvalid;
7156     }
7157   } else if (d > NVGPickEPS) {
7158     // zero, one or two roots
7159     d = nvg__sqrtf(d);
7160 
7161     t.ptr[0] = (-b+d)/(2.0f*a);
7162     t.ptr[1] = (-b-d)/(2.0f*a);
7163 
7164     for (int i = 0; i < 2; ++i) {
7165       if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) {
7166         inflections[nvalid] = t.ptr[i];
7167         ++nvalid;
7168       }
7169     }
7170   } else {
7171     // zero roots
7172   }
7173 
7174   *ninflections = nvalid;
7175 }
7176 
7177 // Sort a small number of floats in ascending order (0 < n < 6)
7178 void nvg__smallsort (float* values, int n) {
7179   bool bSwapped = true;
7180   for (int j = 0; j < n-1 && bSwapped; ++j) {
7181     bSwapped = false;
7182     for (int i = 0; i < n-1; ++i) {
7183       if (values[i] > values[i+1]) {
7184         auto tmp = values[i];
7185         values[i] = values[i+1];
7186         values[i+1] = tmp;
7187       }
7188     }
7189   }
7190 }
7191 
7192 // Calculates the bounding rect of a given cubic bezier curve.
7193 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) {
7194   float[4] inflections = void;
7195   int ninflections = 0;
7196   float[2] tpoint = void;
7197 
7198   nvg__initBounds(bounds);
7199 
7200   // Include start and end points in bounds
7201   nvg__expandBounds(bounds, &points[0], 1);
7202   nvg__expandBounds(bounds, &points[6], 1);
7203 
7204   // Calculate dx==0 and dy==0 inflection points and add them to the bounds
7205 
7206   nvg__bezierInflections(points, 0, &ninflections, inflections.ptr);
7207   nvg__bezierInflections(points, 1, &ninflections, inflections.ptr);
7208 
7209   foreach (immutable int i; 0..ninflections) {
7210     nvg__bezierEval(points, inflections[i], tpoint);
7211     nvg__expandBounds(bounds, tpoint.ptr, 1);
7212   }
7213 }
7214 
7215 // Checks to see if a line originating from x,y along the +ve x axis
7216 // intersects the given line (points[0],points[1]) -> (points[2], points[3]).
7217 // Returns `true` on intersection.
7218 // Horizontal lines are never hit.
7219 bool nvg__intersectLine (const(float)* points, float x, float y) {
7220   immutable float x1 = points[0];
7221   immutable float y1 = points[1];
7222   immutable float x2 = points[2];
7223   immutable float y2 = points[3];
7224   immutable float d = y2-y1;
7225   if (d > NVGPickEPS || d < -NVGPickEPS) {
7226     immutable float s = (x2-x1)/d;
7227     immutable float lineX = x1+(y-y1)*s;
7228     return (lineX > x);
7229   } else {
7230     return false;
7231   }
7232 }
7233 
7234 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier.
7235 // It is assumed that the line originates from within the bounding box of
7236 // the bezier and that the curve has no dy=0 inflection points.
7237 // Returns the number of intersections found (which is either 1 or 0).
7238 int nvg__intersectBezier (const(float)* points, float x, float y) {
7239   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7240   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7241 
7242   if (y0 == y1 && y1 == y2 && y2 == y3) return 0;
7243 
7244   // Initial t guess
7245   float t = void;
7246        if (y3 != y0) t = (y-y0)/(y3-y0);
7247   else if (x3 != x0) t = (x-x0)/(x3-x0);
7248   else t = 0.5f;
7249 
7250   // A few Newton iterations
7251   for (int iter = 0; iter < 6; ++iter) {
7252     immutable float omt = 1-t;
7253     immutable float omt2 = omt*omt;
7254     immutable float t2 = t*t;
7255     immutable float omt3 = omt2*omt;
7256     immutable float t3 = t2*t;
7257 
7258     immutable float ty = y0*omt3 +
7259       y1*3.0f*omt2*t +
7260       y2*3.0f*omt*t2 +
7261       y3*t3;
7262 
7263     // Newton iteration
7264     immutable float dty = 3.0f*omt2*(y1-y0) +
7265       6.0f*omt*t*(y2-y1) +
7266       3.0f*t2*(y3-y2);
7267 
7268     // dty will never == 0 since:
7269     //  Either omt, omt2 are zero OR t2 is zero
7270     //  y0 != y1 != y2 != y3 (checked above)
7271     t = t-(ty-y)/dty;
7272   }
7273 
7274   {
7275     immutable float omt = 1-t;
7276     immutable float omt2 = omt*omt;
7277     immutable float t2 = t*t;
7278     immutable float omt3 = omt2*omt;
7279     immutable float t3 = t2*t;
7280 
7281     immutable float tx =
7282       x0*omt3+
7283       x1*3.0f*omt2*t+
7284       x2*3.0f*omt*t2+
7285       x3*t3;
7286 
7287     return (tx > x ? 1 : 0);
7288   }
7289 }
7290 
7291 // Finds the closest point on a line to a given point
7292 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) {
7293   immutable float x1 = points[0];
7294   immutable float y1 = points[1];
7295   immutable float x2 = points[2];
7296   immutable float y2 = points[3];
7297   immutable float pqx = x2-x1;
7298   immutable float pqz = y2-y1;
7299   immutable float dx = x-x1;
7300   immutable float dz = y-y1;
7301   immutable float d = pqx*pqx+pqz*pqz;
7302   float t = pqx*dx+pqz*dz;
7303   if (d > 0) t /= d;
7304   if (t < 0) t = 0; else if (t > 1) t = 1;
7305   closest[0] = x1+t*pqx;
7306   closest[1] = y1+t*pqz;
7307   *ot = t;
7308 }
7309 
7310 // Finds the closest point on a curve for a given point (x,y).
7311 // Assumes that the curve has no dx==0 or dy==0 inflection points.
7312 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) {
7313   immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0];
7314   immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1];
7315 
7316   // This assumes that the curve has no dy=0 inflection points.
7317 
7318   // Initial t guess
7319   float t = 0.5f;
7320 
7321   // A few Newton iterations
7322   for (int iter = 0; iter < 6; ++iter) {
7323     immutable float omt = 1-t;
7324     immutable float omt2 = omt*omt;
7325     immutable float t2 = t*t;
7326     immutable float omt3 = omt2*omt;
7327     immutable float t3 = t2*t;
7328 
7329     immutable float ty =
7330       y0*omt3+
7331       y1*3.0f*omt2*t+
7332       y2*3.0f*omt*t2+
7333       y3*t3;
7334 
7335     immutable float tx =
7336       x0*omt3+
7337       x1*3.0f*omt2*t+
7338       x2*3.0f*omt*t2+
7339       x3*t3;
7340 
7341     // Newton iteration
7342     immutable float dty =
7343       3.0f*omt2*(y1-y0)+
7344       6.0f*omt*t*(y2-y1)+
7345       3.0f*t2*(y3-y2);
7346 
7347     immutable float ddty =
7348       6.0f*omt*(y2-2.0f*y1+y0)+
7349       6.0f*t*(y3-2.0f*y2+y1);
7350 
7351     immutable float dtx =
7352       3.0f*omt2*(x1-x0)+
7353       6.0f*omt*t*(x2-x1)+
7354       3.0f*t2*(x3-x2);
7355 
7356     immutable float ddtx =
7357       6.0f*omt*(x2-2.0f*x1+x0)+
7358       6.0f*t*(x3-2.0f*x2+x1);
7359 
7360     immutable float errorx = tx-x;
7361     immutable float errory = ty-y;
7362 
7363     immutable float n = errorx*dtx+errory*dty;
7364     if (n == 0) break;
7365 
7366     immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty;
7367     if (d != 0) t = t-n/d; else break;
7368   }
7369 
7370   t = nvg__max(0, nvg__min(1.0, t));
7371   *ot = t;
7372   {
7373     immutable float omt = 1-t;
7374     immutable float omt2 = omt*omt;
7375     immutable float t2 = t*t;
7376     immutable float omt3 = omt2*omt;
7377     immutable float t3 = t2*t;
7378 
7379     immutable float ty =
7380       y0*omt3+
7381       y1*3.0f*omt2*t+
7382       y2*3.0f*omt*t2+
7383       y3*t3;
7384 
7385     immutable float tx =
7386       x0*omt3+
7387       x1*3.0f*omt2*t+
7388       x2*3.0f*omt*t2+
7389       x3*t3;
7390 
7391     closest[0] = tx;
7392     closest[1] = ty;
7393   }
7394 }
7395 
7396 // Returns:
7397 //  1  If (x,y) is contained by the stroke of the path
7398 //  0  If (x,y) is not contained by the path.
7399 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) {
7400   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7401   if (psp.firstSegment == -1) return 0;
7402 
7403   float[2] closest = void;
7404   float[2] d = void;
7405   float t = void;
7406 
7407   // trace a line from x,y out along the positive x axis and count the number of intersections
7408   int nsegments = psp.nsegments;
7409   const(NVGsegment)* seg = ps.segments+psp.firstSegment;
7410   const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null);
7411   immutable float strokeWidthSqd = strokeWidth*strokeWidth;
7412 
7413   for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) {
7414     if (nvg__pointInBounds(x, y, seg.bounds)) {
7415       // Line potentially hits stroke.
7416       switch (seg.type) {
7417         case Command.LineTo:
7418           nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7419           break;
7420         case Command.BezierTo:
7421           nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t);
7422           break;
7423         default:
7424           continue;
7425       }
7426 
7427       d.ptr[0] = x-closest.ptr[0];
7428       d.ptr[1] = y-closest.ptr[1];
7429 
7430       if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) ||
7431           (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 ||
7432           (lineJoin == NVGLineCap.Round))
7433       {
7434         // Closest point is in the middle of the line/curve, at a rounded join/cap
7435         // or at a smooth join
7436         immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7437         if (distSqd < strokeWidthSqd) return 1;
7438       } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) ||
7439                  (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) {
7440         switch (lineCap) {
7441           case NVGLineCap.Butt:
7442             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7443             immutable float dirD = (t < NVGPickEPS ?
7444               -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) :
7445                 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]);
7446             if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1;
7447             break;
7448           case NVGLineCap.Square:
7449             if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1;
7450             break;
7451           case NVGLineCap.Round:
7452             immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1];
7453             if (distSqd < strokeWidthSqd) return 1;
7454             break;
7455           default:
7456             break;
7457         }
7458       } else if (seg.flags&NVGSegmentFlags.Corner) {
7459         // Closest point is at a corner
7460         const(NVGsegment)* seg0, seg1;
7461 
7462         if (t < NVGPickEPS) {
7463           seg0 = prevseg;
7464           seg1 = seg;
7465         } else {
7466           seg0 = seg;
7467           seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1);
7468         }
7469 
7470         if (!(seg1.flags&NVGSegmentFlags.Bevel)) {
7471           immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1];
7472           immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1];
7473           if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1;
7474         } else {
7475           d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth;
7476           d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth;
7477           if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1;
7478         }
7479       }
7480     }
7481   }
7482 
7483   return 0;
7484 }
7485 
7486 // Returns:
7487 //   1  If (x,y) is contained by the path and the path is solid.
7488 //  -1  If (x,y) is contained by the path and the path is a hole.
7489 //   0  If (x,y) is not contained by the path.
7490 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) {
7491   if (!nvg__pointInBounds(x, y, psp.bounds)) return 0;
7492   if (psp.firstSegment == -1) return 0;
7493 
7494   const(NVGsegment)* seg = &ps.segments[psp.firstSegment];
7495   int nsegments = psp.nsegments;
7496   int nintersections = 0;
7497 
7498   // trace a line from x,y out along the positive x axis and count the number of intersections
7499   for (int s = 0; s < nsegments; ++s, ++seg) {
7500     if ((seg.bounds.ptr[1]-NVGPickEPS) < y &&
7501         (seg.bounds.ptr[3]-NVGPickEPS) > y &&
7502         seg.bounds.ptr[2] > x)
7503     {
7504       // Line hits the box.
7505       switch (seg.type) {
7506         case Command.LineTo:
7507           if (seg.bounds.ptr[0] > x) {
7508             // line originates outside the box
7509             ++nintersections;
7510           } else {
7511             // line originates inside the box
7512             nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y);
7513           }
7514           break;
7515         case Command.BezierTo:
7516           if (seg.bounds.ptr[0] > x) {
7517             // line originates outside the box
7518             ++nintersections;
7519           } else {
7520             // line originates inside the box
7521             nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y);
7522           }
7523           break;
7524         default:
7525           break;
7526       }
7527     }
7528   }
7529 
7530   if (evenOddMode) {
7531     return nintersections;
7532   } else {
7533     return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0);
7534   }
7535 }
7536 
7537 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7538   int pickCount = 0;
7539   const(NVGpickSubPath)* psp = pp.subPaths;
7540   while (psp !is null) {
7541     pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode);
7542     psp = psp.next;
7543   }
7544   return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0);
7545 }
7546 
7547 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) {
7548   const(NVGpickSubPath)* psp = pp.subPaths;
7549   while (psp !is null) {
7550     if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true;
7551     psp = psp.next;
7552   }
7553   return false;
7554 }
7555 
7556 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) {
7557   if (nvg__pointInBounds(x, y, pp.bounds)) {
7558     //{ import core.stdc.stdio; printf("  (0): in bounds!\n"); }
7559     if (pp.flags&NVGPathFlags.Scissor) {
7560       const(float)* scissor = &ps.points[pp.scissor*2];
7561       // untransform scissor translation
7562       float stx = void, sty = void;
7563       ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]);
7564       immutable float rx = x-stx;
7565       immutable float ry = y-sty;
7566       //{ 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]); }
7567       if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] ||
7568           nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7])
7569       {
7570         //{ import core.stdc.stdio; printf("    (1): scissor reject!\n"); }
7571         return false;
7572       }
7573     }
7574     return true;
7575   }
7576   return false;
7577 }
7578 
7579 int nvg__countBitsUsed (uint v) pure {
7580   pragma(inline, true);
7581   import core.bitop : bsr;
7582   return (v != 0 ? bsr(v)+1 : 0);
7583 }
7584 
7585 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) {
7586   if (ps is null || pp is null) return;
7587 
7588   int[4] cellbounds;
7589   int base = ps.nlevels-1;
7590   int level;
7591   int levelwidth;
7592   int levelshift;
7593   int levelx;
7594   int levely;
7595   NVGpickPath** cell = null;
7596 
7597   // Bit tricks for inserting into an implicit quadtree.
7598 
7599   // Calc bounds of path in cells at the lowest level
7600   cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim);
7601   cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim);
7602   cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim);
7603   cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim);
7604 
7605   // Find which bits differ between the min/max x/y coords
7606   cellbounds.ptr[0] ^= cellbounds.ptr[2];
7607   cellbounds.ptr[1] ^= cellbounds.ptr[3];
7608 
7609   // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x);
7610   // to calculate the level to insert at (the level at which the bounds fit in a single cell)
7611   level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1]));
7612   if (level < 0) level = 0;
7613   //{ 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]); }
7614   //level = 0;
7615 
7616   // Find the correct cell in the chosen level, clamping to the edges.
7617   levelwidth = 1<<level;
7618   levelshift = (ps.nlevels-level)-1;
7619   levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1);
7620   levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1);
7621 
7622   // Insert the path into the linked list at that cell.
7623   cell = &ps.levels[level][levely*levelwidth+levelx];
7624 
7625   pp.cellnext = *cell;
7626   *cell = pp;
7627 
7628   if (ps.paths is null) ps.lastPath = pp;
7629   pp.next = ps.paths;
7630   ps.paths = pp;
7631 
7632   // Store the order (depth) of the path for picking ops.
7633   pp.order = cast(short)ps.npaths;
7634   ++ps.npaths;
7635 }
7636 
7637 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) {
7638   NVGpickScene* ps = nvg__pickSceneGet(ctx);
7639 
7640   //NVG_PICK_DEBUG_NEWFRAME();
7641 
7642   // Return all paths & sub paths from last frame to the free list
7643   while (ps.paths !is null) {
7644     NVGpickPath* pp = ps.paths.next;
7645     nvg__freePickPath(ps, ps.paths);
7646     ps.paths = pp;
7647   }
7648 
7649   ps.paths = null;
7650   ps.npaths = 0;
7651 
7652   // Store the screen metrics for the quadtree
7653   ps.width = width;
7654   ps.height = height;
7655 
7656   immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1));
7657   ps.xdim = cast(float)width/lowestSubDiv;
7658   ps.ydim = cast(float)height/lowestSubDiv;
7659 
7660   // Allocate the quadtree if required.
7661   if (ps.levels is null) {
7662     int ncells = 1;
7663 
7664     ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels);
7665     for (int l = 0; l < ps.nlevels; ++l) {
7666       int leveldim = 1<<l;
7667       ncells += leveldim*leveldim;
7668     }
7669 
7670     ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells);
7671 
7672     int cell = 1;
7673     for (int l = 1; l < ps.nlevels; ++l) {
7674       ps.levels[l] = &ps.levels[0][cell];
7675       int leveldim = 1<<l;
7676       cell += leveldim*leveldim;
7677     }
7678 
7679     ps.ncells = ncells;
7680   }
7681   memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof);
7682 
7683   // Allocate temporary storage for nvgHitTestAll results if required.
7684   if (ps.picked is null) {
7685     ps.cpicked = 16;
7686     ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked);
7687   }
7688 
7689   ps.npoints = 0;
7690   ps.nsegments = 0;
7691 }
7692 } // nothrow @trusted @nogc
7693 
7694 
7695 /// Return outline of the current path. Returned outline is not flattened.
7696 /// Group: paths
7697 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc {
7698   if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init;
7699 
7700   auto res = NVGPathOutline.createNew();
7701 
7702   const(float)[] acommands = ctx.commands[0..ctx.ncommands];
7703   int ncommands = cast(int)acommands.length;
7704   const(float)* commands = acommands.ptr;
7705 
7706   float cx = 0, cy = 0;
7707   float[2] start = void;
7708   float[4] totalBounds = [float.max, float.max, -float.max, -float.max];
7709   float[8] bcp = void; // bezier curve points; used to calculate bounds
7710 
7711   void addToBounds (in float x, in float y) nothrow @trusted @nogc {
7712     totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x);
7713     totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y);
7714     totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x);
7715     totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y);
7716   }
7717 
7718   bool hasPoints = false;
7719 
7720   void closeIt () nothrow @trusted @nogc {
7721     if (!hasPoints) return;
7722     if (cx != start.ptr[0] || cy != start.ptr[1]) {
7723       res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7724       res.ds.putArgs(start[]);
7725       cx = start.ptr[0];
7726       cy = start.ptr[1];
7727       addToBounds(cx, cy);
7728     }
7729   }
7730 
7731   int i = 0;
7732   while (i < ncommands) {
7733     int cmd = cast(int)commands[i++];
7734     switch (cmd) {
7735       case Command.MoveTo: // one coordinate pair
7736         const(float)* tfxy = commands+i;
7737         i += 2;
7738         // add command
7739         res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo);
7740         res.ds.putArgs(tfxy[0..2]);
7741         // new starting point
7742         start.ptr[0..2] = tfxy[0..2];
7743         cx = tfxy[0];
7744         cy = tfxy[0];
7745         addToBounds(cx, cy);
7746         hasPoints = true;
7747         break;
7748       case Command.LineTo: // one coordinate pair
7749         const(float)* tfxy = commands+i;
7750         i += 2;
7751         // add command
7752         res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo);
7753         res.ds.putArgs(tfxy[0..2]);
7754         cx = tfxy[0];
7755         cy = tfxy[0];
7756         addToBounds(cx, cy);
7757         hasPoints = true;
7758         break;
7759       case Command.BezierTo: // three coordinate pairs
7760         const(float)* tfxy = commands+i;
7761         i += 3*2;
7762         // add command
7763         res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo);
7764         res.ds.putArgs(tfxy[0..6]);
7765         // bounds
7766         bcp.ptr[0] = cx;
7767         bcp.ptr[1] = cy;
7768         bcp.ptr[2..8] = tfxy[0..6];
7769         nvg__bezierBounds(bcp.ptr, totalBounds);
7770         cx = tfxy[4];
7771         cy = tfxy[5];
7772         hasPoints = true;
7773         break;
7774       case Command.Close:
7775         closeIt();
7776         hasPoints = false;
7777         break;
7778       case Command.Winding:
7779         //psp.winding = cast(short)cast(int)commands[i];
7780         i += 1;
7781         break;
7782       default:
7783         break;
7784     }
7785   }
7786 
7787   res.ds.bounds[] = totalBounds[];
7788   return res;
7789 }
7790 
7791 
7792 // ////////////////////////////////////////////////////////////////////////// //
7793 // Text
7794 
7795 /** Creates font by loading it from the disk from specified file name.
7796  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7797  * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that).
7798  *
7799  * On POSIX systems it is possible to use fontconfig font names too.
7800  * `:noaa` in font path is still allowed, but it must be the last option.
7801  *
7802  * Group: text_api
7803  */
7804 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted {
7805   return ctx.fs.addFont(name, path, ctx.params.fontAA);
7806 }
7807 
7808 /** Creates font by loading it from the specified memory chunk.
7809  * Returns handle to the font or FONS_INVALID (aka -1) on error.
7810  * Won't free data on error.
7811  *
7812  * Group: text_api
7813  */
7814 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc {
7815   return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA);
7816 }
7817 
7818 /// Add fonts from another context.
7819 /// This is more effective than reloading fonts, 'cause font data will be shared.
7820 /// Group: text_api
7821 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc {
7822   if (ctx is null || source is null) return;
7823   ctx.fs.addFontsFrom(source.fs);
7824 }
7825 
7826 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found.
7827 /// Group: text_api
7828 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc {
7829   pragma(inline, true);
7830   return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name));
7831 }
7832 
7833 /// Sets the font size of current text style.
7834 /// Group: text_api
7835 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc {
7836   pragma(inline, true);
7837   nvg__getState(ctx).fontSize = size;
7838 }
7839 
7840 /// Gets the font size of current text style.
7841 /// Group: text_api
7842 public float fontSize (NVGContext ctx) nothrow @trusted @nogc {
7843   pragma(inline, true);
7844   return nvg__getState(ctx).fontSize;
7845 }
7846 
7847 /// Sets the blur of current text style.
7848 /// Group: text_api
7849 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc {
7850   pragma(inline, true);
7851   nvg__getState(ctx).fontBlur = blur;
7852 }
7853 
7854 /// Gets the blur of current text style.
7855 /// Group: text_api
7856 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc {
7857   pragma(inline, true);
7858   return nvg__getState(ctx).fontBlur;
7859 }
7860 
7861 /// Sets the letter spacing of current text style.
7862 /// Group: text_api
7863 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc {
7864   pragma(inline, true);
7865   nvg__getState(ctx).letterSpacing = spacing;
7866 }
7867 
7868 /// Gets the letter spacing of current text style.
7869 /// Group: text_api
7870 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc {
7871   pragma(inline, true);
7872   return nvg__getState(ctx).letterSpacing;
7873 }
7874 
7875 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
7876 /// Group: text_api
7877 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc {
7878   pragma(inline, true);
7879   nvg__getState(ctx).lineHeight = lineHeight;
7880 }
7881 
7882 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size.
7883 /// Group: text_api
7884 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc {
7885   pragma(inline, true);
7886   return nvg__getState(ctx).lineHeight;
7887 }
7888 
7889 /// Sets the text align of current text style, see [NVGTextAlign] for options.
7890 /// Group: text_api
7891 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc {
7892   pragma(inline, true);
7893   nvg__getState(ctx).textAlign = talign;
7894 }
7895 
7896 /// Ditto.
7897 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc {
7898   pragma(inline, true);
7899   nvg__getState(ctx).textAlign.horizontal = h;
7900 }
7901 
7902 /// Ditto.
7903 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc {
7904   pragma(inline, true);
7905   nvg__getState(ctx).textAlign.vertical = v;
7906 }
7907 
7908 /// Ditto.
7909 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc {
7910   pragma(inline, true);
7911   nvg__getState(ctx).textAlign.reset(h, v);
7912 }
7913 
7914 /// Ditto.
7915 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc {
7916   pragma(inline, true);
7917   nvg__getState(ctx).textAlign.reset(h, v);
7918 }
7919 
7920 /// Gets the text align of current text style, see [NVGTextAlign] for options.
7921 /// Group: text_api
7922 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc {
7923   pragma(inline, true);
7924   return nvg__getState(ctx).textAlign;
7925 }
7926 
7927 /// Sets the font face based on specified id of current text style.
7928 /// Group: text_api
7929 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc {
7930   pragma(inline, true);
7931   nvg__getState(ctx).fontId = font;
7932 }
7933 
7934 /// Gets the font face based on specified id of current text style.
7935 /// Group: text_api
7936 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc {
7937   pragma(inline, true);
7938   return nvg__getState(ctx).fontId;
7939 }
7940 
7941 /** Sets the font face based on specified name of current text style.
7942  *
7943  * The underlying implementation is using O(1) data structure to lookup
7944  * font names, so you probably should use this function instead of [fontFaceId]
7945  * to make your code more robust and less error-prone.
7946  *
7947  * Group: text_api
7948  */
7949 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc {
7950   pragma(inline, true);
7951   nvg__getState(ctx).fontId = ctx.fs.getFontByName(font);
7952 }
7953 
7954 static if (is(typeof(&fons__nvg__toPath))) {
7955   public enum NanoVegaHasCharToPath = true; ///
7956 } else {
7957   public enum NanoVegaHasCharToPath = false; ///
7958 }
7959 
7960 /// Adds glyph outlines to the current path. Vertical 0 is baseline.
7961 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
7962 /// Returns `false` if there is no such glyph, or current font is not scalable.
7963 /// Group: text_api
7964 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
7965   NVGstate* state = nvg__getState(ctx);
7966   ctx.fs.fontId = state.fontId;
7967   return ctx.fs.toPath(ctx, dch, bounds);
7968 }
7969 
7970 static if (is(typeof(&fons__nvg__bounds))) {
7971   public enum NanoVegaHasCharPathBounds = true; ///
7972 } else {
7973   public enum NanoVegaHasCharPathBounds = false; ///
7974 }
7975 
7976 /// Returns bounds of the glyph outlines. Vertical 0 is baseline.
7977 /// The glyph is not scaled in any way.
7978 /// Returns `false` if there is no such glyph, or current font is not scalable.
7979 /// Group: text_api
7980 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc {
7981   NVGstate* state = nvg__getState(ctx);
7982   ctx.fs.fontId = state.fontId;
7983   return ctx.fs.getPathBounds(dch, bounds);
7984 }
7985 
7986 /** [charOutline] will return [NVGPathOutline].
7987 
7988  some usage samples:
7989 
7990  ---
7991     float[4] bounds = void;
7992 
7993     nvg.scale(0.5, 0.5);
7994     nvg.translate(500, 800);
7995     nvg.evenOddFill;
7996 
7997     nvg.newPath();
7998     nvg.charToPath('&', bounds[]);
7999     conwriteln(bounds[]);
8000     nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0")));
8001     nvg.strokeColor(NVGColor("#0f0"));
8002     nvg.strokeWidth = 3;
8003     nvg.fill();
8004     nvg.stroke();
8005     // glyph bounds
8006     nvg.newPath();
8007     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8008     nvg.strokeColor(NVGColor("#00f"));
8009     nvg.stroke();
8010 
8011     nvg.newPath();
8012     nvg.charToPath('g', bounds[]);
8013     conwriteln(bounds[]);
8014     nvg.fill();
8015     nvg.strokeColor(NVGColor("#0f0"));
8016     nvg.stroke();
8017     // glyph bounds
8018     nvg.newPath();
8019     nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
8020     nvg.strokeColor(NVGColor("#00f"));
8021     nvg.stroke();
8022 
8023     nvg.newPath();
8024     nvg.moveTo(0, 0);
8025     nvg.lineTo(600, 0);
8026     nvg.strokeColor(NVGColor("#0ff"));
8027     nvg.stroke();
8028 
8029     if (auto ol = nvg.charOutline('Q')) {
8030       scope(exit) ol.kill();
8031       nvg.newPath();
8032       conwriteln("==== length: ", ol.length, " ====");
8033       foreach (const ref cmd; ol.commands) {
8034         //conwriteln("  ", cmd.code, ": ", cmd.args[]);
8035         assert(cmd.valid);
8036         final switch (cmd.code) {
8037           case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break;
8038           case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break;
8039           case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break;
8040           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;
8041         }
8042       }
8043       nvg.strokeColor(NVGColor("#f00"));
8044       nvg.stroke();
8045     }
8046  ---
8047 
8048  Group: text_api
8049  */
8050 public struct NVGPathOutline {
8051 private nothrow @trusted @nogc:
8052   struct DataStore {
8053     uint rc; // refcount
8054     ubyte* data;
8055     uint used;
8056     uint size;
8057     uint ccount; // number of commands
8058     float[4] bounds = 0; /// outline bounds
8059   nothrow @trusted @nogc:
8060     void putBytes (const(void)[] b) {
8061       if (b.length == 0) return;
8062       if (b.length >= int.max/8) assert(0, "NanoVega: out of memory");
8063       if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory");
8064       if (used+cast(uint)b.length > size) {
8065         import core.stdc.stdlib : realloc;
8066         uint newsz = size;
8067         while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192);
8068         assert(used+cast(uint)b.length <= newsz);
8069         data = cast(ubyte*)realloc(data, newsz);
8070         if (data is null) assert(0, "NanoVega: out of memory");
8071         size = newsz;
8072       }
8073       import core.stdc.string : memcpy;
8074       memcpy(data+used, b.ptr, b.length);
8075       used += cast(uint)b.length;
8076     }
8077     void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); }
8078     void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); }
8079   }
8080 
8081   static void incRef (DataStore* ds) {
8082     pragma(inline, true);
8083     if (ds !is null) {
8084       ++ds.rc;
8085       //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); }
8086     }
8087   }
8088 
8089   static void decRef (DataStore* ds) {
8090     version(aliced) pragma(inline, true);
8091     if (ds !is null) {
8092       //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); }
8093       if (--ds.rc == 0) {
8094         import core.stdc.stdlib : free;
8095         import core.stdc.string : memset;
8096         if (ds.data !is null) free(ds.data);
8097         memset(ds, 0, DataStore.sizeof); // just in case
8098         free(ds);
8099         //{ import core.stdc.stdio; printf("  ods(%p): killed.\n"); }
8100       }
8101     }
8102   }
8103 
8104 private:
8105   static NVGPathOutline createNew () {
8106     import core.stdc.stdlib : malloc;
8107     import core.stdc.string : memset;
8108     auto ds = cast(DataStore*)malloc(DataStore.sizeof);
8109     if (ds is null) assert(0, "NanoVega: out of memory");
8110     memset(ds, 0, DataStore.sizeof);
8111     ds.rc = 1;
8112     NVGPathOutline res;
8113     res.dsaddr = cast(usize)ds;
8114     return res;
8115   }
8116 
8117 private:
8118   usize dsaddr; // fool GC
8119 
8120   @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; }
8121 
8122 public:
8123   /// commands
8124   static struct Command {
8125     ///
8126     enum Kind : ubyte {
8127       MoveTo, ///
8128       LineTo, ///
8129       QuadTo, ///
8130       BezierTo, ///
8131       End, /// no more commands (this command is not `valid`!)
8132 
8133     }
8134     Kind code; ///
8135     const(float)[] args; ///
8136     @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } ///
8137 
8138     static uint arglen (Kind code) pure nothrow @safe @nogc {
8139       pragma(inline, true);
8140       return
8141         code == Kind.MoveTo || code == Kind.LineTo ? 2 :
8142         code == Kind.QuadTo ? 4 :
8143         code == Kind.BezierTo ? 6 :
8144         0;
8145     }
8146 
8147     /// perform NanoVega command with stored data.
8148     void perform (NVGContext ctx) const nothrow @trusted @nogc {
8149       if (ctx is null) return;
8150       final switch (code) {
8151         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break;
8152         case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break;
8153         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break;
8154         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break;
8155         case Kind.End: break;
8156       }
8157     }
8158 
8159     /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix.
8160     void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc {
8161       if (ctx is null || !valid) return;
8162       float[6] pts = void;
8163       pts[0..args.length] = args[];
8164       foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]);
8165       final switch (code) {
8166         case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break;
8167         case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break;
8168         case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break;
8169         case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break;
8170         case Kind.End: break;
8171       }
8172     }
8173   }
8174 
8175 public:
8176   /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo).
8177   static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8178     auto res = createNew();
8179     res.ds.putCommand(Command.Kind.MoveTo);
8180     res.ds.putArgs(x0, y0);
8181     res.ds.putCommand(Command.Kind.QuadTo);
8182     res.ds.putArgs(cx, cy, x, y);
8183     return res;
8184   }
8185 
8186   /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo).
8187   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) {
8188     auto res = createNew();
8189     res.ds.putCommand(Command.Kind.MoveTo);
8190     res.ds.putArgs(x1, y1);
8191     res.ds.putCommand(Command.Kind.BezierTo);
8192     res.ds.putArgs(x2, y2, x3, y3, x4, y4);
8193     return res;
8194   }
8195 
8196 public:
8197   this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8198   ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8199 
8200   void opAssign() (in auto ref NVGPathOutline a) {
8201     incRef(cast(DataStore*)a.dsaddr);
8202     decRef(cast(DataStore*)dsaddr);
8203     dsaddr = a.dsaddr;
8204   }
8205 
8206   /// Clear storage.
8207   void clear () {
8208     pragma(inline, true);
8209     decRef(ds);
8210     dsaddr = 0;
8211   }
8212 
8213   /// Is this outline empty?
8214   @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); }
8215 
8216   /// Returns number of commands in outline.
8217   @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); }
8218 
8219   /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8220   NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); }
8221 
8222   /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8223   NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); }
8224 
8225   // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo.
8226   private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const {
8227     import core.stdc.string : memset;
8228 
8229     NVGPathOutline res;
8230     if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do
8231 
8232     // check if we need to flatten the path
8233     if (tfm is null) {
8234       bool dowork = false;
8235       foreach (const ref cs; commands) {
8236         if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) {
8237           dowork = true;
8238           break;
8239         }
8240       }
8241       if (!dowork) { res = this; return res; } // nothing to do
8242     }
8243 
8244     NVGcontextinternal ctx;
8245     memset(&ctx, 0, ctx.sizeof);
8246     ctx.cache = nvg__allocPathCache();
8247     scope(exit) {
8248       import core.stdc.stdlib : free;
8249       nvg__deletePathCache(ctx.cache);
8250     }
8251 
8252     ctx.tessTol = 0.25f;
8253     ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer
8254     ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
8255     ctx.distTol = 0.01f;
8256     ctx.tesselatortype = NVGTesselation.DeCasteljau;
8257 
8258     nvg__addPath(&ctx); // we need this for `nvg__addPoint()`
8259 
8260     // has some curves or transformations, convert path
8261     res = createNew();
8262     float[8] args = void;
8263 
8264     res.ds.bounds = [float.max, float.max, -float.max, -float.max];
8265 
8266     float lastX = float.max, lastY = float.max;
8267     bool lastWasMove = false;
8268 
8269     void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc {
8270       if (tfm !is null) tfm.point(x, y);
8271       bool isMove = (cmd == Command.Kind.MoveTo);
8272       if (isMove) {
8273         // moveto
8274         if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8275       } else {
8276         // lineto
8277         if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return;
8278       }
8279       lastWasMove = isMove;
8280       lastX = x;
8281       lastY = y;
8282       res.ds.putCommand(cmd);
8283       res.ds.putArgs(x, y);
8284       res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x);
8285       res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y);
8286       res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x);
8287       res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y);
8288     }
8289 
8290     // sorry for this pasta
8291     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 {
8292       ctx.cache.npoints = 0;
8293       if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
8294         nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8295       } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
8296         nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner);
8297       } else {
8298         nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner);
8299       }
8300       // add generated points
8301       foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y);
8302     }
8303 
8304     void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) {
8305       flattenBezier(
8306         x0, y0,
8307         x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0),
8308         x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y),
8309         x, y,
8310         0,
8311       );
8312     }
8313 
8314     float cx = 0, cy = 0;
8315     foreach (const ref cs; commands) {
8316       switch (cs.code) {
8317         case Command.Kind.LineTo:
8318         case Command.Kind.MoveTo:
8319           addPoint(cs.args[0], cs.args[1], cs.code);
8320           cx = cs.args[0];
8321           cy = cs.args[1];
8322           break;
8323         case Command.Kind.QuadTo:
8324           flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]);
8325           cx = cs.args[2];
8326           cy = cs.args[3];
8327           break;
8328         case Command.Kind.BezierTo:
8329           flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0);
8330           cx = cs.args[4];
8331           cy = cs.args[5];
8332           break;
8333         default:
8334           break;
8335       }
8336     }
8337 
8338     return res;
8339   }
8340 
8341   /// Returns forward range with all glyph commands.
8342   auto commands () const nothrow @trusted @nogc {
8343     static struct Range {
8344     private nothrow @trusted @nogc:
8345       usize dsaddr;
8346       uint cpos; // current position in data
8347       uint cleft; // number of commands left
8348       @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); }
8349     public:
8350       this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); }
8351       ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); }
8352       void opAssign() (in auto ref Range a) {
8353         incRef(cast(DataStore*)a.dsaddr);
8354         decRef(cast(DataStore*)dsaddr);
8355         dsaddr = a.dsaddr;
8356         cpos = a.cpos;
8357         cleft = a.cleft;
8358       }
8359       float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds
8360       @property bool empty () const pure { pragma(inline, true); return (cleft == 0); }
8361       @property int length () const pure { pragma(inline, true); return cleft; }
8362       @property Range save () const { pragma(inline, true); Range res = this; return res; }
8363       @property Command front () const {
8364         Command res = void;
8365         if (cleft > 0) {
8366           res.code = cast(Command.Kind)data[cpos];
8367           switch (res.code) {
8368             case Command.Kind.MoveTo:
8369             case Command.Kind.LineTo:
8370               res.args = (cast(const(float*))(data+cpos+1))[0..1*2];
8371               break;
8372             case Command.Kind.QuadTo:
8373               res.args = (cast(const(float*))(data+cpos+1))[0..2*2];
8374               break;
8375             case Command.Kind.BezierTo:
8376               res.args = (cast(const(float*))(data+cpos+1))[0..3*2];
8377               break;
8378             default:
8379               res.code = Command.Kind.End;
8380               res.args = null;
8381               break;
8382           }
8383         } else {
8384           res.code = Command.Kind.End;
8385           res.args = null;
8386         }
8387         return res;
8388       }
8389       void popFront () {
8390         if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command
8391         --cleft;
8392         switch (data[cpos]) {
8393           case Command.Kind.MoveTo:
8394           case Command.Kind.LineTo:
8395             cpos += 1+1*2*cast(uint)float.sizeof;
8396             break;
8397           case Command.Kind.QuadTo:
8398             cpos += 1+2*2*cast(uint)float.sizeof;
8399             break;
8400           case Command.Kind.BezierTo:
8401             cpos += 1+3*2*cast(uint)float.sizeof;
8402             break;
8403           default:
8404             cleft = 0;
8405             break;
8406         }
8407       }
8408     }
8409     if (dsaddr) {
8410       incRef(cast(DataStore*)dsaddr); // range anchors it
8411       return Range(dsaddr, 0, ds.ccount);
8412     } else {
8413       return Range.init;
8414     }
8415   }
8416 }
8417 
8418 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility.
8419 
8420 /// Destroy glyph outiline and free allocated memory.
8421 /// Group: text_api
8422 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc {
8423   pragma(inline, true);
8424   ol.clear();
8425 }
8426 
8427 static if (is(typeof(&fons__nvg__toOutline))) {
8428   public enum NanoVegaHasCharOutline = true; ///
8429 } else {
8430   public enum NanoVegaHasCharOutline = false; ///
8431 }
8432 
8433 /// Returns glyph outlines as array of commands. Vertical 0 is baseline.
8434 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead.
8435 /// Returns `null` if there is no such glyph, or current font is not scalable.
8436 /// Group: text_api
8437 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc {
8438   import core.stdc.stdlib : malloc;
8439   import core.stdc.string : memcpy;
8440   NVGstate* state = nvg__getState(ctx);
8441   ctx.fs.fontId = state.fontId;
8442   auto oline = NVGPathOutline.createNew();
8443   if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear();
8444   return oline;
8445 }
8446 
8447 
8448 float nvg__quantize (float a, float d) pure nothrow @safe @nogc {
8449   pragma(inline, true);
8450   return (cast(int)(a/d+0.5f))*d;
8451 }
8452 
8453 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc {
8454   pragma(inline, true);
8455   return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f);
8456 }
8457 
8458 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc {
8459   int[4] dirty = void;
8460   if (ctx.fs.validateTexture(dirty.ptr)) {
8461     auto fontImage = &ctx.fontImages[ctx.fontImageIdx];
8462     // Update texture
8463     if (fontImage.valid) {
8464       int iw, ih;
8465       const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih);
8466       int x = dirty[0];
8467       int y = dirty[1];
8468       int w = dirty[2]-dirty[0];
8469       int h = dirty[3]-dirty[1];
8470       ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data);
8471     }
8472   }
8473 }
8474 
8475 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc {
8476   int iw, ih;
8477   nvg__flushTextTexture(ctx);
8478   if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false;
8479   // if next fontImage already have a texture
8480   if (ctx.fontImages[ctx.fontImageIdx+1].valid) {
8481     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih);
8482   } else {
8483     // calculate the new font image size and create it
8484     ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih);
8485     if (iw > ih) ih *= 2; else iw *= 2;
8486     if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE;
8487     ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null);
8488     if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) {
8489       ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx;
8490       ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount
8491     }
8492   }
8493   ++ctx.fontImageIdx;
8494   ctx.fs.resetAtlas(iw, ih);
8495   return true;
8496 }
8497 
8498 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc {
8499   NVGstate* state = nvg__getState(ctx);
8500   NVGPaint paint = state.fill;
8501 
8502   // Render triangles.
8503   paint.image = ctx.fontImages[ctx.fontImageIdx];
8504 
8505   // Apply global alpha
8506   paint.innerColor.a *= state.alpha;
8507   paint.middleColor.a *= state.alpha;
8508   paint.outerColor.a *= state.alpha;
8509 
8510   ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
8511 
8512   ++ctx.drawCallCount;
8513   ctx.textTriCount += nverts/3;
8514 }
8515 
8516 /// Draws text string at specified location. Returns next x position.
8517 /// Group: text_api
8518 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8519   NVGstate* state = nvg__getState(ctx);
8520   FONSTextIter!T iter, prevIter;
8521   FONSQuad q;
8522   NVGVertex* verts;
8523   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8524   float invscale = 1.0f/scale;
8525   int cverts = 0;
8526   int nverts = 0;
8527 
8528   if (state.fontId == FONS_INVALID) return x;
8529   if (str.length == 0) return x;
8530 
8531   ctx.fs.size = state.fontSize*scale;
8532   ctx.fs.spacing = state.letterSpacing*scale;
8533   ctx.fs.blur = state.fontBlur*scale;
8534   ctx.fs.textAlign = state.textAlign;
8535   ctx.fs.fontId = state.fontId;
8536 
8537   cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate
8538   verts = nvg__allocTempVerts(ctx, cverts);
8539   if (verts is null) return x;
8540 
8541   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x;
8542   prevIter = iter;
8543   while (iter.next(q)) {
8544     float[4*2] c = void;
8545     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8546       if (nverts != 0) {
8547         // TODO: add back-end bit to do this just once per frame
8548         nvg__flushTextTexture(ctx);
8549         nvg__renderText(ctx, verts, nverts);
8550         nverts = 0;
8551       }
8552       if (!nvg__allocTextAtlas(ctx)) break; // no memory :(
8553       iter = prevIter;
8554       iter.next(q); // try again
8555       if (iter.prevGlyphIndex < 0) {
8556         // still can not find glyph, try replacement
8557         iter = prevIter;
8558         if (!iter.getDummyChar(q)) break;
8559       }
8560     }
8561     prevIter = iter;
8562     // transform corners
8563     state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale);
8564     state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale);
8565     state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale);
8566     state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale);
8567     // create triangles
8568     if (nverts+6 <= cverts) {
8569       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8570       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8571       nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts;
8572       nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts;
8573       nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts;
8574       nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts;
8575     }
8576   }
8577 
8578   // TODO: add back-end bit to do this just once per frame
8579   if (nverts > 0) {
8580     nvg__flushTextTexture(ctx);
8581     nvg__renderText(ctx, verts, nverts);
8582   }
8583 
8584   return iter.nextx/scale;
8585 }
8586 
8587 /** Draws multi-line text string at specified location wrapped at the specified width.
8588  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8589  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8590  *
8591  * Group: text_api
8592  */
8593 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
8594   NVGstate* state = nvg__getState(ctx);
8595   if (state.fontId == FONS_INVALID) return;
8596 
8597   NVGTextRow!T[2] rows;
8598   auto oldAlign = state.textAlign;
8599   scope(exit) state.textAlign = oldAlign;
8600   auto halign = state.textAlign.horizontal;
8601   float lineh = 0;
8602 
8603   ctx.textMetrics(null, null, &lineh);
8604   state.textAlign.horizontal = NVGTextAlign.H.Left;
8605   for (;;) {
8606     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
8607     //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); }
8608     if (rres.length == 0) break;
8609     foreach (ref row; rres) {
8610       final switch (halign) {
8611         case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break;
8612         case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break;
8613         case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break;
8614       }
8615       y += lineh*state.lineHeight;
8616     }
8617     str = rres[$-1].rest;
8618   }
8619 }
8620 
8621 private template isGoodPositionDelegate(DG) {
8622   private DG dg;
8623   static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) ||
8624              is(typeof({ NVGGlyphPosition pos; dg(pos); })))
8625     enum isGoodPositionDelegate = true;
8626   else
8627     enum isGoodPositionDelegate = false;
8628 }
8629 
8630 /** Calculates the glyph x positions of the specified text.
8631  * Measured values are returned in local coordinate space.
8632  *
8633  * Group: text_api
8634  */
8635 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc
8636 if (isAnyCharType!T)
8637 {
8638   if (str.length == 0 || positions.length == 0) return positions[0..0];
8639   usize posnum;
8640   auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) {
8641     positions.ptr[posnum++] = pos;
8642     return (posnum < positions.length);
8643   });
8644   return positions[0..len];
8645 }
8646 
8647 /// Ditto.
8648 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg)
8649 if (isAnyCharType!T && isGoodPositionDelegate!DG)
8650 {
8651   import std.traits : ReturnType;
8652   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8653 
8654   NVGstate* state = nvg__getState(ctx);
8655   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8656   float invscale = 1.0f/scale;
8657   FONSTextIter!T iter, prevIter;
8658   FONSQuad q;
8659   int npos = 0;
8660 
8661   if (str.length == 0) return 0;
8662 
8663   ctx.fs.size = state.fontSize*scale;
8664   ctx.fs.spacing = state.letterSpacing*scale;
8665   ctx.fs.blur = state.fontBlur*scale;
8666   ctx.fs.textAlign = state.textAlign;
8667   ctx.fs.fontId = state.fontId;
8668 
8669   if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos;
8670   prevIter = iter;
8671   while (iter.next(q)) {
8672     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8673       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8674       iter = prevIter;
8675       iter.next(q); // try again
8676       if (iter.prevGlyphIndex < 0) {
8677         // still can not find glyph, try replacement
8678         iter = prevIter;
8679         if (!iter.getDummyChar(q)) break;
8680       }
8681     }
8682     prevIter = iter;
8683     NVGGlyphPosition position = void; //WARNING!
8684     position.strpos = cast(usize)(iter.stringp-str.ptr);
8685     position.x = iter.x*invscale;
8686     position.minx = nvg__min(iter.x, q.x0)*invscale;
8687     position.maxx = nvg__max(iter.nextx, q.x1)*invscale;
8688     ++npos;
8689     static if (RetBool) { if (!dg(position)) return npos; } else dg(position);
8690   }
8691 
8692   return npos;
8693 }
8694 
8695 private template isGoodRowDelegate(CT, DG) {
8696   private DG dg;
8697   static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) ||
8698              is(typeof({ NVGTextRow!CT row; dg(row); })))
8699     enum isGoodRowDelegate = true;
8700   else
8701     enum isGoodRowDelegate = false;
8702 }
8703 
8704 /** Breaks the specified text into lines.
8705  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8706  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8707  *
8708  * Group: text_api
8709  */
8710 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc
8711 if (isAnyCharType!T)
8712 {
8713   if (rows.length == 0) return rows;
8714   if (rows.length > int.max-1) rows = rows[0..int.max-1];
8715   int nrow = 0;
8716   auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) {
8717     rows[nrow++] = row;
8718     return (nrow < rows.length);
8719   });
8720   return rows[0..count];
8721 }
8722 
8723 /** Breaks the specified text into lines.
8724  * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
8725  * Words longer than the max width are slit at nearest character (i.e. no hyphenation).
8726  * Returns number of rows.
8727  *
8728  * Group: text_api
8729  */
8730 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg)
8731 if (isAnyCharType!T && isGoodRowDelegate!(T, DG))
8732 {
8733   import std.traits : ReturnType;
8734   static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true;
8735 
8736   enum NVGcodepointType : int {
8737     Space,
8738     NewLine,
8739     Char,
8740   }
8741 
8742   NVGstate* state = nvg__getState(ctx);
8743   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8744   float invscale = 1.0f/scale;
8745   FONSTextIter!T iter, prevIter;
8746   FONSQuad q;
8747   int nrows = 0;
8748   float rowStartX = 0;
8749   float rowWidth = 0;
8750   float rowMinX = 0;
8751   float rowMaxX = 0;
8752   int rowStart = 0;
8753   int rowEnd = 0;
8754   int wordStart = 0;
8755   float wordStartX = 0;
8756   float wordMinX = 0;
8757   int breakEnd = 0;
8758   float breakWidth = 0;
8759   float breakMaxX = 0;
8760   int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space;
8761   uint pcodepoint = 0;
8762 
8763   if (state.fontId == FONS_INVALID) return 0;
8764   if (str.length == 0 || dg is null) return 0;
8765 
8766   ctx.fs.size = state.fontSize*scale;
8767   ctx.fs.spacing = state.letterSpacing*scale;
8768   ctx.fs.blur = state.fontBlur*scale;
8769   ctx.fs.textAlign = state.textAlign;
8770   ctx.fs.fontId = state.fontId;
8771 
8772   breakRowWidth *= scale;
8773 
8774   enum Phase {
8775     Normal, // searching for breaking point
8776     SkipBlanks, // skip leading blanks
8777   }
8778   Phase phase = Phase.SkipBlanks; // don't skip blanks on first line
8779 
8780   if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0;
8781   prevIter = iter;
8782   while (iter.next(q)) {
8783     if (iter.prevGlyphIndex < 0) { // can not retrieve glyph?
8784       if (!nvg__allocTextAtlas(ctx)) break; // no memory
8785       iter = prevIter;
8786       iter.next(q); // try again
8787       if (iter.prevGlyphIndex < 0) {
8788         // still can not find glyph, try replacement
8789         iter = prevIter;
8790         if (!iter.getDummyChar(q)) break;
8791       }
8792     }
8793     prevIter = iter;
8794     switch (iter.codepoint) {
8795       case 9: // \t
8796       case 11: // \v
8797       case 12: // \f
8798       case 32: // space
8799       case 0x00a0: // NBSP
8800         type = NVGcodepointType.Space;
8801         break;
8802       case 10: // \n
8803         type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8804         break;
8805       case 13: // \r
8806         type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine);
8807         break;
8808       case 0x0085: // NEL
8809       case 0x2028: // Line Separator
8810       case 0x2029: // Paragraph Separator
8811         type = NVGcodepointType.NewLine;
8812         break;
8813       default:
8814         type = NVGcodepointType.Char;
8815         break;
8816     }
8817     if (phase == Phase.SkipBlanks) {
8818       // fix row start
8819       rowStart = cast(int)(iter.stringp-str.ptr);
8820       rowEnd = rowStart;
8821       rowStartX = iter.x;
8822       rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX;
8823       rowMinX = q.x0-rowStartX;
8824       rowMaxX = q.x1-rowStartX;
8825       wordStart = rowStart;
8826       wordStartX = iter.x;
8827       wordMinX = q.x0-rowStartX;
8828       breakEnd = rowStart;
8829       breakWidth = 0.0;
8830       breakMaxX = 0.0;
8831       if (type == NVGcodepointType.Space) continue;
8832       phase = Phase.Normal;
8833     }
8834 
8835     if (type == NVGcodepointType.NewLine) {
8836       // always handle new lines
8837       NVGTextRow!T row;
8838       row.string = str;
8839       row.start = rowStart;
8840       row.end = rowEnd;
8841       row.width = rowWidth*invscale;
8842       row.minx = rowMinX*invscale;
8843       row.maxx = rowMaxX*invscale;
8844       ++nrows;
8845       static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8846       phase = Phase.SkipBlanks;
8847     } else {
8848       float nextWidth = iter.nextx-rowStartX;
8849       // track last non-white space character
8850       if (type == NVGcodepointType.Char) {
8851         rowEnd = cast(int)(iter.nextp-str.ptr);
8852         rowWidth = iter.nextx-rowStartX;
8853         rowMaxX = q.x1-rowStartX;
8854       }
8855       // track last end of a word
8856       if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) {
8857         breakEnd = cast(int)(iter.stringp-str.ptr);
8858         breakWidth = rowWidth;
8859         breakMaxX = rowMaxX;
8860       }
8861       // track last beginning of a word
8862       if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) {
8863         wordStart = cast(int)(iter.stringp-str.ptr);
8864         wordStartX = iter.x;
8865         wordMinX = q.x0-rowStartX;
8866       }
8867       // break to new line when a character is beyond break width
8868       if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) {
8869         // the run length is too long, need to break to new line
8870         NVGTextRow!T row;
8871         row.string = str;
8872         if (breakEnd == rowStart) {
8873           // the current word is longer than the row length, just break it from here
8874           row.start = rowStart;
8875           row.end = cast(int)(iter.stringp-str.ptr);
8876           row.width = rowWidth*invscale;
8877           row.minx = rowMinX*invscale;
8878           row.maxx = rowMaxX*invscale;
8879           ++nrows;
8880           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8881           rowStartX = iter.x;
8882           rowStart = cast(int)(iter.stringp-str.ptr);
8883           rowEnd = cast(int)(iter.nextp-str.ptr);
8884           rowWidth = iter.nextx-rowStartX;
8885           rowMinX = q.x0-rowStartX;
8886           rowMaxX = q.x1-rowStartX;
8887           wordStart = rowStart;
8888           wordStartX = iter.x;
8889           wordMinX = q.x0-rowStartX;
8890         } else {
8891           // break the line from the end of the last word, and start new line from the beginning of the new
8892           //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); }
8893           row.start = rowStart;
8894           row.end = breakEnd;
8895           row.width = breakWidth*invscale;
8896           row.minx = rowMinX*invscale;
8897           row.maxx = breakMaxX*invscale;
8898           ++nrows;
8899           static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8900           rowStartX = wordStartX;
8901           rowStart = wordStart;
8902           rowEnd = cast(int)(iter.nextp-str.ptr);
8903           rowWidth = iter.nextx-rowStartX;
8904           rowMinX = wordMinX;
8905           rowMaxX = q.x1-rowStartX;
8906           // no change to the word start
8907         }
8908         // set null break point
8909         breakEnd = rowStart;
8910         breakWidth = 0.0;
8911         breakMaxX = 0.0;
8912       }
8913     }
8914 
8915     pcodepoint = iter.codepoint;
8916     ptype = type;
8917   }
8918 
8919   // break the line from the end of the last word, and start new line from the beginning of the new
8920   if (phase != Phase.SkipBlanks && rowStart < str.length) {
8921     //{ import core.stdc.stdio : printf; printf("  rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); }
8922     NVGTextRow!T row;
8923     row.string = str;
8924     row.start = rowStart;
8925     row.end = cast(int)str.length;
8926     row.width = rowWidth*invscale;
8927     row.minx = rowMinX*invscale;
8928     row.maxx = rowMaxX*invscale;
8929     ++nrows;
8930     static if (RetBool) { if (!dg(row)) return nrows; } else dg(row);
8931   }
8932 
8933   return nrows;
8934 }
8935 
8936 /** Returns iterator which you can use to calculate text bounds and advancement.
8937  * This is usable when you need to do some text layouting with wrapping, to avoid
8938  * guesswork ("will advancement for this space stay the same?"), and Schlemiel's
8939  * algorithm. Note that you can copy the returned struct to save iterator state.
8940  *
8941  * You can check if iterator is valid with [valid] property, put new chars with
8942  * [put] method, get current advance with [advance] property, and current
8943  * bounds with `getBounds(ref float[4] bounds)` method.
8944  *
8945  * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.)
8946  *
8947  * Group: text_api
8948  */
8949 public struct TextBoundsIterator {
8950 private:
8951   NVGContext ctx;
8952   FONSTextBoundsIterator fsiter; // fontstash iterator
8953   float scale, invscale, xscaled, yscaled;
8954   // font settings
8955   float fsSize, fsSpacing, fsBlur;
8956   int fsFontId;
8957   NVGTextAlign fsAlign;
8958 
8959 public:
8960   /// Setups iteration. Takes current font parameters from the given NanoVega context.
8961   this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); }
8962 
8963   /// Resets iteration. Takes current font parameters from the given NanoVega context.
8964   void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc {
8965     fsiter = fsiter.init;
8966     this = this.init;
8967     if (actx is null) return;
8968     NVGstate* state = nvg__getState(actx);
8969     if (state is null) return;
8970     if (state.fontId == FONS_INVALID) { ctx = null; return; }
8971 
8972     ctx = actx;
8973     scale = nvg__getFontScale(state)*ctx.devicePxRatio;
8974     invscale = 1.0f/scale;
8975 
8976     fsSize = state.fontSize*scale;
8977     fsSpacing = state.letterSpacing*scale;
8978     fsBlur = state.fontBlur*scale;
8979     fsAlign = state.textAlign;
8980     fsFontId = state.fontId;
8981     restoreFont();
8982 
8983     xscaled = ax*scale;
8984     yscaled = ay*scale;
8985     fsiter.reset(ctx.fs, xscaled, yscaled);
8986   }
8987 
8988   /// Restart iteration. Will not restore font.
8989   void restart () nothrow @trusted @nogc {
8990     if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled);
8991   }
8992 
8993   /// Restore font settings for the context.
8994   void restoreFont () nothrow @trusted @nogc {
8995     if (ctx !is null) {
8996       ctx.fs.size = fsSize;
8997       ctx.fs.spacing = fsSpacing;
8998       ctx.fs.blur = fsBlur;
8999       ctx.fs.textAlign = fsAlign;
9000       ctx.fs.fontId = fsFontId;
9001     }
9002   }
9003 
9004   /// Is this iterator valid?
9005   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); }
9006 
9007   /// Add chars.
9008   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); }
9009 
9010   /// Returns current advance
9011   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); }
9012 
9013   /// Returns current text bounds.
9014   void getBounds (ref float[4] bounds) nothrow @trusted @nogc {
9015     if (ctx !is null) {
9016       fsiter.getBounds(bounds);
9017       ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]);
9018       bounds[0] *= invscale;
9019       bounds[1] *= invscale;
9020       bounds[2] *= invscale;
9021       bounds[3] *= invscale;
9022     } else {
9023       bounds[] = 0;
9024     }
9025   }
9026 
9027   /// Returns current horizontal text bounds.
9028   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
9029     if (ctx !is null) {
9030       fsiter.getHBounds(xmin, xmax);
9031       xmin *= invscale;
9032       xmax *= invscale;
9033     }
9034   }
9035 
9036   /// Returns current vertical text bounds.
9037   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
9038     if (ctx !is null) {
9039       //fsiter.getVBounds(ymin, ymax);
9040       ctx.fs.getLineBounds(yscaled, &ymin, &ymax);
9041       ymin *= invscale;
9042       ymax *= invscale;
9043     }
9044   }
9045 }
9046 
9047 /// Returns font line height (without line spacing), measured in local coordinate space.
9048 /// Group: text_api
9049 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc {
9050   float res = void;
9051   ctx.textMetrics(null, null, &res);
9052   return res;
9053 }
9054 
9055 /// Returns font ascender (positive), measured in local coordinate space.
9056 /// Group: text_api
9057 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc {
9058   float res = void;
9059   ctx.textMetrics(&res, null, null);
9060   return res;
9061 }
9062 
9063 /// Returns font descender (negative), measured in local coordinate space.
9064 /// Group: text_api
9065 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc {
9066   float res = void;
9067   ctx.textMetrics(null, &res, null);
9068   return res;
9069 }
9070 
9071 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text.
9072  * Measured values are returned in local coordinate space.
9073  *
9074  * Group: text_api
9075  */
9076 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) {
9077   float[4] bnd = void;
9078   ctx.textBounds(0, 0, str, bnd[]);
9079   if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) {
9080     if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]);
9081     if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]);
9082   } else {
9083     if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0];
9084     if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1];
9085   }
9086 }
9087 
9088 /** Measures the specified text string. Returns horizontal size of the measured text.
9089  * Measured values are returned in local coordinate space.
9090  *
9091  * Group: text_api
9092  */
9093 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) {
9094   float w = void;
9095   ctx.textExtents(str, &w, null);
9096   return w;
9097 }
9098 
9099 /** Measures the specified text string. Parameter bounds should be a float[4],
9100  * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
9101  * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
9102  * Measured values are returned in local coordinate space.
9103  *
9104  * Group: text_api
9105  */
9106 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc
9107 if (isAnyCharType!T)
9108 {
9109   NVGstate* state = nvg__getState(ctx);
9110 
9111   if (state.fontId == FONS_INVALID) {
9112     bounds[] = 0;
9113     return 0;
9114   }
9115 
9116   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9117   ctx.fs.size = state.fontSize*scale;
9118   ctx.fs.spacing = state.letterSpacing*scale;
9119   ctx.fs.blur = state.fontBlur*scale;
9120   ctx.fs.textAlign = state.textAlign;
9121   ctx.fs.fontId = state.fontId;
9122 
9123   float[4] b = void;
9124   immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]);
9125   immutable float invscale = 1.0f/scale;
9126   if (bounds.length) {
9127     // use line bounds for height
9128     ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3);
9129     if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale;
9130     if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale;
9131     if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale;
9132     if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale;
9133   }
9134   return width*invscale;
9135 }
9136 
9137 /// Ditto.
9138 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) {
9139   NVGstate* state = nvg__getState(ctx);
9140   NVGTextRow!T[2] rows;
9141   float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9142   float invscale = 1.0f/scale;
9143   float lineh = 0, rminy = 0, rmaxy = 0;
9144   float minx, miny, maxx, maxy;
9145 
9146   if (state.fontId == FONS_INVALID) {
9147     bounds[] = 0;
9148     return;
9149   }
9150 
9151   auto oldAlign = state.textAlign;
9152   scope(exit) state.textAlign = oldAlign;
9153   auto halign = state.textAlign.horizontal;
9154 
9155   ctx.textMetrics(null, null, &lineh);
9156   state.textAlign.horizontal = NVGTextAlign.H.Left;
9157 
9158   minx = maxx = x;
9159   miny = maxy = y;
9160 
9161   ctx.fs.size = state.fontSize*scale;
9162   ctx.fs.spacing = state.letterSpacing*scale;
9163   ctx.fs.blur = state.fontBlur*scale;
9164   ctx.fs.textAlign = state.textAlign;
9165   ctx.fs.fontId = state.fontId;
9166   ctx.fs.getLineBounds(0, &rminy, &rmaxy);
9167   rminy *= invscale;
9168   rmaxy *= invscale;
9169 
9170   for (;;) {
9171     auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]);
9172     if (rres.length == 0) break;
9173     foreach (ref row; rres) {
9174       float rminx, rmaxx, dx = 0;
9175       // horizontal bounds
9176       final switch (halign) {
9177         case NVGTextAlign.H.Left: dx = 0; break;
9178         case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break;
9179         case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break;
9180       }
9181       rminx = x+row.minx+dx;
9182       rmaxx = x+row.maxx+dx;
9183       minx = nvg__min(minx, rminx);
9184       maxx = nvg__max(maxx, rmaxx);
9185       // vertical bounds
9186       miny = nvg__min(miny, y+rminy);
9187       maxy = nvg__max(maxy, y+rmaxy);
9188       y += lineh*state.lineHeight;
9189     }
9190     str = rres[$-1].rest;
9191   }
9192 
9193   if (bounds.length) {
9194     if (bounds.length > 0) bounds.ptr[0] = minx;
9195     if (bounds.length > 1) bounds.ptr[1] = miny;
9196     if (bounds.length > 2) bounds.ptr[2] = maxx;
9197     if (bounds.length > 3) bounds.ptr[3] = maxy;
9198   }
9199 }
9200 
9201 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space.
9202 /// Group: text_api
9203 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
9204   NVGstate* state = nvg__getState(ctx);
9205 
9206   if (state.fontId == FONS_INVALID) {
9207     if (ascender !is null) *ascender *= 0;
9208     if (descender !is null) *descender *= 0;
9209     if (lineh !is null) *lineh *= 0;
9210     return;
9211   }
9212 
9213   immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio;
9214   immutable float invscale = 1.0f/scale;
9215 
9216   ctx.fs.size = state.fontSize*scale;
9217   ctx.fs.spacing = state.letterSpacing*scale;
9218   ctx.fs.blur = state.fontBlur*scale;
9219   ctx.fs.textAlign = state.textAlign;
9220   ctx.fs.fontId = state.fontId;
9221 
9222   ctx.fs.getVertMetrics(ascender, descender, lineh);
9223   if (ascender !is null) *ascender *= invscale;
9224   if (descender !is null) *descender *= invscale;
9225   if (lineh !is null) *lineh *= invscale;
9226 }
9227 
9228 
9229 // ////////////////////////////////////////////////////////////////////////// //
9230 // fontstash
9231 // ////////////////////////////////////////////////////////////////////////// //
9232 import core.stdc.stdlib : malloc, realloc, free;
9233 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen;
9234 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET;
9235 
9236 public:
9237 // welcome to version hell!
9238 version(nanovg_force_stb_ttf) {
9239 } else {
9240   version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; }
9241 }
9242 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false;
9243 //version(nanovg_ignore_mono);
9244 
9245 version(nanovg_force_stb_ttf) {
9246   private enum NanoVegaForceFreeType = false;
9247 } else {
9248   version (nanovg_builtin_freetype_bindings) {
9249     version(Posix) {
9250       private enum NanoVegaForceFreeType = true;
9251     } else {
9252       private enum NanoVegaForceFreeType = false;
9253     }
9254   } else {
9255     version(Posix) {
9256       private enum NanoVegaForceFreeType = true;
9257     } else {
9258       private enum NanoVegaForceFreeType = false;
9259     }
9260   }
9261 }
9262 
9263 version(nanovg_use_freetype_ii) {
9264   enum NanoVegaIsUsingSTBTTF = false;
9265   //pragma(msg, "iv.freetype: forced");
9266 } else {
9267   static if (NanoVegaForceFreeType) {
9268     enum NanoVegaIsUsingSTBTTF = false;
9269   } else {
9270     static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) {
9271       import iv.stb.ttf;
9272       enum NanoVegaIsUsingSTBTTF = true;
9273       version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf");
9274     } else static if (__traits(compiles, { import arsd.ttf; })) {
9275       import arsd.ttf;
9276       enum NanoVegaIsUsingSTBTTF = true;
9277       version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf");
9278     } else static if (__traits(compiles, { import stb_truetype; })) {
9279       import stb_truetype;
9280       enum NanoVegaIsUsingSTBTTF = true;
9281       version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype");
9282     } else static if (__traits(compiles, { import iv.freetype; })) {
9283       version (nanovg_builtin_freetype_bindings) {
9284         enum NanoVegaIsUsingSTBTTF = false;
9285         version = nanovg_builtin_freetype_bindings;
9286       } else {
9287         import iv.freetype;
9288         enum NanoVegaIsUsingSTBTTF = false;
9289       }
9290       version(nanovg_report_stb_ttf) pragma(msg, "freetype");
9291     } else {
9292       static assert(0, "no stb_ttf/iv.freetype found!");
9293     }
9294   }
9295 }
9296 
9297 
9298 // ////////////////////////////////////////////////////////////////////////// //
9299 //version = nanovg_ft_mono;
9300 
9301 /// Invald font id.
9302 /// Group: font_stash
9303 public enum FONS_INVALID = -1;
9304 
9305 public enum FONSBitmapFlag : uint {
9306   Required = 0,
9307   Optional = 1,
9308 }
9309 
9310 public enum FONSError : int {
9311   NoError = 0,
9312   AtlasFull = 1, // Font atlas is full.
9313   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.
9314   StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
9315   StatesUnderflow = 4, // Trying to pop too many states fonsPopState().
9316 }
9317 
9318 /// Initial parameters for new FontStash.
9319 /// Group: font_stash
9320 public struct FONSParams {
9321   enum Flag : uint {
9322     ZeroTopLeft    = 0U, // default
9323     ZeroBottomLeft = 1U,
9324   }
9325   int width, height;
9326   Flag flags = Flag.ZeroTopLeft;
9327   void* userPtr;
9328   bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate;
9329   int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize;
9330   void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate;
9331   void function (void* uptr) nothrow @trusted @nogc renderDelete;
9332   @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); }
9333 }
9334 
9335 //TODO: document this
9336 public struct FONSQuad {
9337   float x0=0, y0=0, s0=0, t0=0;
9338   float x1=0, y1=0, s1=0, t1=0;
9339 }
9340 
9341 //TODO: document this
9342 public struct FONSTextIter(CT) if (isAnyCharType!CT) {
9343   alias CharType = CT;
9344   float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0;
9345   uint codepoint;
9346   short isize, iblur;
9347   FONSContext stash;
9348   FONSfont* font;
9349   int prevGlyphIndex;
9350   const(CT)* s; // string
9351   const(CT)* n; // next
9352   const(CT)* e; // end
9353   FONSBitmapFlag bitmapOption;
9354   static if (is(CT == char)) {
9355     uint utf8state;
9356   }
9357 
9358   this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); }
9359   ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; }
9360 
9361   @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; }
9362   @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; }
9363   @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; }
9364 
9365   bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc {
9366     import core.stdc.string : memset;
9367 
9368     memset(&this, 0, this.sizeof);
9369     if (astash is null) return false;
9370 
9371     FONSstate* state = astash.getState;
9372 
9373     if (state.font < 0 || state.font >= astash.nfonts) return false;
9374     font = astash.fonts[state.font];
9375     if (font is null || font.fdata is null) return false;
9376 
9377     isize = cast(short)(state.size*10.0f);
9378     iblur = cast(short)state.blur;
9379     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
9380 
9381     // align horizontally
9382     if (state.talign.left) {
9383       // empty
9384     } else if (state.talign.right) {
9385       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9386       ax -= width;
9387     } else if (state.talign.center) {
9388       immutable float width = astash.getTextBounds(ax, ay, astr, null);
9389       ax -= width*0.5f;
9390     }
9391 
9392     // align vertically
9393     ay += astash.getVertAlign(font, state.talign, isize);
9394 
9395     x = nextx = ax;
9396     y = nexty = ay;
9397     spacing = state.spacing;
9398 
9399     if (astr.ptr is null) {
9400            static if (is(CharType == char)) astr = "";
9401       else static if (is(CharType == wchar)) astr = ""w;
9402       else static if (is(CharType == dchar)) astr = ""d;
9403       else static assert(0, "wtf?!");
9404     }
9405     s = astr.ptr;
9406     n = astr.ptr;
9407     e = astr.ptr+astr.length;
9408 
9409     codepoint = 0;
9410     prevGlyphIndex = -1;
9411     bitmapOption = abitmapOption;
9412     stash = astash;
9413 
9414     return true;
9415   }
9416 
9417   bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc {
9418     if (stash is null || font is null) return false;
9419     // get glyph and quad
9420     x = nextx;
9421     y = nexty;
9422     FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption);
9423     if (glyph !is null) {
9424       stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9425       prevGlyphIndex = glyph.index;
9426       return true;
9427     } else {
9428       prevGlyphIndex = -1;
9429       return false;
9430     }
9431   }
9432 
9433   bool next (ref FONSQuad quad) nothrow @trusted @nogc {
9434     if (stash is null || font is null) return false;
9435     FONSglyph* glyph = null;
9436     static if (is(CharType == char)) {
9437       const(char)* str = this.n;
9438       this.s = this.n;
9439       if (str is this.e) return false;
9440       const(char)* e = this.e;
9441       for (; str !is e; ++str) {
9442         /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/
9443         mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str"));
9444         if (utf8state) continue;
9445         ++str; // 'cause we'll break anyway
9446         // get glyph and quad
9447         x = nextx;
9448         y = nexty;
9449         glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9450         if (glyph !is null) {
9451           stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9452           prevGlyphIndex = glyph.index;
9453         } else {
9454           prevGlyphIndex = -1;
9455         }
9456         break;
9457       }
9458       this.n = str;
9459     } else {
9460       const(CharType)* str = this.n;
9461       this.s = this.n;
9462       if (str is this.e) return false;
9463       codepoint = cast(uint)(*str++);
9464       if (codepoint > dchar.max) codepoint = 0xFFFD;
9465       // get glyph and quad
9466       x = nextx;
9467       y = nexty;
9468       glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption);
9469       if (glyph !is null) {
9470         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad);
9471         prevGlyphIndex = glyph.index;
9472       } else {
9473         prevGlyphIndex = -1;
9474       }
9475       this.n = str;
9476     }
9477     return true;
9478   }
9479 }
9480 
9481 
9482 // ////////////////////////////////////////////////////////////////////////// //
9483 //static if (!HasAST) version = nanovg_use_freetype_ii_x;
9484 
9485 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) {
9486 version(nanovg_builtin_freetype_bindings) {
9487 pragma(lib, "freetype");
9488 private extern(C) nothrow @trusted @nogc {
9489 private import core.stdc.config : c_long, c_ulong;
9490 alias FT_Pos = c_long;
9491 // config/ftconfig.h
9492 alias FT_Int16 = short;
9493 alias FT_UInt16 = ushort;
9494 alias FT_Int32 = int;
9495 alias FT_UInt32 = uint;
9496 alias FT_Fast = int;
9497 alias FT_UFast = uint;
9498 alias FT_Int64 = long;
9499 alias FT_Uint64 = ulong;
9500 // fttypes.h
9501 alias FT_Bool = ubyte;
9502 alias FT_FWord = short;
9503 alias FT_UFWord = ushort;
9504 alias FT_Char = char;
9505 alias FT_Byte = ubyte;
9506 alias FT_Bytes = FT_Byte*;
9507 alias FT_Tag = FT_UInt32;
9508 alias FT_String = char;
9509 alias FT_Short = short;
9510 alias FT_UShort = ushort;
9511 alias FT_Int = int;
9512 alias FT_UInt = uint;
9513 alias FT_Long = c_long;
9514 alias FT_ULong = c_ulong;
9515 alias FT_F2Dot14 = short;
9516 alias FT_F26Dot6 = c_long;
9517 alias FT_Fixed = c_long;
9518 alias FT_Error = int;
9519 alias FT_Pointer = void*;
9520 alias FT_Offset = usize;
9521 alias FT_PtrDist = ptrdiff_t;
9522 
9523 struct FT_UnitVector {
9524   FT_F2Dot14 x;
9525   FT_F2Dot14 y;
9526 }
9527 
9528 struct FT_Matrix {
9529   FT_Fixed xx, xy;
9530   FT_Fixed yx, yy;
9531 }
9532 
9533 struct FT_Data {
9534   const(FT_Byte)* pointer;
9535   FT_Int length;
9536 }
9537 alias FT_Face = FT_FaceRec*;
9538 struct FT_FaceRec {
9539   FT_Long num_faces;
9540   FT_Long face_index;
9541   FT_Long face_flags;
9542   FT_Long style_flags;
9543   FT_Long num_glyphs;
9544   FT_String* family_name;
9545   FT_String* style_name;
9546   FT_Int num_fixed_sizes;
9547   FT_Bitmap_Size* available_sizes;
9548   FT_Int num_charmaps;
9549   FT_CharMap* charmaps;
9550   FT_Generic generic;
9551   FT_BBox bbox;
9552   FT_UShort units_per_EM;
9553   FT_Short ascender;
9554   FT_Short descender;
9555   FT_Short height;
9556   FT_Short max_advance_width;
9557   FT_Short max_advance_height;
9558   FT_Short underline_position;
9559   FT_Short underline_thickness;
9560   FT_GlyphSlot glyph;
9561   FT_Size size;
9562   FT_CharMap charmap;
9563   FT_Driver driver;
9564   FT_Memory memory;
9565   FT_Stream stream;
9566   FT_ListRec sizes_list;
9567   FT_Generic autohint;
9568   void* extensions;
9569   FT_Face_Internal internal;
9570 }
9571 struct FT_Bitmap_Size {
9572   FT_Short height;
9573   FT_Short width;
9574   FT_Pos size;
9575   FT_Pos x_ppem;
9576   FT_Pos y_ppem;
9577 }
9578 alias FT_CharMap = FT_CharMapRec*;
9579 struct FT_CharMapRec {
9580   FT_Face face;
9581   FT_Encoding encoding;
9582   FT_UShort platform_id;
9583   FT_UShort encoding_id;
9584 }
9585 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); }
9586 struct FT_Generic {
9587   void* data;
9588   FT_Generic_Finalizer finalizer;
9589 }
9590 struct FT_Vector {
9591   FT_Pos x;
9592   FT_Pos y;
9593 }
9594 struct FT_BBox {
9595   FT_Pos xMin, yMin;
9596   FT_Pos xMax, yMax;
9597 }
9598 alias FT_Pixel_Mode = int;
9599 enum {
9600   FT_PIXEL_MODE_NONE = 0,
9601   FT_PIXEL_MODE_MONO,
9602   FT_PIXEL_MODE_GRAY,
9603   FT_PIXEL_MODE_GRAY2,
9604   FT_PIXEL_MODE_GRAY4,
9605   FT_PIXEL_MODE_LCD,
9606   FT_PIXEL_MODE_LCD_V,
9607   FT_PIXEL_MODE_MAX
9608 }
9609 struct FT_Bitmap {
9610   uint rows;
9611   uint width;
9612   int pitch;
9613   ubyte* buffer;
9614   ushort num_grays;
9615   ubyte pixel_mode;
9616   ubyte palette_mode;
9617   void* palette;
9618 }
9619 struct FT_Outline {
9620   short n_contours;
9621   short n_points;
9622   FT_Vector* points;
9623   byte* tags;
9624   short* contours;
9625   int flags;
9626 }
9627 alias FT_GlyphSlot = FT_GlyphSlotRec*;
9628 struct FT_GlyphSlotRec {
9629   FT_Library library;
9630   FT_Face face;
9631   FT_GlyphSlot next;
9632   FT_UInt reserved;
9633   FT_Generic generic;
9634   FT_Glyph_Metrics metrics;
9635   FT_Fixed linearHoriAdvance;
9636   FT_Fixed linearVertAdvance;
9637   FT_Vector advance;
9638   FT_Glyph_Format format;
9639   FT_Bitmap bitmap;
9640   FT_Int bitmap_left;
9641   FT_Int bitmap_top;
9642   FT_Outline outline;
9643   FT_UInt num_subglyphs;
9644   FT_SubGlyph subglyphs;
9645   void* control_data;
9646   c_long control_len;
9647   FT_Pos lsb_delta;
9648   FT_Pos rsb_delta;
9649   void* other;
9650   FT_Slot_Internal internal;
9651 }
9652 alias FT_Size = FT_SizeRec*;
9653 struct FT_SizeRec {
9654   FT_Face face;
9655   FT_Generic generic;
9656   FT_Size_Metrics metrics;
9657   FT_Size_Internal internal;
9658 }
9659 alias FT_Encoding = FT_Tag;
9660 alias FT_Face_Internal = void*;
9661 alias FT_Driver = void*;
9662 alias FT_Memory = void*;
9663 alias FT_Stream = void*;
9664 alias FT_Library = void*;
9665 alias FT_SubGlyph = void*;
9666 alias FT_Slot_Internal = void*;
9667 alias FT_Size_Internal = void*;
9668 alias FT_ListNode = FT_ListNodeRec*;
9669 alias FT_List = FT_ListRec*;
9670 struct FT_ListNodeRec {
9671   FT_ListNode prev;
9672   FT_ListNode next;
9673   void* data;
9674 }
9675 struct FT_ListRec {
9676   FT_ListNode head;
9677   FT_ListNode tail;
9678 }
9679 struct FT_Glyph_Metrics {
9680   FT_Pos width;
9681   FT_Pos height;
9682   FT_Pos horiBearingX;
9683   FT_Pos horiBearingY;
9684   FT_Pos horiAdvance;
9685   FT_Pos vertBearingX;
9686   FT_Pos vertBearingY;
9687   FT_Pos vertAdvance;
9688 }
9689 alias FT_Glyph_Format = FT_Tag;
9690 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc {
9691   pragma(inline, true);
9692   return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4);
9693 }
9694 enum : FT_Tag {
9695   FT_GLYPH_FORMAT_NONE = 0,
9696   FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'),
9697   FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'),
9698   FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'),
9699   FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'),
9700 }
9701 struct FT_Size_Metrics {
9702   FT_UShort x_ppem;
9703   FT_UShort y_ppem;
9704 
9705   FT_Fixed x_scale;
9706   FT_Fixed y_scale;
9707 
9708   FT_Pos ascender;
9709   FT_Pos descender;
9710   FT_Pos height;
9711   FT_Pos max_advance;
9712 }
9713 enum FT_LOAD_DEFAULT = 0x0U;
9714 enum FT_LOAD_NO_SCALE = 1U<<0;
9715 enum FT_LOAD_NO_HINTING = 1U<<1;
9716 enum FT_LOAD_RENDER = 1U<<2;
9717 enum FT_LOAD_NO_BITMAP = 1U<<3;
9718 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4;
9719 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5;
9720 enum FT_LOAD_CROP_BITMAP = 1U<<6;
9721 enum FT_LOAD_PEDANTIC = 1U<<7;
9722 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9;
9723 enum FT_LOAD_NO_RECURSE = 1U<<10;
9724 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11;
9725 enum FT_LOAD_MONOCHROME = 1U<<12;
9726 enum FT_LOAD_LINEAR_DESIGN = 1U<<13;
9727 enum FT_LOAD_NO_AUTOHINT = 1U<<15;
9728 enum FT_LOAD_COLOR = 1U<<20;
9729 enum FT_LOAD_COMPUTE_METRICS = 1U<<21;
9730 enum FT_FACE_FLAG_KERNING = 1U<<6;
9731 alias FT_Kerning_Mode = int;
9732 enum /*FT_Kerning_Mode*/ {
9733   FT_KERNING_DEFAULT = 0,
9734   FT_KERNING_UNFITTED,
9735   FT_KERNING_UNSCALED
9736 }
9737 extern(C) nothrow @nogc {
9738   alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*);
9739   alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*);
9740   alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*);
9741   alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*);
9742 }
9743 struct FT_Outline_Funcs {
9744   FT_Outline_MoveToFunc move_to;
9745   FT_Outline_LineToFunc line_to;
9746   FT_Outline_ConicToFunc conic_to;
9747   FT_Outline_CubicToFunc cubic_to;
9748   int shift;
9749   FT_Pos delta;
9750 }
9751 
9752 FT_Error FT_Init_FreeType (FT_Library*);
9753 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*);
9754 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong);
9755 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt);
9756 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32);
9757 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*);
9758 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*);
9759 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*);
9760 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*);
9761 }
9762 } else version(bindbc) {
9763   import bindbc.freetype;
9764   alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT;
9765   alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED;
9766   alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED;
9767 } else {
9768   import iv.freetype;
9769 }
9770 
9771 struct FONSttFontImpl {
9772   FT_Face font;
9773   bool mono; // no aa?
9774 }
9775 
9776 __gshared FT_Library ftLibrary;
9777 
9778 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
9779   FT_Error ftError;
9780   //FONS_NOTUSED(context);
9781   ftError = FT_Init_FreeType(&ftLibrary);
9782   return (ftError == 0);
9783 }
9784 
9785 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
9786   font.mono = v;
9787 }
9788 
9789 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
9790   return font.mono;
9791 }
9792 
9793 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
9794   FT_Error ftError;
9795   //font.font.userdata = stash;
9796   ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font);
9797   return ftError == 0;
9798 }
9799 
9800 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
9801   *ascent = font.font.ascender;
9802   *descent = font.font.descender;
9803   *lineGap = font.font.height-(*ascent - *descent);
9804 }
9805 
9806 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
9807   return size/(font.font.ascender-font.font.descender);
9808 }
9809 
9810 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
9811   return FT_Get_Char_Index(font.font, codepoint);
9812 }
9813 
9814 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 {
9815   FT_Error ftError;
9816   FT_GlyphSlot ftGlyph;
9817   //version(nanovg_ignore_mono) enum exflags = 0;
9818   //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0;
9819   uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0);
9820   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)));
9821   if (ftError) return 0;
9822   ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags);
9823   if (ftError) return 0;
9824   ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance);
9825   if (ftError) return 0;
9826   ftGlyph = font.font.glyph;
9827   *lsb = cast(int)ftGlyph.metrics.horiBearingX;
9828   *x0 = ftGlyph.bitmap_left;
9829   *x1 = *x0+ftGlyph.bitmap.width;
9830   *y0 = -ftGlyph.bitmap_top;
9831   *y1 = *y0+ftGlyph.bitmap.rows;
9832   return 1;
9833 }
9834 
9835 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
9836   FT_GlyphSlot ftGlyph = font.font.glyph;
9837   //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
9838   //version(nanovg_ignore_mono) enum RenderAA = true;
9839   //else version(nanovg_ft_mono) enum RenderAA = false;
9840   //else enum RenderAA = true;
9841   if (font.mono) {
9842     auto src = ftGlyph.bitmap.buffer;
9843     auto dst = output;
9844     auto spt = ftGlyph.bitmap.pitch;
9845     if (spt < 0) spt = -spt;
9846     foreach (int y; 0..ftGlyph.bitmap.rows) {
9847       ubyte count = 0, b = 0;
9848       auto s = src;
9849       auto d = dst;
9850       foreach (int x; 0..ftGlyph.bitmap.width) {
9851         if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
9852         *d++ = (b&0x80 ? 255 : 0);
9853       }
9854       src += spt;
9855       dst += outStride;
9856     }
9857   } else {
9858     auto src = ftGlyph.bitmap.buffer;
9859     auto dst = output;
9860     auto spt = ftGlyph.bitmap.pitch;
9861     if (spt < 0) spt = -spt;
9862     foreach (int y; 0..ftGlyph.bitmap.rows) {
9863       import core.stdc.string : memcpy;
9864       //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width];
9865       memcpy(dst, src, ftGlyph.bitmap.width);
9866       src += spt;
9867       dst += outStride;
9868     }
9869   }
9870 }
9871 
9872 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
9873   FT_Vector ftKerning;
9874   version(none) {
9875     // fitted kerning
9876     FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
9877     //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); }
9878     return cast(int)ftKerning.x; // round up and convert to integer
9879   } else {
9880     // unfitted kerning
9881     //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning);
9882     if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0;
9883     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;
9884     if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0;
9885     version(none) {
9886       if (ftKerning.x) {
9887         //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); }
9888         { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); }
9889       }
9890     }
9891     version(none) {
9892       FT_Vector kk;
9893       if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!");
9894       auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel
9895       //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
9896       //assert(ftKerning.x == kadvfrac);
9897       if (ftKerning.x || kadvfrac) {
9898         { 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); }
9899       }
9900       //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer
9901       return kadvfrac/64.0f;
9902     }
9903     //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer
9904     return ftKerning.x/64.0f;
9905   }
9906 }
9907 
9908 extern(C) nothrow @trusted @nogc {
9909   static struct OutlinerData {
9910     @disable this (this);
9911     void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
9912     NVGContext vg;
9913     NVGPathOutline.DataStore* ol;
9914     FT_BBox outlineBBox;
9915   nothrow @trusted @nogc:
9916     static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
9917     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
9918   }
9919 
9920   int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
9921     auto odata = cast(OutlinerData*)user;
9922     if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y));
9923     if (odata.ol !is null) {
9924       odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
9925       odata.ol.putArgs(odata.transx(to.x));
9926       odata.ol.putArgs(odata.transy(to.y));
9927     }
9928     return 0;
9929   }
9930 
9931   int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
9932     auto odata = cast(OutlinerData*)user;
9933     if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y));
9934     if (odata.ol !is null) {
9935       odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
9936       odata.ol.putArgs(odata.transx(to.x));
9937       odata.ol.putArgs(odata.transy(to.y));
9938     }
9939     return 0;
9940   }
9941 
9942   int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
9943     auto odata = cast(OutlinerData*)user;
9944     if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y));
9945     if (odata.ol !is null) {
9946       odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
9947       odata.ol.putArgs(odata.transx(c1.x));
9948       odata.ol.putArgs(odata.transy(c1.y));
9949       odata.ol.putArgs(odata.transx(to.x));
9950       odata.ol.putArgs(odata.transy(to.y));
9951     }
9952     return 0;
9953   }
9954 
9955   int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
9956     auto odata = cast(OutlinerData*)user;
9957     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));
9958     if (odata.ol !is null) {
9959       odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
9960       odata.ol.putArgs(odata.transx(c1.x));
9961       odata.ol.putArgs(odata.transy(c1.y));
9962       odata.ol.putArgs(odata.transx(c2.x));
9963       odata.ol.putArgs(odata.transy(c2.y));
9964       odata.ol.putArgs(odata.transx(to.x));
9965       odata.ol.putArgs(odata.transy(to.y));
9966     }
9967     return 0;
9968   }
9969 }
9970 
9971 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
9972   if (bounds.length > 4) bounds = bounds.ptr[0..4];
9973 
9974   FT_Outline_Funcs funcs;
9975   funcs.move_to = &fons__nvg__moveto_cb;
9976   funcs.line_to = &fons__nvg__lineto_cb;
9977   funcs.conic_to = &fons__nvg__quadto_cb;
9978   funcs.cubic_to = &fons__nvg__cubicto_cb;
9979 
9980   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
9981   if (err) { bounds[] = 0; return false; }
9982   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
9983 
9984   FT_Outline outline = font.font.glyph.outline;
9985 
9986   OutlinerData odata;
9987   odata.vg = vg;
9988   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
9989 
9990   err = FT_Outline_Decompose(&outline, &funcs, &odata);
9991   if (err) { bounds[] = 0; return false; }
9992   if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin;
9993   if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax;
9994   if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax;
9995   if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin;
9996   return true;
9997 }
9998 
9999 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10000   FT_Outline_Funcs funcs;
10001   funcs.move_to = &fons__nvg__moveto_cb;
10002   funcs.line_to = &fons__nvg__lineto_cb;
10003   funcs.conic_to = &fons__nvg__quadto_cb;
10004   funcs.cubic_to = &fons__nvg__cubicto_cb;
10005 
10006   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10007   if (err) return false;
10008   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false;
10009 
10010   FT_Outline outline = font.font.glyph.outline;
10011 
10012   OutlinerData odata;
10013   odata.ol = ol;
10014   FT_Outline_Get_CBox(&outline, &odata.outlineBBox);
10015 
10016   err = FT_Outline_Decompose(&outline, &funcs, &odata);
10017   if (err) return false;
10018   ol.bounds.ptr[0] = odata.outlineBBox.xMin;
10019   ol.bounds.ptr[1] = -odata.outlineBBox.yMax;
10020   ol.bounds.ptr[2] = odata.outlineBBox.xMax;
10021   ol.bounds.ptr[3] = -odata.outlineBBox.yMin;
10022   return true;
10023 }
10024 
10025 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10026   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10027 
10028   auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
10029   if (err) return false;
10030   if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; }
10031 
10032   FT_Outline outline = font.font.glyph.outline;
10033   FT_BBox outlineBBox;
10034   FT_Outline_Get_CBox(&outline, &outlineBBox);
10035   if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
10036   if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
10037   if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
10038   if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
10039   return true;
10040 }
10041 
10042 
10043 } else {
10044 // ////////////////////////////////////////////////////////////////////////// //
10045 // sorry
10046 import std.traits : isFunctionPointer, isDelegate;
10047 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10048   import std.traits;
10049   enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
10050   return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
10051 }
10052 
10053 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) {
10054   try {
10055     return assumeNoThrowNoGC(t)();
10056   } catch (Exception e) {
10057     assert(0, "OOPS!");
10058   }
10059 }
10060 
10061 struct FONSttFontImpl {
10062   stbtt_fontinfo font;
10063   bool mono; // no aa?
10064 }
10065 
10066 int fons__tt_init (FONSContext context) nothrow @trusted @nogc {
10067   return 1;
10068 }
10069 
10070 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc {
10071   font.mono = v;
10072 }
10073 
10074 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc {
10075   return font.mono;
10076 }
10077 
10078 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc {
10079   int stbError;
10080   font.font.userdata = context;
10081   forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); });
10082   return stbError;
10083 }
10084 
10085 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc {
10086   forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); });
10087 }
10088 
10089 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc {
10090   float res = void;
10091   forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); });
10092   return res;
10093 }
10094 
10095 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc {
10096   int res;
10097   forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); });
10098   return res;
10099 }
10100 
10101 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 {
10102   forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); });
10103   forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); });
10104   return 1;
10105 }
10106 
10107 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc {
10108   forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); });
10109 }
10110 
10111 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc {
10112   // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em)
10113   // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting
10114   float res = void;
10115   forceNoThrowNoGC({
10116     res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2);
10117     res *= stbtt_ScaleForPixelHeight(&font.font, size);
10118   });
10119   /*
10120   if (res != 0) {
10121     { 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)); }
10122   }
10123   */
10124   //k8: dunno if this is right; i guess it isn't but...
10125   return res;
10126 }
10127 
10128 // old arsd.ttf sux! ;-)
10129 static if (is(typeof(STBTT_vcubic))) {
10130 
10131 static struct OutlinerData {
10132   @disable this (this);
10133   void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); }
10134   NVGPathOutline.DataStore* ol;
10135 nothrow @trusted @nogc:
10136   static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; }
10137   static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10138 }
10139 
10140 
10141 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc {
10142   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10143 
10144   bool okflag = false;
10145 
10146   forceNoThrowNoGC({
10147     int x0, y0, x1, y1;
10148     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10149       bounds[] = 0;
10150       return;
10151     }
10152 
10153     if (bounds.length > 0) bounds.ptr[0] = x0;
10154     if (bounds.length > 1) bounds.ptr[1] = -y1;
10155     if (bounds.length > 2) bounds.ptr[2] = x1;
10156     if (bounds.length > 3) bounds.ptr[3] = -y0;
10157 
10158     static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; }
10159 
10160     stbtt_vertex* verts = null;
10161     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10162     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10163     if (vcount < 1) return;
10164 
10165     foreach (const ref vt; verts[0..vcount]) {
10166       switch (vt.type) {
10167         case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break;
10168         case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break;
10169         case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break;
10170         case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break;
10171         default:
10172       }
10173     }
10174 
10175     okflag = true;
10176   });
10177 
10178   return okflag;
10179 }
10180 
10181 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
10182   bool okflag = false;
10183 
10184   forceNoThrowNoGC({
10185     int x0, y0, x1, y1;
10186 
10187     if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10188       ol.bounds[] = 0;
10189       return;
10190     }
10191 
10192     ol.bounds.ptr[0] = x0;
10193     ol.bounds.ptr[1] = -y1;
10194     ol.bounds.ptr[2] = x1;
10195     ol.bounds.ptr[3] = -y0;
10196 
10197     stbtt_vertex* verts = null;
10198     scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); }
10199     int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts);
10200     if (vcount < 1) return;
10201 
10202     OutlinerData odata;
10203     odata.ol = ol;
10204 
10205     foreach (const ref vt; verts[0..vcount]) {
10206       switch (vt.type) {
10207         case STBTT_vmove:
10208           odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo);
10209           odata.ol.putArgs(odata.transx(vt.x));
10210           odata.ol.putArgs(odata.transy(vt.y));
10211           break;
10212         case STBTT_vline:
10213           odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo);
10214           odata.ol.putArgs(odata.transx(vt.x));
10215           odata.ol.putArgs(odata.transy(vt.y));
10216           break;
10217         case STBTT_vcurve:
10218           odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo);
10219           odata.ol.putArgs(odata.transx(vt.x));
10220           odata.ol.putArgs(odata.transy(vt.y));
10221           odata.ol.putArgs(odata.transx(vt.cx));
10222           odata.ol.putArgs(odata.transy(vt.cy));
10223           break;
10224         case STBTT_vcubic:
10225           odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo);
10226           odata.ol.putArgs(odata.transx(vt.x));
10227           odata.ol.putArgs(odata.transy(vt.y));
10228           odata.ol.putArgs(odata.transx(vt.cx));
10229           odata.ol.putArgs(odata.transy(vt.cy));
10230           odata.ol.putArgs(odata.transx(vt.cx1));
10231           odata.ol.putArgs(odata.transy(vt.cy1));
10232           break;
10233         default:
10234       }
10235     }
10236 
10237     okflag = true;
10238   });
10239 
10240   return okflag;
10241 }
10242 
10243 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc {
10244   if (bounds.length > 4) bounds = bounds.ptr[0..4];
10245 
10246   bool okflag = false;
10247 
10248   forceNoThrowNoGC({
10249     int x0, y0, x1, y1;
10250     if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) {
10251       if (bounds.length > 0) bounds.ptr[0] = x0;
10252       if (bounds.length > 1) bounds.ptr[1] = -y1;
10253       if (bounds.length > 2) bounds.ptr[2] = x1;
10254       if (bounds.length > 3) bounds.ptr[3] = -y0;
10255       okflag = true;
10256     } else {
10257       bounds[] = 0;
10258     }
10259   });
10260 
10261   return okflag;
10262 }
10263 
10264 } // check for old stb_ttf
10265 
10266 
10267 } // version
10268 
10269 
10270 // ////////////////////////////////////////////////////////////////////////// //
10271 private:
10272 enum FONS_SCRATCH_BUF_SIZE = 64000;
10273 enum FONS_HASH_LUT_SIZE = 256;
10274 enum FONS_INIT_FONTS = 4;
10275 enum FONS_INIT_GLYPHS = 256;
10276 enum FONS_INIT_ATLAS_NODES = 256;
10277 enum FONS_VERTEX_COUNT = 1024;
10278 enum FONS_MAX_STATES = 20;
10279 enum FONS_MAX_FALLBACKS = 20;
10280 
10281 
10282 struct FONSglyph {
10283   uint codepoint;
10284   int index;
10285   int next;
10286   short size, blur;
10287   short x0, y0, x1, y1;
10288   short xadv, xoff, yoff;
10289 }
10290 
10291 // refcounted
10292 struct FONSfontData {
10293   ubyte* data;
10294   int dataSize;
10295   bool freeData;
10296   int rc;
10297 
10298   @disable this (this); // no copies
10299   void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); }
10300 }
10301 
10302 // won't set rc to 1
10303 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc {
10304   import core.stdc.stdlib : malloc;
10305   assert(adata !is null);
10306   assert(asize > 0);
10307   auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof);
10308   if (res is null) assert(0, "FONS: out of memory");
10309   res.data = adata;
10310   res.dataSize = asize;
10311   res.freeData = afree;
10312   res.rc = 0;
10313   return res;
10314 }
10315 
10316 void incref (FONSfontData* fd) pure nothrow @trusted @nogc {
10317   pragma(inline, true);
10318   if (fd !is null) ++fd.rc;
10319 }
10320 
10321 void decref (ref FONSfontData* fd) nothrow @trusted @nogc {
10322   if (fd !is null) {
10323     if (--fd.rc == 0) {
10324       import core.stdc.stdlib : free;
10325       if (fd.freeData && fd.data !is null) {
10326         free(fd.data);
10327         fd.data = null;
10328       }
10329       free(fd);
10330       fd = null;
10331     }
10332   }
10333 }
10334 
10335 // as creating and destroying fonts is a rare operation, malloc some data
10336 struct FONSfont {
10337   FONSttFontImpl font;
10338   char* name; // malloced, strz, always lowercase
10339   uint namelen;
10340   uint namehash;
10341   char* path; // malloced, strz
10342   FONSfontData* fdata;
10343   float ascender;
10344   float descender;
10345   float lineh;
10346   FONSglyph* glyphs;
10347   int cglyphs;
10348   int nglyphs;
10349   int[FONS_HASH_LUT_SIZE] lut;
10350   int[FONS_MAX_FALLBACKS] fallbacks;
10351   int nfallbacks;
10352 
10353   @disable this (this);
10354   void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); }
10355 
10356   static uint djbhash (const(void)[] s) pure nothrow @safe @nogc {
10357     uint hash = 5381;
10358     foreach (ubyte b; cast(const(ubyte)[])s) {
10359       if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower
10360       hash = ((hash<<5)+hash)+b;
10361     }
10362     return hash;
10363   }
10364 
10365   // except glyphs
10366   void freeMemory () nothrow @trusted @nogc {
10367     import core.stdc.stdlib : free;
10368     if (name !is null) { free(name); name = null; }
10369     namelen = namehash = 0;
10370     if (path !is null) { free(path); path = null; }
10371     fdata.decref();
10372   }
10373 
10374   // this also calcs name hash
10375   void setName (const(char)[] aname) nothrow @trusted @nogc {
10376     //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); }
10377     import core.stdc.stdlib : realloc;
10378     if (aname.length > int.max/32) assert(0, "FONS: invalid font name");
10379     namelen = cast(uint)aname.length;
10380     name = cast(char*)realloc(name, namelen+1);
10381     if (name is null) assert(0, "FONS: out of memory");
10382     if (aname.length) name[0..aname.length] = aname[];
10383     name[namelen] = 0;
10384     // lowercase it
10385     foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10386     namehash = djbhash(name[0..namelen]);
10387     //{ import core.stdc.stdio; printf("  [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); }
10388   }
10389 
10390   void setPath (const(char)[] apath) nothrow @trusted @nogc {
10391     import core.stdc.stdlib : realloc;
10392     if (apath.length > int.max/32) assert(0, "FONS: invalid font path");
10393     path = cast(char*)realloc(path, apath.length+1);
10394     if (path is null) assert(0, "FONS: out of memory");
10395     if (apath.length) path[0..apath.length] = apath[];
10396     path[apath.length] = 0;
10397   }
10398 
10399   // this won't check hash
10400   bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc {
10401     //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); }
10402     if (namelen != aname.length) return false;
10403     const(char)* ns = name;
10404     // name part
10405     foreach (char ch; aname) {
10406       if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
10407       if (ch != *ns++) return false;
10408     }
10409     // done (length was checked earlier)
10410     return true;
10411   }
10412 
10413   void clear () nothrow @trusted @nogc {
10414     import core.stdc.stdlib : free;
10415     import core.stdc.string : memset;
10416     if (glyphs !is null) free(glyphs);
10417     freeMemory();
10418     memset(&this, 0, this.sizeof);
10419   }
10420 
10421   FONSglyph* allocGlyph () nothrow @trusted @nogc {
10422     if (nglyphs+1 > cglyphs) {
10423       import core.stdc.stdlib : realloc;
10424       cglyphs = (cglyphs == 0 ? 8 : cglyphs*2);
10425       glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs);
10426       if (glyphs is null) assert(0, "FontStash: out of memory");
10427     }
10428     ++nglyphs;
10429     return &glyphs[nglyphs-1];
10430   }
10431 }
10432 
10433 void kill (ref FONSfont* font) nothrow @trusted @nogc {
10434   if (font !is null) {
10435     import core.stdc.stdlib : free;
10436     font.clear();
10437     free(font);
10438     font = null;
10439   }
10440 }
10441 
10442 
10443 // ////////////////////////////////////////////////////////////////////////// //
10444 struct FONSstate {
10445   int font;
10446   NVGTextAlign talign;
10447   float size = 0;
10448   float blur = 0;
10449   float spacing = 0;
10450 }
10451 
10452 
10453 // ////////////////////////////////////////////////////////////////////////// //
10454 // atlas based on Skyline Bin Packer by Jukka Jylänki
10455 alias FONSAtlas = FONSatlasInternal*;
10456 
10457 struct FONSatlasInternal {
10458   static struct Node {
10459     short x, y, width;
10460   }
10461 
10462   int width, height;
10463   Node* nodes;
10464   int nnodes;
10465   int cnodes;
10466 
10467   @disable this (this);
10468   void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); }
10469 
10470 nothrow @trusted @nogc:
10471   static FONSAtlas create (int w, int h, int nnodes) {
10472     import core.stdc.stdlib : malloc;
10473     import core.stdc.string : memset;
10474 
10475     FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof);
10476     if (atlas is null) assert(0, "FontStash: out of memory");
10477     memset(atlas, 0, FONSatlasInternal.sizeof);
10478 
10479     atlas.width = w;
10480     atlas.height = h;
10481 
10482     // allocate space for skyline nodes
10483     atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes);
10484     if (atlas.nodes is null) assert(0, "FontStash: out of memory");
10485     memset(atlas.nodes, 0, Node.sizeof*nnodes);
10486     atlas.nnodes = 0;
10487     atlas.cnodes = nnodes;
10488 
10489     // init root node
10490     atlas.nodes[0].x = 0;
10491     atlas.nodes[0].y = 0;
10492     atlas.nodes[0].width = cast(short)w;
10493     ++atlas.nnodes;
10494 
10495     return atlas;
10496   }
10497 
10498   void clear () {
10499     import core.stdc.stdlib : free;
10500     import core.stdc.string : memset;
10501 
10502     if (nodes !is null) free(nodes);
10503     memset(&this, 0, this.sizeof);
10504   }
10505 
10506   void insertNode (int idx, int x, int y, int w) {
10507     if (nnodes+1 > cnodes) {
10508       import core.stdc.stdlib : realloc;
10509       cnodes = (cnodes == 0 ? 8 : cnodes*2);
10510       nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes);
10511       if (nodes is null) assert(0, "FontStash: out of memory");
10512     }
10513     for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1];
10514     nodes[idx].x = cast(short)x;
10515     nodes[idx].y = cast(short)y;
10516     nodes[idx].width = cast(short)w;
10517     ++nnodes;
10518   }
10519 
10520   void removeNode (int idx) {
10521     if (nnodes == 0) return;
10522     foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i];
10523     --nnodes;
10524   }
10525 
10526   // insert node for empty space
10527   void expand (int w, int h) {
10528     if (w > width) insertNode(nnodes, width, 0, w-width);
10529     width = w;
10530     height = h;
10531   }
10532 
10533   void reset (int w, int h) {
10534     width = w;
10535     height = h;
10536     nnodes = 0;
10537     // init root node
10538     nodes[0].x = 0;
10539     nodes[0].y = 0;
10540     nodes[0].width = cast(short)w;
10541     ++nnodes;
10542   }
10543 
10544   void addSkylineLevel (int idx, int x, int y, int w, int h) {
10545     insertNode(idx, x, y+h, w);
10546 
10547     // delete skyline segments that fall under the shadow of the new segment
10548     for (int i = idx+1; i < nnodes; ++i) {
10549       if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) {
10550         int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x;
10551         nodes[i].x += cast(short)shrink;
10552         nodes[i].width -= cast(short)shrink;
10553         if (nodes[i].width <= 0) {
10554           removeNode(i);
10555           --i;
10556         } else {
10557           break;
10558         }
10559       } else {
10560         break;
10561       }
10562     }
10563 
10564     // Merge same height skyline segments that are next to each other
10565     for (int i = 0; i < nnodes-1; ++i) {
10566       if (nodes[i].y == nodes[i+1].y) {
10567         nodes[i].width += nodes[i+1].width;
10568         removeNode(i+1);
10569         --i;
10570       }
10571     }
10572   }
10573 
10574   // checks if there is enough space at the location of skyline span 'i',
10575   // and return the max height of all skyline spans under that at that location,
10576   // (think tetris block being dropped at that position); or -1 if no space found
10577   int rectFits (int i, int w, int h) {
10578     int x = nodes[i].x;
10579     int y = nodes[i].y;
10580     if (x+w > width) return -1;
10581     int spaceLeft = w;
10582     while (spaceLeft > 0) {
10583       if (i == nnodes) return -1;
10584       y = nvg__max(y, nodes[i].y);
10585       if (y+h > height) return -1;
10586       spaceLeft -= nodes[i].width;
10587       ++i;
10588     }
10589     return y;
10590   }
10591 
10592   bool addRect (int rw, int rh, int* rx, int* ry) {
10593     int besth = height, bestw = width, besti = -1;
10594     int bestx = -1, besty = -1;
10595 
10596     // Bottom left fit heuristic.
10597     for (int i = 0; i < nnodes; ++i) {
10598       int y = rectFits(i, rw, rh);
10599       if (y != -1) {
10600         if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) {
10601           besti = i;
10602           bestw = nodes[i].width;
10603           besth = y+rh;
10604           bestx = nodes[i].x;
10605           besty = y;
10606         }
10607       }
10608     }
10609 
10610     if (besti == -1) return false;
10611 
10612     // perform the actual packing
10613     addSkylineLevel(besti, bestx, besty, rw, rh);
10614 
10615     *rx = bestx;
10616     *ry = besty;
10617 
10618     return true;
10619   }
10620 }
10621 
10622 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc {
10623   if (atlas !is null) {
10624     import core.stdc.stdlib : free;
10625     atlas.clear();
10626     free(atlas);
10627     atlas = null;
10628   }
10629 }
10630 
10631 
10632 // ////////////////////////////////////////////////////////////////////////// //
10633 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation.
10634 /// Group: font_stash
10635 public struct FONScontextInternal {
10636 private:
10637   FONSParams params;
10638   float itw, ith;
10639   ubyte* texData;
10640   int[4] dirtyRect;
10641   FONSfont** fonts; // actually, a simple hash table; can't grow yet
10642   int cfonts; // allocated
10643   int nfonts; // used (so we can track hash table stats)
10644   int* hashidx; // [hsize] items; holds indicies in [fonts] array
10645   int hused, hsize;// used items and total items in [hashidx]
10646   FONSAtlas atlas;
10647   ubyte* scratch;
10648   int nscratch;
10649   FONSstate[FONS_MAX_STATES] states;
10650   int nstates;
10651 
10652   void delegate (FONSError error, int val) nothrow @trusted @nogc handleError;
10653 
10654   @disable this (this);
10655   void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); }
10656 
10657 private:
10658   static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
10659     if (s0.length != s1.length) return false;
10660     const(char)* sp0 = s0.ptr;
10661     const(char)* sp1 = s1.ptr;
10662     foreach (immutable _; 0..s0.length) {
10663       char c0 = *sp0++;
10664       char c1 = *sp1++;
10665       if (c0 != c1) {
10666         if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower
10667         if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower
10668         if (c0 != c1) return false;
10669       }
10670     }
10671     return true;
10672   }
10673 
10674   inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return {
10675     pragma(inline, true);
10676     return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]);
10677   }
10678 
10679   // simple linear probing; returns [FONS_INVALID] if not found
10680   int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc {
10681     if (nfonts == 0) return FONS_INVALID;
10682     auto nhash = FONSfont.djbhash(name);
10683     //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10684     auto res = nhash%hsize;
10685     // hash will never be 100% full, so this loop is safe
10686     for (;;) {
10687       int idx = hashidx[res];
10688       if (idx == -1) break;
10689       auto font = fonts[idx];
10690       if (font is null) assert(0, "FONS internal error");
10691       if (font.namehash == nhash && font.nameEqu(name)) return idx;
10692       //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10693       res = (res+1)%hsize;
10694     }
10695     return FONS_INVALID;
10696   }
10697 
10698   // should be called $(B before) freeing `fonts[fidx]`
10699   void removeIndexFromHash (int fidx) nothrow @trusted @nogc {
10700     if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error");
10701     if (fonts[fidx] is null) assert(0, "FONS internal error");
10702     if (hused != nfonts) assert(0, "FONS internal error");
10703     auto nhash = fonts[fidx].namehash;
10704     auto res = nhash%hsize;
10705     // hash will never be 100% full, so this loop is safe
10706     for (;;) {
10707       int idx = hashidx[res];
10708       if (idx == -1) assert(0, "FONS INTERNAL ERROR");
10709       if (idx == fidx) {
10710         // i found her! copy rest here
10711         int nidx = (res+1)%hsize;
10712         for (;;) {
10713           if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too
10714           res = nidx;
10715           nidx = (nidx+1)%hsize;
10716         }
10717         return;
10718       }
10719       res = (res+1)%hsize;
10720     }
10721   }
10722 
10723   // add font with the given index to hash
10724   // prerequisite: font should not exists in hash
10725   void addIndexToHash (int idx) nothrow @trusted @nogc {
10726     if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error");
10727     if (fonts[idx] is null) assert(0, "FONS internal error");
10728     import core.stdc.stdlib : realloc;
10729     auto nhash = fonts[idx].namehash;
10730     //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); }
10731     // allocate new hash table if there was none
10732     if (hsize == 0) {
10733       enum InitSize = 256;
10734       auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof);
10735       if (newlist is null) assert(0, "FONS: out of memory");
10736       newlist[0..InitSize] = -1;
10737       hsize = InitSize;
10738       hused = 0;
10739       hashidx = newlist;
10740     }
10741     int res = cast(int)(nhash%hsize);
10742     // need to rehash? we want our hash table 50% full at max
10743     if (hashidx[res] != -1 && hused >= hsize/2) {
10744       uint nsz = hsize*2;
10745       if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts");
10746       auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof);
10747       if (newlist is null) assert(0, "FONS: out of memory");
10748       newlist[0..nsz] = -1;
10749       hused = 0;
10750       // rehash
10751       foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) {
10752         if (ff is null) continue;
10753         // find slot for this font (guaranteed to have one)
10754         uint newslot = ff.namehash%nsz;
10755         while (newlist[newslot] != -1) newslot = (newslot+1)%nsz;
10756         newlist[newslot] = cast(int)fidx;
10757         ++hused;
10758       }
10759       hsize = nsz;
10760       hashidx = newlist;
10761       // we added everything, including [idx], so nothing more to do here
10762     } else {
10763       // find slot (guaranteed to have one)
10764       while (hashidx[res] != -1) res = (res+1)%hsize;
10765       // i found her!
10766       hashidx[res] = idx;
10767       ++hused;
10768     }
10769   }
10770 
10771   void addWhiteRect (int w, int h) nothrow @trusted @nogc {
10772     int gx, gy;
10773     ubyte* dst;
10774 
10775     if (!atlas.addRect(w, h, &gx, &gy)) return;
10776 
10777     // Rasterize
10778     dst = &texData[gx+gy*params.width];
10779     foreach (int y; 0..h) {
10780       foreach (int x; 0..w) {
10781         dst[x] = 0xff;
10782       }
10783       dst += params.width;
10784     }
10785 
10786     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx);
10787     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy);
10788     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w);
10789     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h);
10790   }
10791 
10792   // returns fid, not hash slot
10793   int allocFontAt (int atidx) nothrow @trusted @nogc {
10794     if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error");
10795 
10796     if (atidx < 0) {
10797       if (nfonts >= cfonts) {
10798         import core.stdc.stdlib : realloc;
10799         import core.stdc.string : memset;
10800         assert(nfonts == cfonts);
10801         int newsz = cfonts+64;
10802         if (newsz > 65535) assert(0, "FONS: too many fonts");
10803         auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof);
10804         if (newlist is null) assert(0, "FONS: out of memory");
10805         memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof);
10806         fonts = newlist;
10807         cfonts = newsz;
10808       }
10809       assert(nfonts < cfonts);
10810     }
10811 
10812     FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof);
10813     if (font is null) assert(0, "FONS: out of memory");
10814     memset(font, 0, FONSfont.sizeof);
10815 
10816     font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS);
10817     if (font.glyphs is null) assert(0, "FONS: out of memory");
10818     font.cglyphs = FONS_INIT_GLYPHS;
10819     font.nglyphs = 0;
10820 
10821     if (atidx < 0) {
10822       fonts[nfonts] = font;
10823       return nfonts++;
10824     } else {
10825       fonts[atidx] = font;
10826       return atidx;
10827     }
10828   }
10829 
10830   // 0: ooops
10831   int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc {
10832     if (renderfont !is null) *renderfont = font;
10833     if (font is null || font.fdata is null) return 0;
10834     auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch);
10835     // try to find the glyph in fallback fonts
10836     if (g == 0) {
10837       foreach (immutable i; 0..font.nfallbacks) {
10838         FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10839         if (fallbackFont !is null) {
10840           int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch);
10841           if (fallbackIndex != 0) {
10842             if (renderfont !is null) *renderfont = fallbackFont;
10843             return g;
10844           }
10845         }
10846       }
10847       // no char, try to find replacement one
10848       if (dch != 0xFFFD) {
10849         g = fons__tt_getGlyphIndex(&font.font, 0xFFFD);
10850         if (g == 0) {
10851           foreach (immutable i; 0..font.nfallbacks) {
10852             FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]];
10853             if (fallbackFont !is null) {
10854               int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD);
10855               if (fallbackIndex != 0) {
10856                 if (renderfont !is null) *renderfont = fallbackFont;
10857                 return g;
10858               }
10859             }
10860           }
10861         }
10862       }
10863     }
10864     return g;
10865   }
10866 
10867   void clear () nothrow @trusted @nogc {
10868     import core.stdc.stdlib : free;
10869 
10870     if (params.renderDelete !is null) params.renderDelete(params.userPtr);
10871     foreach (immutable int i; 0..nfonts) fonts[i].kill();
10872 
10873     if (atlas !is null) atlas.kill();
10874     if (fonts !is null) free(fonts);
10875     if (texData !is null) free(texData);
10876     if (scratch !is null) free(scratch);
10877     if (hashidx !is null) free(hashidx);
10878   }
10879 
10880   // add font from another fontstash
10881   int addCookedFont (FONSfont* font) nothrow @trusted @nogc {
10882     if (font is null || font.fdata is null) return FONS_INVALID;
10883     font.fdata.incref();
10884     auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono);
10885     if (res == FONS_INVALID) font.fdata.decref(); // oops
10886     return res;
10887   }
10888 
10889   // fdata refcount must be already increased; it won't be changed
10890   int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc {
10891     int i, ascent, descent, fh, lineGap;
10892 
10893     if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
10894     if (name.length > 32767) return FONS_INVALID;
10895     if (fdata is null) return FONS_INVALID;
10896 
10897     // find a font with the given name
10898     int newidx;
10899     FONSfont* oldfont = null;
10900     int oldidx = findNameInHash(name);
10901     if (oldidx != FONS_INVALID) {
10902       // replacement font
10903       oldfont = fonts[oldidx];
10904       newidx = oldidx;
10905     } else {
10906       // new font, allocate new bucket
10907       newidx = -1;
10908     }
10909 
10910     newidx = allocFontAt(newidx);
10911     FONSfont* font = fonts[newidx];
10912     font.setName(name);
10913     font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup
10914     font.fdata = fdata; // set the font data (don't change reference count)
10915     fons__tt_setMono(&this, &font.font, !defAA);
10916 
10917     // init font
10918     nscratch = 0;
10919     if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) {
10920       // we promised to not free data on error, so just clear the data store (it will be freed by the caller)
10921       font.fdata = null;
10922       font.kill();
10923       if (oldidx != FONS_INVALID) {
10924         assert(oldidx == newidx);
10925         fonts[oldidx] = oldfont;
10926       } else {
10927         assert(newidx == nfonts-1);
10928         fonts[newidx] = null;
10929         --nfonts;
10930       }
10931       return FONS_INVALID;
10932     } else {
10933       // free old font data, if any
10934       if (oldfont !is null) oldfont.kill();
10935     }
10936 
10937     // add font to name hash
10938     if (oldidx == FONS_INVALID) addIndexToHash(newidx);
10939 
10940     // store normalized line height
10941     // the real line height is got by multiplying the lineh by font size
10942     fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap);
10943     fh = ascent-descent;
10944     font.ascender = cast(float)ascent/cast(float)fh;
10945     font.descender = cast(float)descent/cast(float)fh;
10946     font.lineh = cast(float)(fh+lineGap)/cast(float)fh;
10947 
10948     //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); }
10949     return newidx;
10950   }
10951 
10952   // isize: size*10
10953   float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc {
10954     if (params.isZeroTopLeft) {
10955       final switch (talign.vertical) {
10956         case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f;
10957         case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10958         case NVGTextAlign.V.Baseline: return 0.0f;
10959         case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f;
10960       }
10961     } else {
10962       final switch (talign.vertical) {
10963         case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f;
10964         case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f;
10965         case NVGTextAlign.V.Baseline: return 0.0f;
10966         case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f;
10967       }
10968     }
10969     assert(0);
10970   }
10971 
10972 public:
10973   /** Create new FontStash context. It can be destroyed with `fs.kill()` later.
10974    *
10975    * Note that if you don't plan to rasterize glyphs (i.e. you will use created
10976    * FontStash only to measure text), you can simply pass `FONSParams.init`).
10977    */
10978   static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc {
10979     import core.stdc.string : memcpy;
10980 
10981     FONSContext stash = null;
10982 
10983     // allocate memory for the font stash
10984     stash = cast(FONSContext)malloc(FONScontextInternal.sizeof);
10985     if (stash is null) goto error;
10986     memset(stash, 0, FONScontextInternal.sizeof);
10987 
10988     memcpy(&stash.params, &params, params.sizeof);
10989     if (stash.params.width < 1) stash.params.width = 32;
10990     if (stash.params.height < 1) stash.params.height = 32;
10991 
10992     // allocate scratch buffer
10993     stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE);
10994     if (stash.scratch is null) goto error;
10995 
10996     // initialize implementation library
10997     if (!fons__tt_init(stash)) goto error;
10998 
10999     if (stash.params.renderCreate !is null) {
11000       if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error;
11001     }
11002 
11003     stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES);
11004     if (stash.atlas is null) goto error;
11005 
11006     // don't allocate space for fonts: hash manager will do that for us later
11007     //stash.cfonts = 0;
11008     //stash.nfonts = 0;
11009 
11010     // create texture for the cache
11011     stash.itw = 1.0f/stash.params.width;
11012     stash.ith = 1.0f/stash.params.height;
11013     stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height);
11014     if (stash.texData is null) goto error;
11015     memset(stash.texData, 0, stash.params.width*stash.params.height);
11016 
11017     stash.dirtyRect.ptr[0] = stash.params.width;
11018     stash.dirtyRect.ptr[1] = stash.params.height;
11019     stash.dirtyRect.ptr[2] = 0;
11020     stash.dirtyRect.ptr[3] = 0;
11021 
11022     // add white rect at 0, 0 for debug drawing
11023     stash.addWhiteRect(2, 2);
11024 
11025     stash.pushState();
11026     stash.clearState();
11027 
11028     return stash;
11029 
11030   error:
11031     stash.kill();
11032     return null;
11033   }
11034 
11035 public:
11036   /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up).
11037   bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc {
11038     FONSfont* baseFont = fonts[base];
11039     if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) {
11040       baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback;
11041       return true;
11042     }
11043     return false;
11044   }
11045 
11046   @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size.
11047   @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size.
11048 
11049   @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing.
11050   @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing.
11051 
11052   @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur.
11053   @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur.
11054 
11055   @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align.
11056   @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align.
11057 
11058   @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id.
11059   @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id.
11060 
11061   @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name.
11062 
11063   /// Check if FontStash has a font with the given name loaded.
11064   bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); }
11065 
11066   /// Get AA for the current font, or for the specified font.
11067   bool getFontAA (int font=-1) nothrow @trusted @nogc {
11068     FONSstate* state = getState;
11069     if (font < 0) font = state.font;
11070     if (font < 0 || font >= nfonts) return false;
11071     FONSfont* f = fonts[font];
11072     return (f !is null ? !f.font.mono : false);
11073   }
11074 
11075   /// Push current state. Returns `false` if state stack overflowed.
11076   bool pushState () nothrow @trusted @nogc {
11077     if (nstates >= FONS_MAX_STATES) {
11078       if (handleError !is null) handleError(FONSError.StatesOverflow, 0);
11079       return false;
11080     }
11081     if (nstates > 0) {
11082       import core.stdc.string : memcpy;
11083       memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof);
11084     }
11085     ++nstates;
11086     return true;
11087   }
11088 
11089   /// Pop current state. Returns `false` if state stack underflowed.
11090   bool popState () nothrow @trusted @nogc {
11091     if (nstates <= 1) {
11092       if (handleError !is null) handleError(FONSError.StatesUnderflow, 0);
11093       return false;
11094     }
11095     --nstates;
11096     return true;
11097   }
11098 
11099   /// Clear current state (i.e. set it to some sane defaults).
11100   void clearState () nothrow @trusted @nogc {
11101     FONSstate* state = getState;
11102     state.size = 12.0f;
11103     state.font = 0;
11104     state.blur = 0;
11105     state.spacing = 0;
11106     state.talign.reset;
11107   }
11108 
11109   private enum NoAlias = ":noaa";
11110 
11111   /** Add font to FontStash.
11112    *
11113    * Load scalable font from disk, and add it to FontStash. If you will try to load a font
11114    * with same name and path several times, FontStash will load it only once. Also, you can
11115    * load new disk font for any existing logical font.
11116    *
11117    * Params:
11118    *   name = logical font name, that will be used to select this font later.
11119    *   path = path to disk file with your font.
11120    *   defAA = should FontStash use antialiased font rasterizer?
11121    *
11122    * Returns:
11123    *   font id or [FONS_INVALID].
11124    */
11125   int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted {
11126     if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID;
11127     if (path.length > 32768) return FONS_INVALID; // arbitrary limit
11128 
11129     // if font path ends with ":noaa", turn off antialiasing
11130     if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) {
11131       path = path[0..$-NoAlias.length];
11132       if (path.length == 0) return FONS_INVALID;
11133       defAA = false;
11134     }
11135 
11136     // if font name ends with ":noaa", turn off antialiasing
11137     if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11138       name = name[0..$-NoAlias.length];
11139       defAA = false;
11140     }
11141 
11142     // find a font with the given name
11143     int fidx = findNameInHash(name);
11144     //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); }
11145 
11146     int loadFontFile (const(char)[] path) {
11147       // check if existing font (if any) has the same path
11148       if (fidx >= 0) {
11149         import core.stdc.string : strlen;
11150         auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0);
11151         version(Posix) {
11152           //{ 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); }
11153           if (plen == path.length && fonts[fidx].path[0..plen] == path) {
11154             //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); }
11155             // i found her!
11156             return fidx;
11157           }
11158         } else {
11159           if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) {
11160             // i found her!
11161             return fidx;
11162           }
11163         }
11164       }
11165       version(Windows) {
11166         // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont")
11167         foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID;
11168       }
11169       // either no such font, or different path
11170       //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); }
11171       int xres = FONS_INVALID;
11172       try {
11173         import core.stdc.stdlib : free, malloc;
11174         static if (NanoVegaHasIVVFS) {
11175           auto fl = VFile(path);
11176           auto dataSize = fl.size;
11177           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11178           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11179           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11180           scope(failure) free(data); // oops
11181           fl.rawReadExact(data[0..cast(uint)dataSize]);
11182           fl.close();
11183         } else {
11184           import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek;
11185           import std.internal.cstring : tempCString;
11186           auto fl = fopen(path.tempCString, "rb");
11187           if (fl is null) return FONS_INVALID;
11188           scope(exit) fclose(fl);
11189           if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID;
11190           auto dataSize = ftell(fl);
11191           if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID;
11192           if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID;
11193           ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize);
11194           if (data is null) assert(0, "out of memory in NanoVega fontstash");
11195           scope(failure) free(data); // oops
11196           ubyte* dptr = data;
11197           auto left = cast(uint)dataSize;
11198           while (left > 0) {
11199             auto rd = fread(dptr, 1, left, fl);
11200             if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter
11201             dptr += rd;
11202             left -= rd;
11203           }
11204         }
11205         scope(failure) free(data); // oops
11206         // create font data
11207         FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data
11208         fdata.incref();
11209         xres = addFontWithData(name, fdata, defAA);
11210         if (xres == FONS_INVALID) {
11211           fdata.decref(); // this will free [data] and [fdata]
11212         } else {
11213           // remember path
11214           fonts[xres].setPath(path);
11215         }
11216       } catch (Exception e) {
11217         // oops; sorry
11218       }
11219       return xres;
11220     }
11221 
11222     // first try direct path
11223     auto res = loadFontFile(path);
11224     // if loading failed, try fontconfig (if fontconfig is available)
11225     static if (NanoVegaHasFontConfig) {
11226       if (res == FONS_INVALID && fontconfigAvailable) {
11227         // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path
11228         bool ok = true;
11229         if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false;
11230         if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } }
11231         if (ok) {
11232           import std.internal.cstring : tempCString;
11233           FcPattern* pat = FcNameParse(path.tempCString);
11234           if (pat !is null) {
11235             scope(exit) FcPatternDestroy(pat);
11236             if (FcConfigSubstitute(null, pat, FcMatchPattern)) {
11237               FcDefaultSubstitute(pat);
11238               // find the font
11239               FcResult result;
11240               FcPattern* font = FcFontMatch(null, pat, &result);
11241               if (font !is null) {
11242                 scope(exit) FcPatternDestroy(font);
11243                 char* file = null;
11244                 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
11245                   if (file !is null && file[0]) {
11246                     import core.stdc.string : strlen;
11247                     res = loadFontFile(file[0..strlen(file)]);
11248                   }
11249                 }
11250               }
11251             }
11252           }
11253         }
11254       }
11255     }
11256     return res;
11257   }
11258 
11259   /** Add font to FontStash, using data from memory.
11260    *
11261    * And already loaded font to FontStash. You can replace existing logical fonts.
11262    * But note that you can't remove logical font by passing "empty" data.
11263    *
11264    * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.)
11265    *
11266    * Params:
11267    *   name = logical font name, that will be used to select this font later.
11268    *   data = font data.
11269    *   dataSize = font data size.
11270    *   freeData = should FontStash take ownership of the font data?
11271    *   defAA = should FontStash use antialiased font rasterizer?
11272    *
11273    * Returns:
11274    *   font id or [FONS_INVALID].
11275    */
11276   int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc {
11277     if (data is null || dataSize < 16) return FONS_INVALID;
11278     FONSfontData* fdata = fons__createFontData(data, dataSize, freeData);
11279     fdata.incref();
11280     auto res = addFontWithData(name, fdata, defAA);
11281     if (res == FONS_INVALID) {
11282       // we promised to not free data on error
11283       fdata.freeData = false;
11284       fdata.decref(); // this will free [fdata]
11285     }
11286     return res;
11287   }
11288 
11289   /** Add fonts from another FontStash.
11290    *
11291    * This is more effective (and faster) than reloading fonts, because internally font data
11292    * is reference counted.
11293    */
11294   void addFontsFrom (FONSContext source) nothrow @trusted @nogc {
11295     if (source is null) return;
11296     foreach (FONSfont* font; source.fonts[0..source.nfonts]) {
11297       if (font !is null) {
11298         auto newidx = addCookedFont(font);
11299         FONSfont* newfont = fonts[newidx];
11300         assert(newfont !is null);
11301         assert(newfont.path is null);
11302         // copy path
11303         if (font.path !is null && font.path[0]) {
11304           import core.stdc.stdlib : malloc;
11305           import core.stdc.string : strcpy, strlen;
11306           newfont.path = cast(char*)malloc(strlen(font.path)+1);
11307           if (newfont.path is null) assert(0, "FONS: out of memory");
11308           strcpy(newfont.path, font.path);
11309         }
11310       }
11311     }
11312   }
11313 
11314   /// Returns logical font name corresponding to the given font id, or `null`.
11315   /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!)
11316   const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc {
11317     if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null;
11318     return fonts[idx].name[0..fonts[idx].namelen];
11319   }
11320 
11321   /// Returns font id corresponding to the given logical font name, or [FONS_INVALID].
11322   int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc {
11323     //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); }
11324     // remove ":noaa" suffix
11325     if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) {
11326       name = name[0..$-NoAlias.length];
11327     }
11328     if (name.length == 0) return FONS_INVALID;
11329     return findNameInHash(name);
11330   }
11331 
11332   /** Measures the specified text string. Parameter bounds should be a float[4],
11333    * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax]
11334    * Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
11335    */
11336   float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) {
11337     FONSstate* state = getState;
11338     uint codepoint;
11339     uint utf8state = 0;
11340     FONSQuad q;
11341     FONSglyph* glyph = null;
11342     int prevGlyphIndex = -1;
11343     short isize = cast(short)(state.size*10.0f);
11344     short iblur = cast(short)state.blur;
11345     FONSfont* font;
11346 
11347     if (state.font < 0 || state.font >= nfonts) return 0;
11348     font = fonts[state.font];
11349     if (font is null || font.fdata is null) return 0;
11350 
11351     float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
11352 
11353     // Align vertically.
11354     y += getVertAlign(font, state.talign, isize);
11355 
11356     float minx = x, maxx = x;
11357     float miny = y, maxy = y;
11358     float startx = x;
11359 
11360     foreach (T ch; str) {
11361       static if (T.sizeof == 1) {
11362         //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;
11363         mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)"));
11364         if (utf8state) continue;
11365       } else {
11366         static if (T.sizeof == 4) {
11367           if (ch > dchar.max) ch = 0xFFFD;
11368         }
11369         codepoint = cast(uint)ch;
11370       }
11371       glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
11372       if (glyph !is null) {
11373         getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
11374         if (q.x0 < minx) minx = q.x0;
11375         if (q.x1 > maxx) maxx = q.x1;
11376         if (params.isZeroTopLeft) {
11377           if (q.y0 < miny) miny = q.y0;
11378           if (q.y1 > maxy) maxy = q.y1;
11379         } else {
11380           if (q.y1 < miny) miny = q.y1;
11381           if (q.y0 > maxy) maxy = q.y0;
11382         }
11383         prevGlyphIndex = glyph.index;
11384       } else {
11385         //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); }
11386         prevGlyphIndex = -1;
11387       }
11388     }
11389 
11390     float advance = x-startx;
11391     //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); }
11392 
11393     // Align horizontally
11394     if (state.talign.left) {
11395       // empty
11396     } else if (state.talign.right) {
11397       minx -= advance;
11398       maxx -= advance;
11399     } else if (state.talign.center) {
11400       minx -= advance*0.5f;
11401       maxx -= advance*0.5f;
11402     }
11403 
11404     if (bounds.length) {
11405       if (bounds.length > 0) bounds.ptr[0] = minx;
11406       if (bounds.length > 1) bounds.ptr[1] = miny;
11407       if (bounds.length > 2) bounds.ptr[2] = maxx;
11408       if (bounds.length > 3) bounds.ptr[3] = maxy;
11409     }
11410 
11411     return advance;
11412   }
11413 
11414   /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value.
11415   void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc {
11416     FONSstate* state = getState;
11417     if (state.font < 0 || state.font >= nfonts) {
11418       if (ascender !is null) *ascender = 0;
11419       if (descender !is null) *descender = 0;
11420       if (lineh !is null) *lineh = 0;
11421     } else {
11422       FONSfont* font = fonts[state.font];
11423       if (font is null || font.fdata is null) {
11424         if (ascender !is null) *ascender = 0;
11425         if (descender !is null) *descender = 0;
11426         if (lineh !is null) *lineh = 0;
11427       } else {
11428         short isize = cast(short)(state.size*10.0f);
11429         if (ascender !is null) *ascender = font.ascender*isize/10.0f;
11430         if (descender !is null) *descender = font.descender*isize/10.0f;
11431         if (lineh !is null) *lineh = font.lineh*isize/10.0f;
11432       }
11433     }
11434   }
11435 
11436   /// Returns line bounds. Any argument can be `null` if you aren't interested in its value.
11437   void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc {
11438     FONSfont* font;
11439     FONSstate* state = getState;
11440     short isize;
11441 
11442     if (minyp !is null) *minyp = 0;
11443     if (maxyp !is null) *maxyp = 0;
11444 
11445     if (state.font < 0 || state.font >= nfonts) return;
11446     font = fonts[state.font];
11447     isize = cast(short)(state.size*10.0f);
11448     if (font is null || font.fdata is null) return;
11449 
11450     y += getVertAlign(font, state.talign, isize);
11451 
11452     if (params.isZeroTopLeft) {
11453       immutable float miny = y-font.ascender*cast(float)isize/10.0f;
11454       immutable float maxy = miny+font.lineh*isize/10.0f;
11455       if (minyp !is null) *minyp = miny;
11456       if (maxyp !is null) *maxyp = maxy;
11457     } else {
11458       immutable float maxy = y+font.descender*cast(float)isize/10.0f;
11459       immutable float miny = maxy-font.lineh*isize/10.0f;
11460       if (minyp !is null) *minyp = miny;
11461       if (maxyp !is null) *maxyp = maxy;
11462     }
11463   }
11464 
11465   /// Returns font line height.
11466   float fontHeight () nothrow @trusted @nogc {
11467     float res = void;
11468     getVertMetrics(null, null, &res);
11469     return res;
11470   }
11471 
11472   /// Returns font ascender (positive).
11473   float fontAscender () nothrow @trusted @nogc {
11474     float res = void;
11475     getVertMetrics(&res, null, null);
11476     return res;
11477   }
11478 
11479   /// Returns font descender (negative).
11480   float fontDescender () nothrow @trusted @nogc {
11481     float res = void;
11482     getVertMetrics(null, &res, null);
11483     return res;
11484   }
11485 
11486   //TODO: document this
11487   const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc {
11488     if (width !is null) *width = params.width;
11489     if (height !is null) *height = params.height;
11490     return texData;
11491   }
11492 
11493   //TODO: document this
11494   bool validateTexture (int* dirty) nothrow @trusted @nogc {
11495     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11496       dirty[0] = dirtyRect.ptr[0];
11497       dirty[1] = dirtyRect.ptr[1];
11498       dirty[2] = dirtyRect.ptr[2];
11499       dirty[3] = dirtyRect.ptr[3];
11500       // reset dirty rect
11501       dirtyRect.ptr[0] = params.width;
11502       dirtyRect.ptr[1] = params.height;
11503       dirtyRect.ptr[2] = 0;
11504       dirtyRect.ptr[3] = 0;
11505       return true;
11506     }
11507     return false;
11508   }
11509 
11510   //TODO: document this
11511   void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc {
11512     handleError = callback;
11513   }
11514 
11515   //TODO: document this
11516   void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc {
11517     if (width !is null) *width = params.width;
11518     if (height !is null) *height = params.height;
11519   }
11520 
11521   //TODO: document this
11522   bool expandAtlas (int width, int height) nothrow @trusted @nogc {
11523     import core.stdc.stdlib : free;
11524     import core.stdc.string : memcpy, memset;
11525 
11526     int maxy = 0;
11527     ubyte* data = null;
11528 
11529     width = nvg__max(width, params.width);
11530     height = nvg__max(height, params.height);
11531 
11532     if (width == params.width && height == params.height) return true;
11533 
11534     // Flush pending glyphs.
11535     flush();
11536 
11537     // Create new texture
11538     if (params.renderResize !is null) {
11539       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11540     }
11541     // Copy old texture data over.
11542     data = cast(ubyte*)malloc(width*height);
11543     if (data is null) return 0;
11544     foreach (immutable int i; 0..params.height) {
11545       ubyte* dst = &data[i*width];
11546       ubyte* src = &texData[i*params.width];
11547       memcpy(dst, src, params.width);
11548       if (width > params.width) memset(dst+params.width, 0, width-params.width);
11549     }
11550     if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width);
11551 
11552     free(texData);
11553     texData = data;
11554 
11555     // Increase atlas size
11556     atlas.expand(width, height);
11557 
11558     // Add existing data as dirty.
11559     foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y);
11560     dirtyRect.ptr[0] = 0;
11561     dirtyRect.ptr[1] = 0;
11562     dirtyRect.ptr[2] = params.width;
11563     dirtyRect.ptr[3] = maxy;
11564 
11565     params.width = width;
11566     params.height = height;
11567     itw = 1.0f/params.width;
11568     ith = 1.0f/params.height;
11569 
11570     return true;
11571   }
11572 
11573   //TODO: document this
11574   bool resetAtlas (int width, int height) nothrow @trusted @nogc {
11575     import core.stdc.stdlib : realloc;
11576     import core.stdc.string : memcpy, memset;
11577 
11578     // flush pending glyphs
11579     flush();
11580 
11581     // create new texture
11582     if (params.renderResize !is null) {
11583       if (params.renderResize(params.userPtr, width, height) == 0) return false;
11584     }
11585 
11586     // reset atlas
11587     atlas.reset(width, height);
11588 
11589     // clear texture data
11590     texData = cast(ubyte*)realloc(texData, width*height);
11591     if (texData is null) assert(0, "FONS: out of memory");
11592     memset(texData, 0, width*height);
11593 
11594     // reset dirty rect
11595     dirtyRect.ptr[0] = width;
11596     dirtyRect.ptr[1] = height;
11597     dirtyRect.ptr[2] = 0;
11598     dirtyRect.ptr[3] = 0;
11599 
11600     // Reset cached glyphs
11601     foreach (FONSfont* font; fonts[0..nfonts]) {
11602       if (font !is null) {
11603         font.nglyphs = 0;
11604         font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1;
11605       }
11606     }
11607 
11608     params.width = width;
11609     params.height = height;
11610     itw = 1.0f/params.width;
11611     ith = 1.0f/params.height;
11612 
11613     // Add white rect at 0, 0 for debug drawing.
11614     addWhiteRect(2, 2);
11615 
11616     return true;
11617   }
11618 
11619   //TODO: document this
11620   bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc {
11621     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11622     static if (is(typeof(&fons__nvg__bounds))) {
11623       FONSstate* state = getState;
11624       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11625       FONSfont* font;
11626       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11627       if (g == 0) { bounds[] = 0; return false; }
11628       assert(font !is null);
11629       return fons__nvg__bounds(&font.font, g, bounds);
11630     } else {
11631       bounds[] = 0;
11632       return false;
11633     }
11634   }
11635 
11636   //TODO: document this
11637   bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc {
11638     if (bounds.length > 4) bounds = bounds.ptr[0..4];
11639     static if (is(typeof(&fons__nvg__toPath))) {
11640       if (vg is null) { bounds[] = 0; return false; }
11641       FONSstate* state = getState;
11642       if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; }
11643       FONSfont* font;
11644       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11645       if (g == 0) { bounds[] = 0; return false; }
11646       assert(font !is null);
11647       return fons__nvg__toPath(vg, &font.font, g, bounds);
11648     } else {
11649       bounds[] = 0;
11650       return false;
11651     }
11652   }
11653 
11654   //TODO: document this
11655   bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc {
11656     if (ol is null) return false;
11657     static if (is(typeof(&fons__nvg__toOutline))) {
11658       FONSstate* state = getState;
11659       if (state.font < 0 || state.font >= nfonts) return false;
11660       FONSfont* font;
11661       auto g = findGlyphForCP(fonts[state.font], dch, &font);
11662       if (g == 0) return false;
11663       assert(font !is null);
11664       return fons__nvg__toOutline(&font.font, g, ol);
11665     } else {
11666       return false;
11667     }
11668   }
11669 
11670   //TODO: document this
11671   FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc {
11672     static uint fons__hashint() (uint a) pure nothrow @safe @nogc {
11673       pragma(inline, true);
11674       a += ~(a<<15);
11675       a ^=  (a>>10);
11676       a +=  (a<<3);
11677       a ^=  (a>>6);
11678       a += ~(a<<11);
11679       a ^=  (a>>16);
11680       return a;
11681     }
11682 
11683     // based on Exponential blur, Jani Huhtanen, 2006
11684     enum APREC = 16;
11685     enum ZPREC = 7;
11686 
11687     static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11688       foreach (immutable int y; 0..h) {
11689         int z = 0; // force zero border
11690         foreach (int x; 1..w) {
11691           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11692           dst[x] = cast(ubyte)(z>>ZPREC);
11693         }
11694         dst[w-1] = 0; // force zero border
11695         z = 0;
11696         for (int x = w-2; x >= 0; --x) {
11697           z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC;
11698           dst[x] = cast(ubyte)(z>>ZPREC);
11699         }
11700         dst[0] = 0; // force zero border
11701         dst += dstStride;
11702       }
11703     }
11704 
11705     static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc {
11706       foreach (immutable int x; 0..w) {
11707         int z = 0; // force zero border
11708         for (int y = dstStride; y < h*dstStride; y += dstStride) {
11709           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11710           dst[y] = cast(ubyte)(z>>ZPREC);
11711         }
11712         dst[(h-1)*dstStride] = 0; // force zero border
11713         z = 0;
11714         for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) {
11715           z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC;
11716           dst[y] = cast(ubyte)(z>>ZPREC);
11717         }
11718         dst[0] = 0; // force zero border
11719         ++dst;
11720       }
11721     }
11722 
11723     static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc {
11724       import std.math : expf = exp;
11725       if (blur < 1) return;
11726       // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
11727       immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3)
11728       int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f))));
11729       fons__blurRows(dst, w, h, dstStride, alpha);
11730       fons__blurCols(dst, w, h, dstStride, alpha);
11731       fons__blurRows(dst, w, h, dstStride, alpha);
11732       fons__blurCols(dst, w, h, dstStride, alpha);
11733       //fons__blurrows(dst, w, h, dstStride, alpha);
11734       //fons__blurcols(dst, w, h, dstStride, alpha);
11735     }
11736 
11737     int advance, lsb, x0, y0, x1, y1, gx, gy;
11738     FONSglyph* glyph = null;
11739     float size = isize/10.0f;
11740     FONSfont* renderFont = font;
11741 
11742     if (isize < 2) return null;
11743     if (iblur > 20) iblur = 20;
11744     int pad = iblur+2;
11745 
11746     // Reset allocator.
11747     nscratch = 0;
11748 
11749     // Find code point and size.
11750     uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1);
11751     int i = font.lut.ptr[h];
11752     while (i != -1) {
11753       //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i];
11754       if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) {
11755         glyph = &font.glyphs[i];
11756         // Negative coordinate indicates there is no bitmap data created.
11757         if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph;
11758         // At this point, glyph exists but the bitmap data is not yet created.
11759         break;
11760       }
11761       i = font.glyphs[i].next;
11762     }
11763 
11764     // Create a new glyph or rasterize bitmap data for a cached glyph.
11765     //scale = fons__tt_getPixelHeightScale(&font.font, size);
11766     int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont);
11767     // It is possible that we did not find a fallback glyph.
11768     // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
11769 
11770     float scale = fons__tt_getPixelHeightScale(&renderFont.font, size);
11771     fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
11772     int gw = x1-x0+pad*2;
11773     int gh = y1-y0+pad*2;
11774 
11775     // Determines the spot to draw glyph in the atlas.
11776     if (bitmapOption == FONSBitmapFlag.Required) {
11777       // Find free spot for the rect in the atlas.
11778       bool added = atlas.addRect(gw, gh, &gx, &gy);
11779       if (!added && handleError !is null) {
11780         // Atlas is full, let the user to resize the atlas (or not), and try again.
11781         handleError(FONSError.AtlasFull, 0);
11782         added = atlas.addRect(gw, gh, &gx, &gy);
11783       }
11784       if (!added) return null;
11785     } else {
11786       // Negative coordinate indicates there is no bitmap data created.
11787       gx = -1;
11788       gy = -1;
11789     }
11790 
11791     // Init glyph.
11792     if (glyph is null) {
11793       glyph = font.allocGlyph();
11794       glyph.codepoint = codepoint;
11795       glyph.size = isize;
11796       glyph.blur = iblur;
11797       glyph.next = 0;
11798 
11799       // Insert char to hash lookup.
11800       glyph.next = font.lut.ptr[h];
11801       font.lut.ptr[h] = font.nglyphs-1;
11802     }
11803     glyph.index = g;
11804     glyph.x0 = cast(short)gx;
11805     glyph.y0 = cast(short)gy;
11806     glyph.x1 = cast(short)(glyph.x0+gw);
11807     glyph.y1 = cast(short)(glyph.y0+gh);
11808     glyph.xadv = cast(short)(scale*advance*10.0f);
11809     glyph.xoff = cast(short)(x0-pad);
11810     glyph.yoff = cast(short)(y0-pad);
11811 
11812     if (bitmapOption == FONSBitmapFlag.Optional) return glyph;
11813 
11814     // Rasterize
11815     ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width];
11816     fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g);
11817 
11818     // Make sure there is one pixel empty border.
11819     dst = &texData[glyph.x0+glyph.y0*params.width];
11820     foreach (immutable int y; 0..gh) {
11821       dst[y*params.width] = 0;
11822       dst[gw-1+y*params.width] = 0;
11823     }
11824     foreach (immutable int x; 0..gw) {
11825       dst[x] = 0;
11826       dst[x+(gh-1)*params.width] = 0;
11827     }
11828 
11829     // Debug code to color the glyph background
11830     version(none) {
11831       foreach (immutable yy; 0..gh) {
11832         foreach (immutable xx; 0..gw) {
11833           int a = cast(int)dst[xx+yy*params.width]+42;
11834           if (a > 255) a = 255;
11835           dst[xx+yy*params.width] = cast(ubyte)a;
11836         }
11837       }
11838     }
11839 
11840     // Blur
11841     if (iblur > 0) {
11842       nscratch = 0;
11843       ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width];
11844       fons__blur(bdst, gw, gh, params.width, iblur);
11845     }
11846 
11847     dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0);
11848     dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0);
11849     dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1);
11850     dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1);
11851 
11852     return glyph;
11853   }
11854 
11855   //TODO: document this
11856   void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc {
11857     if (prevGlyphIndex >= 0) {
11858       immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here?
11859       //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); }
11860       *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts)
11861     }
11862 
11863     // Each glyph has 2px border to allow good interpolation,
11864     // one pixel to prevent leaking, and one to allow good interpolation for rendering.
11865     // Inset the texture region by one pixel for correct interpolation.
11866     immutable float xoff = cast(short)(glyph.xoff+1);
11867     immutable float yoff = cast(short)(glyph.yoff+1);
11868     immutable float x0 = cast(float)(glyph.x0+1);
11869     immutable float y0 = cast(float)(glyph.y0+1);
11870     immutable float x1 = cast(float)(glyph.x1-1);
11871     immutable float y1 = cast(float)(glyph.y1-1);
11872 
11873     if (params.isZeroTopLeft) {
11874       immutable float rx = cast(float)cast(int)(*x+xoff);
11875       immutable float ry = cast(float)cast(int)(*y+yoff);
11876 
11877       q.x0 = rx;
11878       q.y0 = ry;
11879       q.x1 = rx+x1-x0;
11880       q.y1 = ry+y1-y0;
11881 
11882       q.s0 = x0*itw;
11883       q.t0 = y0*ith;
11884       q.s1 = x1*itw;
11885       q.t1 = y1*ith;
11886     } else {
11887       immutable float rx = cast(float)cast(int)(*x+xoff);
11888       immutable float ry = cast(float)cast(int)(*y-yoff);
11889 
11890       q.x0 = rx;
11891       q.y0 = ry;
11892       q.x1 = rx+x1-x0;
11893       q.y1 = ry-y1+y0;
11894 
11895       q.s0 = x0*itw;
11896       q.t0 = y0*ith;
11897       q.s1 = x1*itw;
11898       q.t1 = y1*ith;
11899     }
11900 
11901     *x += cast(int)(glyph.xadv/10.0f+0.5f);
11902   }
11903 
11904   void flush () nothrow @trusted @nogc {
11905     // flush texture
11906     if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) {
11907       if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData);
11908       // reset dirty rect
11909       dirtyRect.ptr[0] = params.width;
11910       dirtyRect.ptr[1] = params.height;
11911       dirtyRect.ptr[2] = 0;
11912       dirtyRect.ptr[3] = 0;
11913     }
11914   }
11915 }
11916 
11917 /// Free all resources used by the `stash`, and `stash` itself.
11918 /// Group: font_stash
11919 public void kill (ref FONSContext stash) nothrow @trusted @nogc {
11920   import core.stdc.stdlib : free;
11921   if (stash is null) return;
11922   stash.clear();
11923   free(stash);
11924   stash = null;
11925 }
11926 
11927 
11928 // ////////////////////////////////////////////////////////////////////////// //
11929 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc {
11930   ubyte* ptr;
11931   FONSContext stash = cast(FONSContext)up;
11932   // 16-byte align the returned pointer
11933   size = (size+0xf)&~0xf;
11934   if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) {
11935     if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size);
11936     return null;
11937   }
11938   ptr = stash.scratch+stash.nscratch;
11939   stash.nscratch += cast(int)size;
11940   return ptr;
11941 }
11942 
11943 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc {
11944   // empty
11945 }
11946 
11947 
11948 // ////////////////////////////////////////////////////////////////////////// //
11949 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
11950 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
11951 
11952 enum FONS_UTF8_ACCEPT = 0;
11953 enum FONS_UTF8_REJECT = 12;
11954 
11955 static immutable ubyte[364] utf8d = [
11956   // The first part of the table maps bytes to character classes that
11957   // to reduce the size of the transition table and create bitmasks.
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   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,
11962   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,
11963   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,
11964   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,
11965   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,
11966 
11967   // The second part is a transition table that maps a combination
11968   // of a state of the automaton and a character class to a state.
11969   0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11970   12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
11971   12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
11972   12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
11973   12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
11974 ];
11975 
11976 private enum DecUtfMixin(string state, string codep, string byte_) =
11977 `{
11978   uint type_ = utf8d.ptr[`~byte_~`];
11979   `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`);
11980   if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) {
11981     `~state~` = FONS_UTF8_ACCEPT;
11982     `~codep~` = 0xFFFD;
11983   }
11984  }`;
11985 
11986 /*
11987 uint fons__decutf8 (uint* state, uint* codep, uint byte_) {
11988   pragma(inline, true);
11989   uint type = utf8d.ptr[byte_];
11990   *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_);
11991   *state = utf8d.ptr[256 + *state+type];
11992   return *state;
11993 }
11994 */
11995 
11996 
11997 // ////////////////////////////////////////////////////////////////////////// //
11998 /// This iterator can be used to do text measurement.
11999 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!)
12000 /// Group: font_stash
12001 public struct FONSTextBoundsIterator {
12002 private:
12003   FONSContext stash;
12004   FONSstate state;
12005   uint codepoint = 0xFFFD;
12006   uint utf8state = 0;
12007   int prevGlyphIndex = -1;
12008   short isize, iblur;
12009   float scale = 0;
12010   FONSfont* font;
12011   float startx = 0, x = 0, y = 0;
12012   float minx = 0, miny = 0, maxx = 0, maxy = 0;
12013 
12014 private:
12015   void clear () nothrow @trusted @nogc {
12016     import core.stdc.string : memset;
12017     memset(&this, 0, this.sizeof);
12018     this.prevGlyphIndex = -1;
12019     this.codepoint = 0xFFFD;
12020   }
12021 
12022 public:
12023   /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12024   this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); }
12025 
12026   /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator.
12027   void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc {
12028     clear();
12029 
12030     if (astash is null || astash.nstates == 0) return;
12031 
12032     stash = astash;
12033     state = *stash.getState;
12034 
12035     if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; }
12036     font = stash.fonts[state.font];
12037     if (font is null || font.fdata is null) { clear(); return; }
12038 
12039     x = ax;
12040     y = ay;
12041     isize = cast(short)(state.size*10.0f);
12042     iblur = cast(short)state.blur;
12043     scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f);
12044 
12045     // align vertically
12046     y += astash.getVertAlign(font, state.talign, isize);
12047 
12048     minx = maxx = x;
12049     miny = maxy = y;
12050     startx = x;
12051   }
12052 
12053   /// Can this iterator be used?
12054   @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); }
12055 
12056   /// Put some text into iterator, calculate new values.
12057   void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) {
12058     enum DoCodePointMixin = q{
12059       glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional);
12060       if (glyph !is null) {
12061         stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q);
12062         if (q.x0 < minx) minx = q.x0;
12063         if (q.x1 > maxx) maxx = q.x1;
12064         if (stash.params.isZeroTopLeft) {
12065           if (q.y0 < miny) miny = q.y0;
12066           if (q.y1 > maxy) maxy = q.y1;
12067         } else {
12068           if (q.y1 < miny) miny = q.y1;
12069           if (q.y0 > maxy) maxy = q.y0;
12070         }
12071         prevGlyphIndex = glyph.index;
12072       } else {
12073         prevGlyphIndex = -1;
12074       }
12075     };
12076 
12077     if (stash is null || str.length == 0) return; // alas
12078 
12079     FONSQuad q;
12080     FONSglyph* glyph;
12081 
12082     static if (is(T == char)) {
12083       foreach (char ch; str) {
12084         mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch"));
12085         if (utf8state) continue; // full char is not collected yet
12086         mixin(DoCodePointMixin);
12087       }
12088     } else {
12089       if (utf8state) {
12090         utf8state = 0;
12091         codepoint = 0xFFFD;
12092         mixin(DoCodePointMixin);
12093       }
12094       foreach (T dch; str) {
12095         static if (is(T == dchar)) {
12096           if (dch > dchar.max) dch = 0xFFFD;
12097         }
12098         codepoint = cast(uint)dch;
12099         mixin(DoCodePointMixin);
12100       }
12101     }
12102   }
12103 
12104   /// Returns current advance.
12105   @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); }
12106 
12107   /// Returns current text bounds.
12108   void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc {
12109     if (stash is null) { bounds[] = 0; return; }
12110     float lminx = minx, lmaxx = maxx;
12111     // align horizontally
12112     if (state.talign.left) {
12113       // empty
12114     } else if (state.talign.right) {
12115       float ca = advance;
12116       lminx -= ca;
12117       lmaxx -= ca;
12118     } else if (state.talign.center) {
12119       float ca = advance*0.5f;
12120       lminx -= ca;
12121       lmaxx -= ca;
12122     }
12123     bounds[0] = lminx;
12124     bounds[1] = miny;
12125     bounds[2] = lmaxx;
12126     bounds[3] = maxy;
12127   }
12128 
12129   /// Returns current horizontal text bounds.
12130   void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc {
12131     if (stash !is null) {
12132       float lminx = minx, lmaxx = maxx;
12133       // align horizontally
12134       if (state.talign.left) {
12135         // empty
12136       } else if (state.talign.right) {
12137         float ca = advance;
12138         lminx -= ca;
12139         lmaxx -= ca;
12140       } else if (state.talign.center) {
12141         float ca = advance*0.5f;
12142         lminx -= ca;
12143         lmaxx -= ca;
12144       }
12145       xmin = lminx;
12146       xmax = lmaxx;
12147     } else {
12148       xmin = xmax = 0;
12149     }
12150   }
12151 
12152   /// Returns current vertical text bounds.
12153   void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc {
12154     pragma(inline, true);
12155     if (stash !is null) {
12156       ymin = miny;
12157       ymax = maxy;
12158     } else {
12159       ymin = ymax = 0;
12160     }
12161   }
12162 
12163   /// Returns font line height.
12164   float lineHeight () nothrow @trusted @nogc {
12165     pragma(inline, true);
12166     return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0);
12167   }
12168 
12169   /// Returns font ascender (positive).
12170   float ascender () nothrow @trusted @nogc {
12171     pragma(inline, true);
12172     return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0);
12173   }
12174 
12175   /// Returns font descender (negative).
12176   float descender () nothrow @trusted @nogc {
12177     pragma(inline, true);
12178     return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0);
12179   }
12180 }
12181 
12182 
12183 // ////////////////////////////////////////////////////////////////////////// //
12184 // backgl
12185 // ////////////////////////////////////////////////////////////////////////// //
12186 import core.stdc.stdlib : malloc, realloc, free;
12187 import core.stdc.string : memcpy, memset;
12188 
12189 static if (__VERSION__ < 2076) {
12190   private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
12191     import std.traits;
12192     enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
12193     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
12194   }
12195 }
12196 
12197 
12198 //import arsd.simpledisplay;
12199 version(nanovg_bindbc_opengl_bindings) {
12200   import bindbc.opengl;
12201 } else version(nanovg_builtin_opengl_bindings) {
12202   import arsd.simpledisplay;
12203 
12204 	/++
12205 		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.
12206 
12207 		History:
12208 			Added January 22, 2021 (version 9.2 release)
12209 	+/
12210 	public class NVGWindow : SimpleWindow {
12211 		NVGContext nvg;
12212 
12213 		/++
12214 
12215 		+/
12216 		this(int width, int height, string title) {
12217 			setOpenGLContextVersion(3, 0);
12218 			super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing);
12219 
12220 			this.onClosing = delegate() {
12221 				nvg.kill();
12222 			};
12223 
12224 			this.visibleForTheFirstTime = delegate() {
12225 				nvg = nvgCreateContext();
12226 				if(nvg is null) throw new Exception("cannot initialize NanoVega");
12227 			};
12228 
12229 			this.redrawOpenGlScene = delegate() {
12230 				if(redrawNVGScene is null)
12231 					return;
12232 				glViewport(0, 0, this.width, this.height);
12233 				if(clearOnEachFrame) {
12234 					glClearColor(0, 0, 0, 0);
12235 					glClear(glNVGClearFlags);
12236 				}
12237 
12238 				nvg.beginFrame(this.width, this.height);
12239 				scope(exit) nvg.endFrame();
12240 
12241 				redrawNVGScene(nvg);
12242 			};
12243 
12244 			this.setEventHandlers(
12245 				&redrawOpenGlSceneNow,
12246 				(KeyEvent ke) {
12247 					if(ke.key == Key.Escape || ke.key == Key.Q)
12248 						this.close();
12249 				}
12250 			);
12251 		}
12252 
12253 		/++
12254 
12255 		+/
12256 		bool clearOnEachFrame = true;
12257 
12258 		/++
12259 
12260 		+/
12261 		void delegate(NVGContext nvg) redrawNVGScene;
12262 
12263 		/++
12264 
12265 		+/
12266 		void redrawNVGSceneNow() {
12267 			redrawOpenGlSceneNow();
12268 		}
12269 	}
12270 
12271 } else {
12272   import iv.glbinds;
12273 }
12274 
12275 private:
12276 // sdpy is missing that yet
12277 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
12278 
12279 
12280 
12281 version(bindbc){
12282   private extern(System) nothrow @nogc:
12283   // this definition doesn't exist in regular OpenGL (?)
12284   enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
12285   private void nanovgInitOpenGL () {
12286     // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check
12287     GLSupport support = loadOpenGL();
12288     if (support == GLSupport.noLibrary)
12289       assert(0, "OpenGL initialization failed: shared library failed to load");
12290     else if (support == GLSupport.badLibrary)
12291       assert(0, "OpenGL initialization failed: a context-independent symbol failed to load");
12292     else if (support == GLSupport.noContext)
12293       assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization");
12294   }
12295 } else { // OpenGL API missing from simpledisplay
12296     private void nanovgInitOpenGL () @nogc nothrow {
12297       __gshared bool initialized = false;
12298       if (initialized) return;
12299 
12300       try
12301         gl3.loadDynamicLibrary();
12302       catch(Exception)
12303       	assert(0, "GL 3 failed to load");
12304 
12305       initialized = true;
12306   }
12307 }
12308 
12309 
12310 
12311 /// Context creation flags.
12312 /// Group: context_management
12313 public enum NVGContextFlag : int {
12314   /// Nothing special, i.e. empty flag.
12315   None = 0,
12316   /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
12317   Antialias = 1U<<0,
12318   /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
12319     * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */
12320   StencilStrokes = 1U<<1,
12321   /// Flag indicating that additional debug checks are done.
12322   Debug = 1U<<2,
12323   /// Filter (antialias) fonts
12324   FontAA = 1U<<7,
12325   /// Don't filter (antialias) fonts
12326   FontNoAA = 1U<<8,
12327   /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`.
12328   Default = 1U<<31,
12329 }
12330 
12331 public enum NANOVG_GL_USE_STATE_FILTER = true;
12332 
12333 /// Returns flags for glClear().
12334 /// Group: context_management
12335 public uint glNVGClearFlags () pure nothrow @safe @nogc {
12336   pragma(inline, true);
12337   return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT);
12338 }
12339 
12340 
12341 // ////////////////////////////////////////////////////////////////////////// //
12342 private:
12343 
12344 version = nanovega_shared_stencil;
12345 //version = nanovega_debug_clipping;
12346 
12347 enum GLNVGuniformLoc {
12348   ViewSize,
12349   Tex,
12350   Frag,
12351   TMat,
12352   TTr,
12353   ClipTex,
12354 }
12355 
12356 alias GLNVGshaderType = int;
12357 enum /*GLNVGshaderType*/ {
12358   NSVG_SHADER_FILLCOLOR,
12359   NSVG_SHADER_FILLGRAD,
12360   NSVG_SHADER_FILLIMG,
12361   NSVG_SHADER_SIMPLE, // also used for clipfill
12362   NSVG_SHADER_IMG,
12363 }
12364 
12365 struct GLNVGshader {
12366   GLuint prog;
12367   GLuint frag;
12368   GLuint vert;
12369   GLint[GLNVGuniformLoc.max+1] loc;
12370 }
12371 
12372 struct GLNVGtexture {
12373   int id;
12374   GLuint tex;
12375   int width, height;
12376   NVGtexture type;
12377   int flags;
12378   shared int rc; // this can be 0 with tex != 0 -- postponed deletion
12379   int nextfree;
12380 }
12381 
12382 struct GLNVGblend {
12383   bool simple;
12384   GLenum srcRGB;
12385   GLenum dstRGB;
12386   GLenum srcAlpha;
12387   GLenum dstAlpha;
12388 }
12389 
12390 alias GLNVGcallType = int;
12391 enum /*GLNVGcallType*/ {
12392   GLNVG_NONE = 0,
12393   GLNVG_FILL,
12394   GLNVG_CONVEXFILL,
12395   GLNVG_STROKE,
12396   GLNVG_TRIANGLES,
12397   GLNVG_AFFINE, // change affine transformation matrix
12398   GLNVG_PUSHCLIP,
12399   GLNVG_POPCLIP,
12400   GLNVG_RESETCLIP,
12401   GLNVG_CLIP_DDUMP_ON,
12402   GLNVG_CLIP_DDUMP_OFF,
12403 }
12404 
12405 struct GLNVGcall {
12406   int type;
12407   int evenOdd; // for fill
12408   int image;
12409   int pathOffset;
12410   int pathCount;
12411   int triangleOffset;
12412   int triangleCount;
12413   int uniformOffset;
12414   NVGMatrix affine;
12415   GLNVGblend blendFunc;
12416   NVGClipMode clipmode;
12417 }
12418 
12419 struct GLNVGpath {
12420   int fillOffset;
12421   int fillCount;
12422   int strokeOffset;
12423   int strokeCount;
12424 }
12425 
12426 align(1) struct GLNVGfragUniforms {
12427 align(1):
12428   enum UNIFORM_ARRAY_SIZE = 13;
12429   // note: after modifying layout or size of uniform array,
12430   // don't forget to also update the fragment shader source!
12431   align(1) union {
12432   align(1):
12433     align(1) struct {
12434     align(1):
12435       float[12] scissorMat; // matrices are actually 3 vec4s
12436       float[12] paintMat;
12437       NVGColor innerCol;
12438       NVGColor middleCol;
12439       NVGColor outerCol;
12440       float[2] scissorExt;
12441       float[2] scissorScale;
12442       float[2] extent;
12443       float radius;
12444       float feather;
12445       float strokeMult;
12446       float strokeThr;
12447       float texType;
12448       float type;
12449       float doclip;
12450       float midp; // for gradients
12451       float unused2, unused3;
12452     }
12453     float[4][UNIFORM_ARRAY_SIZE] uniformArray;
12454   }
12455 }
12456 
12457 enum GLMaskState {
12458   DontMask = -1,
12459   Uninitialized = 0,
12460   Initialized = 1,
12461   JustCleared = 2,
12462 }
12463 
12464 import core.sync.mutex;
12465 __gshared Mutex GLNVGTextureLocker;
12466 shared static this() {
12467 	GLNVGTextureLocker = new Mutex();
12468 }
12469 
12470 struct GLNVGcontext {
12471   private import core.thread : ThreadID;
12472 
12473   GLNVGshader shader;
12474   GLNVGtexture* textures;
12475   float[2] view;
12476   int freetexid; // -1: none
12477   int ntextures;
12478   int ctextures;
12479   GLuint vertBuf;
12480   int fragSize;
12481   int flags;
12482   // FBOs for masks
12483   GLuint[NVG_MAX_STATES] fbo;
12484   GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil
12485   int fboWidth, fboHeight;
12486   GLMaskState[NVG_MAX_STATES] maskStack;
12487   int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description
12488   int lastClipFBO; // -666: cache invalidated; -1: don't mask
12489   int lastClipUniOfs;
12490   bool doClipUnion; // specal mode
12491   GLNVGshader shaderFillFBO;
12492   GLNVGshader shaderCopyFBO;
12493 
12494   bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion)
12495   shared bool mustCleanTextures; // will be `true` if we should delete some textures
12496   ThreadID mainTID;
12497   uint mainFBO;
12498 
12499   // Per frame buffers
12500   GLNVGcall* calls;
12501   int ccalls;
12502   int ncalls;
12503   GLNVGpath* paths;
12504   int cpaths;
12505   int npaths;
12506   NVGVertex* verts;
12507   int cverts;
12508   int nverts;
12509   ubyte* uniforms;
12510   int cuniforms;
12511   int nuniforms;
12512   NVGMatrix lastAffine;
12513 
12514   // cached state
12515   static if (NANOVG_GL_USE_STATE_FILTER) {
12516     GLuint boundTexture;
12517     GLuint stencilMask;
12518     GLenum stencilFunc;
12519     GLint stencilFuncRef;
12520     GLuint stencilFuncMask;
12521     GLNVGblend blendFunc;
12522   }
12523 }
12524 
12525 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); }
12526 
12527 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc {
12528   static if (NANOVG_GL_USE_STATE_FILTER) {
12529     if (gl.boundTexture != tex) {
12530       gl.boundTexture = tex;
12531       glBindTexture(GL_TEXTURE_2D, tex);
12532     }
12533   } else {
12534     glBindTexture(GL_TEXTURE_2D, tex);
12535   }
12536 }
12537 
12538 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc {
12539   static if (NANOVG_GL_USE_STATE_FILTER) {
12540     if (gl.stencilMask != mask) {
12541       gl.stencilMask = mask;
12542       glStencilMask(mask);
12543     }
12544   } else {
12545     glStencilMask(mask);
12546   }
12547 }
12548 
12549 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc {
12550   static if (NANOVG_GL_USE_STATE_FILTER) {
12551     if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) {
12552       gl.stencilFunc = func;
12553       gl.stencilFuncRef = ref_;
12554       gl.stencilFuncMask = mask;
12555       glStencilFunc(func, ref_, mask);
12556     }
12557   } else {
12558     glStencilFunc(func, ref_, mask);
12559   }
12560 }
12561 
12562 // texture id is never zero
12563 // sets refcount to one
12564 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12565   GLNVGtexture* tex = null;
12566 
12567   int tid = gl.freetexid;
12568   if (tid == -1) {
12569     if (gl.ntextures >= gl.ctextures) {
12570       assert(gl.ntextures == gl.ctextures);
12571       //pragma(msg, GLNVGtexture.sizeof*32);
12572       int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate
12573       GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures);
12574       if (textures is null) assert(0, "NanoVega: out of memory for textures");
12575       memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof);
12576       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }}
12577       gl.textures = textures;
12578       gl.ctextures = ctextures;
12579     }
12580     assert(gl.ntextures+1 <= gl.ctextures);
12581     tid = gl.ntextures++;
12582     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("  got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }}
12583   } else {
12584     gl.freetexid = gl.textures[tid].nextfree;
12585   }
12586   assert(tid <= gl.ntextures);
12587 
12588   assert(gl.textures[tid].id == 0);
12589   tex = &gl.textures[tid];
12590   memset(tex, 0, (*tex).sizeof);
12591   tex.id = tid+1;
12592   tex.rc = 1;
12593   tex.nextfree = -1;
12594 
12595   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }}
12596 
12597   return tex;
12598 }
12599 
12600 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc {
12601   if (id <= 0 || id > gl.ntextures) return null;
12602   if (gl.textures[id-1].id == 0) return null; // free one
12603   assert(gl.textures[id-1].id == id);
12604   return &gl.textures[id-1];
12605 }
12606 
12607 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc {
12608   if (id <= 0 || id > gl.ntextures) return false;
12609   auto tx = &gl.textures[id-1];
12610   if (tx.id == 0) { id = 0; return false; } // free one
12611   assert(tx.id == id);
12612   assert(tx.tex != 0);
12613   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }}
12614   import core.atomic : atomicOp;
12615   if (atomicOp!"-="(tx.rc, 1) == 0) {
12616     import core.thread : ThreadID;
12617     ThreadID mytid;
12618     static if (__VERSION__ < 2076) {
12619       DGNoThrowNoGC(() {
12620         import core.thread; mytid = Thread.getThis.id;
12621       })();
12622     } else {
12623       try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {}
12624     }
12625     if (gl.mainTID == mytid && gl.inFrame) {
12626       // can delete it right now
12627       if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex);
12628       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12629       memset(tx, 0, (*tx).sizeof);
12630       //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); }
12631       tx.nextfree = gl.freetexid;
12632       gl.freetexid = id-1;
12633     } else {
12634       // alas, we aren't doing frame business, so we should postpone deletion
12635       version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }}
12636       version(aliced) {
12637 	{
12638         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12639           tx.id = 0; // mark it as dead
12640           gl.mustCleanTextures = true; // set "need cleanup" flag
12641 	}
12642       } else {
12643         try {
12644             GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
12645             tx.id = 0; // mark it as dead
12646             gl.mustCleanTextures = true; // set "need cleanup" flag
12647         } catch (Exception e) {}
12648       }
12649     }
12650   }
12651   id = 0;
12652   return true;
12653 }
12654 
12655 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc {
12656   import core.stdc.stdio : fprintf, stderr;
12657   GLchar[512+1] str = 0;
12658   GLsizei len = 0;
12659   glGetShaderInfoLog(shader, 512, &len, str.ptr);
12660   if (len > 512) len = 512;
12661   str[len] = '\0';
12662   fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr);
12663 }
12664 
12665 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc {
12666   import core.stdc.stdio : fprintf, stderr;
12667   GLchar[512+1] str = 0;
12668   GLsizei len = 0;
12669   glGetProgramInfoLog(prog, 512, &len, str.ptr);
12670   if (len > 512) len = 512;
12671   str[len] = '\0';
12672   fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr);
12673 }
12674 
12675 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc {
12676   static if (!force) {
12677     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12678   }
12679   glGetError();
12680 }
12681 
12682 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc {
12683   GLenum err;
12684   static if (!force) {
12685     if ((gl.flags&NVGContextFlag.Debug) == 0) return;
12686   }
12687   err = glGetError();
12688   if (err != GL_NO_ERROR) {
12689     import core.stdc.stdio : fprintf, stderr;
12690     fprintf(stderr, "Error %08x after %s\n", err, str);
12691     return;
12692   }
12693 }
12694 
12695 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc {
12696   GLint status;
12697   GLuint prog, vert, frag;
12698   const(char)*[3] str;
12699 
12700   memset(shader, 0, (*shader).sizeof);
12701 
12702   prog = glCreateProgram();
12703   vert = glCreateShader(GL_VERTEX_SHADER);
12704   frag = glCreateShader(GL_FRAGMENT_SHADER);
12705   str[0] = header;
12706   str[1] = (opts !is null ? opts : "");
12707   str[2] = vshader;
12708   glShaderSource(vert, 3, cast(const(char)**)str.ptr, null);
12709 
12710   glCompileShader(vert);
12711   glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
12712   if (status != GL_TRUE) {
12713     glnvg__dumpShaderError(vert, name, "vert");
12714     return false;
12715   }
12716 
12717   str[0] = header;
12718   str[1] = (opts !is null ? opts : "");
12719   str[2] = fshader;
12720   glShaderSource(frag, 3, cast(const(char)**)str.ptr, null);
12721 
12722   glCompileShader(frag);
12723   glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
12724   if (status != GL_TRUE) {
12725     glnvg__dumpShaderError(frag, name, "frag");
12726     return false;
12727   }
12728 
12729   glAttachShader(prog, vert);
12730   glAttachShader(prog, frag);
12731 
12732   glBindAttribLocation(prog, 0, "vertex");
12733   glBindAttribLocation(prog, 1, "tcoord");
12734 
12735   glLinkProgram(prog);
12736   glGetProgramiv(prog, GL_LINK_STATUS, &status);
12737   if (status != GL_TRUE) {
12738     glnvg__dumpProgramError(prog, name);
12739     return false;
12740   }
12741 
12742   shader.prog = prog;
12743   shader.vert = vert;
12744   shader.frag = frag;
12745 
12746   return true;
12747 }
12748 
12749 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc {
12750   if (shader.prog != 0) glDeleteProgram(shader.prog);
12751   if (shader.vert != 0) glDeleteShader(shader.vert);
12752   if (shader.frag != 0) glDeleteShader(shader.frag);
12753 }
12754 
12755 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc {
12756   shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize");
12757   shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex");
12758   shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag");
12759   shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat");
12760   shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr");
12761   shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex");
12762 }
12763 
12764 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc {
12765   foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) {
12766     if (fbo != 0) {
12767       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
12768       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
12769       foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; }
12770       glDeleteFramebuffers(1, &fbo);
12771       fbo = 0;
12772     }
12773   }
12774   gl.fboWidth = gl.fboHeight = 0;
12775 }
12776 
12777 // returns `true` is new FBO was created
12778 // will not unbind buffer, if it was created
12779 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc {
12780   assert(fidx >= 0 && fidx < gl.fbo.length);
12781   assert(gl.fboWidth > 0);
12782   assert(gl.fboHeight > 0);
12783 
12784   if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized
12785 
12786   glnvg__resetError(gl);
12787 
12788   // allocate FBO object
12789   GLuint fbo = 0;
12790   glGenFramebuffers(1, &fbo);
12791   if (fbo == 0) assert(0, "NanoVega: cannot create FBO");
12792   glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers");
12793   glBindFramebuffer(GL_FRAMEBUFFER, fbo);
12794   //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0);
12795 
12796   // attach 2D texture to this FBO
12797   GLuint tidColor = 0;
12798   glGenTextures(1, &tidColor);
12799   if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO");
12800   glBindTexture(GL_TEXTURE_2D, tidColor);
12801   //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
12802   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
12803   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S");
12804   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
12805   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T");
12806   //FIXME: linear or nearest?
12807   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
12808   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12809   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER");
12810   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
12811   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
12812   glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER");
12813   // empty texture
12814   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12815   // create texture with only one color channel
12816   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null);
12817   //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
12818   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)");
12819   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0);
12820   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)");
12821 
12822   // attach stencil texture to this FBO
12823   GLuint tidStencil = 0;
12824   version(nanovega_shared_stencil) {
12825     if (gl.fboTex.ptr[0].ptr[0] == 0) {
12826       glGenTextures(1, &tidStencil);
12827       if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12828       gl.fboTex.ptr[0].ptr[0] = tidStencil;
12829     } else {
12830       tidStencil = gl.fboTex.ptr[0].ptr[0];
12831     }
12832     if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs
12833   } else {
12834     glGenTextures(1, &tidStencil);
12835     if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO");
12836     gl.fboTex.ptr[0].ptr[0] = tidStencil;
12837   }
12838   glBindTexture(GL_TEXTURE_2D, tidStencil);
12839   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null);
12840   glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)");
12841   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0);
12842   glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)");
12843 
12844   {
12845     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
12846     if (status != GL_FRAMEBUFFER_COMPLETE) {
12847       version(all) {
12848         import core.stdc.stdio;
12849         if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n");
12850         if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n");
12851         if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n");
12852         if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n");
12853       }
12854       assert(0, "NanoVega: framebuffer creation failed");
12855     }
12856   }
12857 
12858   // clear 'em all
12859   if (doclear) {
12860     glClearColor(0, 0, 0, 0);
12861     glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12862   }
12863 
12864   // save texture ids
12865   gl.fbo.ptr[fidx] = fbo;
12866   gl.fboTex.ptr[fidx].ptr[0] = tidColor;
12867   version(nanovega_shared_stencil) {} else {
12868     gl.fboTex.ptr[fidx].ptr[1] = tidStencil;
12869   }
12870 
12871   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12872 
12873   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); }
12874 
12875   return true;
12876 }
12877 
12878 // will not unbind buffer
12879 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc {
12880   assert(fidx >= 0 && fidx < gl.fbo.length);
12881   assert(gl.fboWidth > 0);
12882   assert(gl.fboHeight > 0);
12883   assert(gl.fbo.ptr[fidx] != 0);
12884   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]);
12885   glClearColor(0, 0, 0, 0);
12886   glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
12887   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); }
12888 }
12889 
12890 // will not unbind buffer
12891 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc {
12892   import core.stdc.string : memset;
12893   assert(didx >= 0 && didx < gl.fbo.length);
12894   assert(sidx >= 0 && sidx < gl.fbo.length);
12895   assert(gl.fboWidth > 0);
12896   assert(gl.fboHeight > 0);
12897   assert(gl.fbo.ptr[didx] != 0);
12898   assert(gl.fbo.ptr[sidx] != 0);
12899   if (didx == sidx) return;
12900 
12901   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); }
12902 
12903   glUseProgram(gl.shaderCopyFBO.prog);
12904 
12905   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]);
12906   glDisable(GL_CULL_FACE);
12907   glDisable(GL_BLEND);
12908   glDisable(GL_SCISSOR_TEST);
12909   glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]);
12910   // copy texture by drawing full quad
12911   enum x = 0;
12912   enum y = 0;
12913   immutable int w = gl.fboWidth;
12914   immutable int h = gl.fboHeight;
12915   immutable(NVGVertex[4]) vertices =
12916    [NVGVertex(x, y), // top-left
12917     NVGVertex(w, y), // top-right
12918     NVGVertex(w, h), // bottom-right
12919     NVGVertex(x, h)]; // bottom-left
12920 
12921   glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
12922   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
12923 
12924   // restore state (but don't unbind FBO)
12925   static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture);
12926   glEnable(GL_CULL_FACE);
12927   glEnable(GL_BLEND);
12928   glUseProgram(gl.shader.prog);
12929 }
12930 
12931 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc {
12932   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); }
12933   /*
12934   if (gl.lastClipFBO >= 0) {
12935     glActiveTexture(GL_TEXTURE1);
12936     glBindTexture(GL_TEXTURE_2D, 0);
12937     glActiveTexture(GL_TEXTURE0);
12938   }
12939   */
12940   gl.lastClipFBO = -666;
12941 }
12942 
12943 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc {
12944   //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12945   if (gl.lastClipFBO != -666) {
12946     // cached
12947     version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); }
12948     frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0);
12949     return;
12950   }
12951 
12952   // no cache
12953   int fboidx = -1;
12954   mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) {
12955     final switch (mst) {
12956       case GLMaskState.DontMask: fboidx = -1; break mainloop;
12957       case GLMaskState.Uninitialized: break;
12958       case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop;
12959       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error");
12960     }
12961   }
12962 
12963   if (fboidx < 0) {
12964     // don't mask
12965     gl.lastClipFBO = -1;
12966     frag.doclip = 0;
12967   } else {
12968     // do masking
12969     assert(gl.fbo.ptr[fboidx] != 0);
12970     gl.lastClipFBO = fboidx;
12971     frag.doclip = 1;
12972   }
12973 
12974   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); }
12975 
12976   if (gl.lastClipFBO >= 0) {
12977     assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12978     glActiveTexture(GL_TEXTURE1);
12979     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
12980     glActiveTexture(GL_TEXTURE0);
12981   }
12982 }
12983 
12984 // returns index in `gl.fbo`, or -1 for "don't mask"
12985 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc {
12986   assert(gl.msp > 0 && gl.msp <= gl.maskStack.length);
12987   // we need initialized FBO, even for "don't mask" case
12988   // for this, look back in stack, and either copy initialized FBO,
12989   // or stop at first uninitialized one, and clear it
12990   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) {
12991     // shortcut
12992     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); }
12993     glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]);
12994     return gl.msp-1;
12995   }
12996   foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) {
12997     final switch (gl.maskStack.ptr[sp]) {
12998       case GLMaskState.DontMask:
12999         // clear it
13000         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); }
13001         if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13002         gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13003         return gl.msp-1;
13004       case GLMaskState.Uninitialized: break; // do nothing
13005       case GLMaskState.Initialized:
13006         // i found her! copy to TOS
13007         version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); }
13008         glnvg__allocFBO(gl, gl.msp-1, false);
13009         glnvg__copyFBOToFrom(gl, gl.msp-1, sp);
13010         gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13011         return gl.msp-1;
13012       case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error");
13013     }
13014   }
13015   // nothing was initialized, lol
13016   version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); }
13017   if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1);
13018   gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared;
13019   return gl.msp-1;
13020 }
13021 
13022 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc {
13023   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13024   GLNVGcall* call = glnvg__allocCall(gl);
13025   if (call is null) return;
13026   call.type = GLNVG_PUSHCLIP;
13027 }
13028 
13029 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc {
13030   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13031   GLNVGcall* call = glnvg__allocCall(gl);
13032   if (call is null) return;
13033   call.type = GLNVG_POPCLIP;
13034 }
13035 
13036 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc {
13037   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13038   GLNVGcall* call = glnvg__allocCall(gl);
13039   if (call is null) return;
13040   call.type = GLNVG_RESETCLIP;
13041 }
13042 
13043 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc {
13044   version(nanovega_debug_clipping) {
13045     GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13046     GLNVGcall* call = glnvg__allocCall(gl);
13047     call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF);
13048   }
13049 }
13050 
13051 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
13052   import core.stdc.stdio : snprintf;
13053 
13054   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13055   enum align_ = 4;
13056 
13057   char[64] shaderHeader = void;
13058   //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n";
13059   snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE);
13060 
13061   enum fillVertShader = q{
13062     uniform vec2 viewSize;
13063     attribute vec2 vertex;
13064     attribute vec2 tcoord;
13065     varying vec2 ftcoord;
13066     varying vec2 fpos;
13067     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13068     uniform vec2 ttr; /* tx and ty of affine matrix */
13069     void main (void) {
13070       /* affine transformation */
13071       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13072       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13073       ftcoord = tcoord;
13074       fpos = vec2(nx, ny);
13075       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13076     }
13077   };
13078 
13079   enum fillFragShader = `
13080     uniform vec4 frag[UNIFORM_ARRAY_SIZE];
13081     uniform sampler2D tex;
13082     uniform sampler2D clipTex;
13083     uniform vec2 viewSize;
13084     varying vec2 ftcoord;
13085     varying vec2 fpos;
13086     #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
13087     #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
13088     #define innerCol frag[6]
13089     #define middleCol frag[7]
13090     #define outerCol frag[7+1]
13091     #define scissorExt frag[8+1].xy
13092     #define scissorScale frag[8+1].zw
13093     #define extent frag[9+1].xy
13094     #define radius frag[9+1].z
13095     #define feather frag[9+1].w
13096     #define strokeMult frag[10+1].x
13097     #define strokeThr frag[10+1].y
13098     #define texType int(frag[10+1].z)
13099     #define type int(frag[10+1].w)
13100     #define doclip int(frag[11+1].x)
13101     #define midp frag[11+1].y
13102 
13103     float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
13104       vec2 ext2 = ext-vec2(rad, rad);
13105       vec2 d = abs(pt)-ext2;
13106       return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad;
13107     }
13108 
13109     // Scissoring
13110     float scissorMask (in vec2 p) {
13111       vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt);
13112       sc = vec2(0.5, 0.5)-sc*scissorScale;
13113       return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0);
13114     }
13115 
13116     #ifdef EDGE_AA
13117     // Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
13118     float strokeMask () {
13119       return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y);
13120     }
13121     #endif
13122 
13123     void main (void) {
13124       // clipping
13125       if (doclip != 0) {
13126         /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/
13127         vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13128         if (clr.r == 0.0) discard;
13129       }
13130       float scissor = scissorMask(fpos);
13131       if (scissor <= 0.0) discard; //k8: is it really faster?
13132       #ifdef EDGE_AA
13133       float strokeAlpha = strokeMask();
13134       if (strokeAlpha < strokeThr) discard;
13135       #else
13136       float strokeAlpha = 1.0;
13137       #endif
13138       // rendering
13139       vec4 color;
13140       if (type == 0) { /* NSVG_SHADER_FILLCOLOR */
13141         color = innerCol;
13142         // Combine alpha
13143         color *= strokeAlpha*scissor;
13144       } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */
13145         // Gradient
13146         // Calculate gradient color using box gradient
13147         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
13148         float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
13149         if (midp <= 0.0) {
13150           color = mix(innerCol, outerCol, d);
13151         } else {
13152           float gdst = min(midp, 1.0);
13153           if (d < gdst) {
13154             color = mix(innerCol, middleCol, d/gdst);
13155           } else {
13156             color = mix(middleCol, outerCol, (d-gdst)/gdst);
13157           }
13158         }
13159         // Combine alpha
13160         color *= strokeAlpha*scissor;
13161       } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
13162         // Image
13163         // Calculate color from texture
13164         vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent;
13165         color = texture2D(tex, pt);
13166         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13167         if (texType == 2) color = vec4(color.x);
13168         // Apply color tint and alpha
13169         color *= innerCol;
13170         // Combine alpha
13171         color *= strokeAlpha*scissor;
13172       } else if (type == 3) { /* NSVG_SHADER_SIMPLE */
13173         // Stencil fill
13174         color = vec4(1, 1, 1, 1);
13175       } else if (type == 4) { /* NSVG_SHADER_IMG */
13176         // Textured tris
13177         color = texture2D(tex, ftcoord);
13178         if (texType == 1) color = vec4(color.xyz*color.w, color.w);
13179         if (texType == 2) color = vec4(color.x);
13180         color *= scissor;
13181         color *= innerCol; // Apply color tint
13182       }
13183       gl_FragColor = color;
13184     }
13185   `;
13186 
13187   enum clipVertShaderFill = q{
13188     uniform vec2 viewSize;
13189     attribute vec2 vertex;
13190     uniform vec4 tmat; /* abcd of affine matrix: xyzw */
13191     uniform vec2 ttr; /* tx and ty of affine matrix */
13192     void main (void) {
13193       /* affine transformation */
13194       float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x;
13195       float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y;
13196       gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1);
13197     }
13198   };
13199 
13200   enum clipFragShaderFill = q{
13201     uniform vec2 viewSize;
13202     void main (void) {
13203       gl_FragColor = vec4(1, 1, 1, 1);
13204     }
13205   };
13206 
13207   enum clipVertShaderCopy = q{
13208     uniform vec2 viewSize;
13209     attribute vec2 vertex;
13210     void main (void) {
13211       gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1);
13212     }
13213   };
13214 
13215   enum clipFragShaderCopy = q{
13216     uniform sampler2D tex;
13217     uniform vec2 viewSize;
13218     void main (void) {
13219       //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);
13220       gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y));
13221     }
13222   };
13223 
13224   glnvg__checkError(gl, "init");
13225 
13226   string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null);
13227   if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false;
13228   if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false;
13229   if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false;
13230 
13231   glnvg__checkError(gl, "uniform locations");
13232   glnvg__getUniforms(&gl.shader);
13233   glnvg__getUniforms(&gl.shaderFillFBO);
13234   glnvg__getUniforms(&gl.shaderCopyFBO);
13235 
13236   // Create dynamic vertex array
13237   glGenBuffers(1, &gl.vertBuf);
13238 
13239   gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_;
13240 
13241   glnvg__checkError(gl, "create done");
13242 
13243   glFinish();
13244 
13245   return true;
13246 }
13247 
13248 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc {
13249   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13250   GLNVGtexture* tex = glnvg__allocTexture(gl);
13251 
13252   if (tex is null) return 0;
13253 
13254   glGenTextures(1, &tex.tex);
13255   tex.width = w;
13256   tex.height = h;
13257   tex.type = type;
13258   tex.flags = imageFlags;
13259   glnvg__bindTexture(gl, tex.tex);
13260 
13261   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }}
13262 
13263   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13264   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13265   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13266   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13267 
13268 
13269 
13270   immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13271   glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data);
13272   // GL 3.0 and later have support for a dedicated function for generating mipmaps
13273   // it needs to be called after the glTexImage2D call
13274   if (imageFlags & NVGImageFlag.GenerateMipmaps)
13275     glGenerateMipmap(GL_TEXTURE_2D);
13276 
13277   immutable tfmin =
13278     (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR :
13279      imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST :
13280      GL_LINEAR);
13281   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0);
13282   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin);
13283   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR));
13284 
13285   int flag;
13286   if (imageFlags&NVGImageFlag.RepeatX)
13287     flag = GL_REPEAT;
13288   else if (imageFlags&NVGImageFlag.ClampToBorderX)
13289     flag = GL_CLAMP_TO_BORDER;
13290   else 
13291     flag = GL_CLAMP_TO_EDGE;
13292   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag);
13293 
13294 
13295   if (imageFlags&NVGImageFlag.RepeatY)
13296     flag = GL_REPEAT;
13297   else if (imageFlags&NVGImageFlag.ClampToBorderY)
13298     flag = GL_CLAMP_TO_BORDER;
13299   else 
13300     flag = GL_CLAMP_TO_EDGE;
13301   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag);
13302 
13303   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13304   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13305   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13306   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13307 
13308   glnvg__checkError(gl, "create tex");
13309   glnvg__bindTexture(gl, 0);
13310 
13311   return tex.id;
13312 }
13313 
13314 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc {
13315   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13316   return glnvg__deleteTexture(gl, image);
13317 }
13318 
13319 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc {
13320   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13321   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13322   if (tex is null) {
13323     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }}
13324     return false;
13325   }
13326   import core.atomic : atomicOp;
13327   atomicOp!"+="(tex.rc, 1);
13328   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }}
13329   return true;
13330 }
13331 
13332 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc {
13333   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13334   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13335 
13336   if (tex is null) {
13337     version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }}
13338     return false;
13339   }
13340 
13341   version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }}
13342 
13343   glnvg__bindTexture(gl, tex.tex);
13344 
13345   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
13346   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width);
13347   glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
13348   glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
13349 
13350   immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED);
13351   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data);
13352 
13353   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
13354   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13355   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
13356   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
13357 
13358   glnvg__bindTexture(gl, 0);
13359 
13360   return true;
13361 }
13362 
13363 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc {
13364   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13365   GLNVGtexture* tex = glnvg__findTexture(gl, image);
13366   if (tex is null) {
13367     if (w !is null) *w = 0;
13368     if (h !is null) *h = 0;
13369     return false;
13370   } else {
13371     if (w !is null) *w = tex.width;
13372     if (h !is null) *h = tex.height;
13373     return true;
13374   }
13375 }
13376 
13377 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc {
13378   assert(t.length >= 6);
13379   assert(m3.length >= 12);
13380   m3.ptr[0] = t.ptr[0];
13381   m3.ptr[1] = t.ptr[1];
13382   m3.ptr[2] = 0.0f;
13383   m3.ptr[3] = 0.0f;
13384   m3.ptr[4] = t.ptr[2];
13385   m3.ptr[5] = t.ptr[3];
13386   m3.ptr[6] = 0.0f;
13387   m3.ptr[7] = 0.0f;
13388   m3.ptr[8] = t.ptr[4];
13389   m3.ptr[9] = t.ptr[5];
13390   m3.ptr[10] = 1.0f;
13391   m3.ptr[11] = 0.0f;
13392 }
13393 
13394 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc {
13395   //pragma(inline, true);
13396   NVGColor res = void;
13397   res.r = c.r*c.a;
13398   res.g = c.g*c.a;
13399   res.b = c.b*c.a;
13400   res.a = c.a;
13401   return res;
13402 }
13403 
13404 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc {
13405   import core.stdc.math : sqrtf;
13406   GLNVGtexture* tex = null;
13407   NVGMatrix invxform = void;
13408 
13409   memset(frag, 0, (*frag).sizeof);
13410 
13411   frag.innerCol = glnvg__premulColor(paint.innerColor);
13412   frag.middleCol = glnvg__premulColor(paint.middleColor);
13413   frag.outerCol = glnvg__premulColor(paint.outerColor);
13414   frag.midp = paint.midp;
13415 
13416   if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
13417     memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);
13418     frag.scissorExt.ptr[0] = 1.0f;
13419     frag.scissorExt.ptr[1] = 1.0f;
13420     frag.scissorScale.ptr[0] = 1.0f;
13421     frag.scissorScale.ptr[1] = 1.0f;
13422   } else {
13423     //nvgTransformInverse(invxform[], scissor.xform[]);
13424     invxform = scissor.xform.inverted;
13425     glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]);
13426     frag.scissorExt.ptr[0] = scissor.extent.ptr[0];
13427     frag.scissorExt.ptr[1] = scissor.extent.ptr[1];
13428     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;
13429     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;
13430   }
13431 
13432   memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof);
13433   frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe;
13434   frag.strokeThr = strokeThr;
13435 
13436   if (paint.image.valid) {
13437     tex = glnvg__findTexture(gl, paint.image.id);
13438     if (tex is null) return false;
13439     if ((tex.flags&NVGImageFlag.FlipY) != 0) {
13440       /*
13441       NVGMatrix flipped;
13442       nvgTransformScale(flipped[], 1.0f, -1.0f);
13443       nvgTransformMultiply(flipped[], paint.xform[]);
13444       nvgTransformInverse(invxform[], flipped[]);
13445       */
13446       /*
13447       NVGMatrix m1 = void, m2 = void;
13448       nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f);
13449       nvgTransformMultiply(m1[], paint.xform[]);
13450       nvgTransformScale(m2[], 1.0f, -1.0f);
13451       nvgTransformMultiply(m2[], m1[]);
13452       nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f);
13453       nvgTransformMultiply(m1[], m2[]);
13454       nvgTransformInverse(invxform[], m1[]);
13455       */
13456       NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f);
13457       m1.mul(paint.xform);
13458       NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f);
13459       m2.mul(m1);
13460       m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f);
13461       m1.mul(m2);
13462       invxform = m1.inverted;
13463     } else {
13464       //nvgTransformInverse(invxform[], paint.xform[]);
13465       invxform = paint.xform.inverted;
13466     }
13467     frag.type = NSVG_SHADER_FILLIMG;
13468 
13469     if (tex.type == NVGtexture.RGBA) {
13470       frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1);
13471     } else {
13472       frag.texType = 2;
13473     }
13474     //printf("frag.texType = %d\n", frag.texType);
13475   } else {
13476     frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD);
13477     frag.radius = paint.radius;
13478     frag.feather = paint.feather;
13479     //nvgTransformInverse(invxform[], paint.xform[]);
13480     invxform = paint.xform.inverted;
13481   }
13482 
13483   glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]);
13484 
13485   return true;
13486 }
13487 
13488 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc {
13489   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13490   glnvg__setFBOClipTexture(gl, frag);
13491   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0]));
13492   glnvg__checkError(gl, "glnvg__setUniforms");
13493   if (image != 0) {
13494     GLNVGtexture* tex = glnvg__findTexture(gl, image);
13495     glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0));
13496     glnvg__checkError(gl, "tex paint tex");
13497   } else {
13498     glnvg__bindTexture(gl, 0);
13499   }
13500 }
13501 
13502 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc {
13503   assert(clipmode != NVGClipMode.None);
13504 
13505   // fill FBO, clear stencil buffer
13506   //TODO: optimize with bounds?
13507   version(all) {
13508     //glnvg__resetAffine(gl);
13509     //glUseProgram(gl.shaderFillFBO.prog);
13510     glDisable(GL_CULL_FACE);
13511     glDisable(GL_BLEND);
13512     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13513     glEnable(GL_STENCIL_TEST);
13514     if (gl.doClipUnion) {
13515       // for "and" we should clear everything that is NOT stencil-masked
13516       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13517       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13518     } else {
13519       glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff);
13520       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13521     }
13522 
13523     immutable(NVGVertex[4]) vertices =
13524      [NVGVertex(0, 0, 0, 0),
13525       NVGVertex(0, gl.fboHeight, 0, 0),
13526       NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0),
13527       NVGVertex(gl.fboWidth, 0, 0, 0)];
13528 
13529     glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices);
13530     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
13531 
13532     //glnvg__restoreAffine(gl);
13533   }
13534 
13535   glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO);
13536   glDisable(GL_COLOR_LOGIC_OP);
13537   //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above
13538   glEnable(GL_BLEND);
13539   glDisable(GL_STENCIL_TEST);
13540   glEnable(GL_CULL_FACE);
13541   glUseProgram(gl.shader.prog);
13542 
13543   // set current FBO as used one
13544   assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0);
13545   if (gl.lastClipFBO != gl.msp-1) {
13546     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); }
13547     gl.lastClipFBO = gl.msp-1;
13548     glActiveTexture(GL_TEXTURE1);
13549     glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]);
13550     glActiveTexture(GL_TEXTURE0);
13551   }
13552 }
13553 
13554 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc {
13555   assert(clipmode != NVGClipMode.None);
13556   GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
13557   // save uniform offset for `glnvg__finishClip()`
13558   gl.lastClipUniOfs = uniformOffset;
13559   // get FBO index, bind this FBO
13560   immutable int clipTexId = glnvg__generateFBOClipTexture(gl);
13561   assert(clipTexId >= 0);
13562   glUseProgram(gl.shaderFillFBO.prog);
13563   glnvg__checkError(gl, "use");
13564   glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]);
13565   // set logic op for clip
13566   gl.doClipUnion = false;
13567   if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) {
13568     // it is cleared to zero, we can just draw a path
13569     glDisable(GL_COLOR_LOGIC_OP);
13570     gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized;
13571   } else {
13572     glEnable(GL_COLOR_LOGIC_OP);
13573     final switch (clipmode) {
13574       case NVGClipMode.None: assert(0, "wtf?!");
13575       case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode
13576       case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR
13577       case NVGClipMode.Xor: glLogicOp(GL_XOR); break;
13578       case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break;
13579       case NVGClipMode.Replace: glLogicOp(GL_COPY); break;
13580     }
13581   }
13582   // set affine matrix
13583   glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr);
13584   glnvg__checkError(gl, "affine 0");
13585   glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4);
13586   glnvg__checkError(gl, "affine 1");
13587   // setup common OpenGL parameters
13588   glDisable(GL_BLEND);
13589   glDisable(GL_CULL_FACE);
13590   glEnable(GL_STENCIL_TEST);
13591   glnvg__stencilMask(gl, 0xff);
13592   glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13593   glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13594   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13595 }
13596 
13597 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc {
13598   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13599   gl.inFrame = true;
13600   gl.view.ptr[0] = cast(float)width;
13601   gl.view.ptr[1] = cast(float)height;
13602   // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary)
13603   if (width != gl.fboWidth || height != gl.fboHeight) {
13604     glnvg__killFBOs(gl);
13605     gl.fboWidth = width;
13606     gl.fboHeight = height;
13607   }
13608   gl.msp = 1;
13609   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13610   // texture cleanup
13611   import core.atomic : atomicLoad;
13612   if (atomicLoad(gl.mustCleanTextures)) {
13613     try {
13614       import core.thread : Thread;
13615       static if (__VERSION__ < 2076) {
13616         DGNoThrowNoGC(() {
13617           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13618         })();
13619       } else {
13620         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13621       }
13622       {
13623         GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow;
13624         gl.mustCleanTextures = false;
13625         foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
13626           // no need to use atomic ops here, as we're locked
13627           if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) {
13628             version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }}
13629             import core.stdc.string : memset;
13630             if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex);
13631             memset(&tex, 0, tex.sizeof);
13632             tex.nextfree = gl.freetexid;
13633             gl.freetexid = cast(int)tidx;
13634           }
13635         }
13636       }
13637     } catch (Exception e) {}
13638   }
13639 }
13640 
13641 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13642   GLNVGpath* paths = &gl.paths[call.pathOffset];
13643   int npaths = call.pathCount;
13644 
13645   if (call.clipmode == NVGClipMode.None) {
13646     // Draw shapes
13647     glEnable(GL_STENCIL_TEST);
13648     glnvg__stencilMask(gl, 0xffU);
13649     glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU);
13650 
13651     glnvg__setUniforms(gl, call.uniformOffset, 0);
13652     glnvg__checkError(gl, "fill simple");
13653 
13654     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13655     if (call.evenOdd) {
13656       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13657       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13658       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13659     } else {
13660       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13661       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13662     }
13663     glDisable(GL_CULL_FACE);
13664     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13665     glEnable(GL_CULL_FACE);
13666 
13667     // Draw anti-aliased pixels
13668     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13669     glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13670     glnvg__checkError(gl, "fill fill");
13671 
13672     if (gl.flags&NVGContextFlag.Antialias) {
13673       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU);
13674       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13675       // Draw fringes
13676       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13677     }
13678 
13679     // Draw fill
13680     glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU);
13681     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13682     if (call.evenOdd) {
13683       glDisable(GL_CULL_FACE);
13684       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13685       //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13686       glEnable(GL_CULL_FACE);
13687     } else {
13688       glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount);
13689     }
13690 
13691     glDisable(GL_STENCIL_TEST);
13692   } else {
13693     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO
13694     glnvg__checkError(gl, "fillclip simple");
13695     glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU);
13696     if (call.evenOdd) {
13697       //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT);
13698       //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT);
13699       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
13700     } else {
13701       glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
13702       glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
13703     }
13704     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13705     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13706   }
13707 }
13708 
13709 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13710   GLNVGpath* paths = &gl.paths[call.pathOffset];
13711   int npaths = call.pathCount;
13712 
13713   if (call.clipmode == NVGClipMode.None) {
13714     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13715     glnvg__checkError(gl, "convex fill");
13716     if (call.evenOdd) glDisable(GL_CULL_FACE);
13717     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13718     if (gl.flags&NVGContextFlag.Antialias) {
13719       // Draw fringes
13720       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13721     }
13722     if (call.evenOdd) glEnable(GL_CULL_FACE);
13723   } else {
13724     glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO
13725     glnvg__checkError(gl, "clip convex fill");
13726     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
13727     if (gl.flags&NVGContextFlag.Antialias) {
13728       // Draw fringes
13729       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13730     }
13731     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13732   }
13733 }
13734 
13735 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13736   GLNVGpath* paths = &gl.paths[call.pathOffset];
13737   int npaths = call.pathCount;
13738 
13739   if (call.clipmode == NVGClipMode.None) {
13740     if (gl.flags&NVGContextFlag.StencilStrokes) {
13741       glEnable(GL_STENCIL_TEST);
13742       glnvg__stencilMask(gl, 0xff);
13743 
13744       // Fill the stroke base without overlap
13745       glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
13746       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
13747       glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image);
13748       glnvg__checkError(gl, "stroke fill 0");
13749       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13750 
13751       // Draw anti-aliased pixels.
13752       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13753       glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
13754       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13755       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13756 
13757       // Clear stencil buffer.
13758       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
13759       glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
13760       glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
13761       glnvg__checkError(gl, "stroke fill 1");
13762       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13763       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13764 
13765       glDisable(GL_STENCIL_TEST);
13766 
13767       //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
13768     } else {
13769       glnvg__setUniforms(gl, call.uniformOffset, call.image);
13770       glnvg__checkError(gl, "stroke fill");
13771       // Draw Strokes
13772       foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13773     }
13774   } else {
13775     glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode);
13776     glnvg__checkError(gl, "stroke fill 0");
13777     foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
13778     glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state
13779   }
13780 }
13781 
13782 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13783   if (call.clipmode == NVGClipMode.None) {
13784     glnvg__setUniforms(gl, call.uniformOffset, call.image);
13785     glnvg__checkError(gl, "triangles fill");
13786     glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount);
13787   } else {
13788     //TODO(?): use texture as mask?
13789   }
13790 }
13791 
13792 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc {
13793   glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr);
13794   glnvg__checkError(gl, "affine");
13795   glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4);
13796   glnvg__checkError(gl, "affine");
13797   //glnvg__setUniforms(gl, call.uniformOffset, call.image);
13798 }
13799 
13800 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc {
13801   scope(exit) gl.inFrame = false;
13802   if (clearTextures && gl.inFrame) {
13803     try {
13804       import core.thread : Thread;
13805       static if (__VERSION__ < 2076) {
13806         DGNoThrowNoGC(() {
13807           if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13808         })();
13809       } else {
13810         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13811       }
13812     } catch (Exception e) {}
13813     foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image);
13814   }
13815   gl.nverts = 0;
13816   gl.npaths = 0;
13817   gl.ncalls = 0;
13818   gl.nuniforms = 0;
13819   gl.msp = 1;
13820   gl.maskStack.ptr[0] = GLMaskState.DontMask;
13821 }
13822 
13823 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc {
13824   glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true);
13825 }
13826 
13827 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc {
13828   if (factor == NVGBlendFactor.Zero) return GL_ZERO;
13829   if (factor == NVGBlendFactor.One) return GL_ONE;
13830   if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR;
13831   if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR;
13832   if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR;
13833   if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR;
13834   if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA;
13835   if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA;
13836   if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA;
13837   if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA;
13838   if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE;
13839   return GL_INVALID_ENUM;
13840 }
13841 
13842 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc {
13843   GLNVGblend res;
13844   res.simple = op.simple;
13845   res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
13846   res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
13847   res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
13848   res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
13849   if (res.simple) {
13850     if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13851       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13852     }
13853   } else {
13854     if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) {
13855       res.simple = true;
13856       res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM;
13857     }
13858   }
13859   return res;
13860 }
13861 
13862 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc {
13863   //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha));
13864   static if (NANOVG_GL_USE_STATE_FILTER) {
13865     if (gl.blendFunc.simple == op.simple) {
13866       if (op.simple) {
13867         if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13868       } else {
13869         if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return;
13870       }
13871     }
13872     gl.blendFunc = op;
13873   }
13874   if (op.simple) {
13875     if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13876       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13877     } else {
13878       glBlendFunc(op.srcAlpha, op.dstAlpha);
13879     }
13880   } else {
13881     if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) {
13882       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13883     } else {
13884       glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha);
13885     }
13886   }
13887 }
13888 
13889 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc {
13890   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13891   GLNVGcall* call;
13892   // if last operation was GLNVG_AFFINE, simply replace the matrix
13893   if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) {
13894     call = &gl.calls[gl.ncalls-1];
13895   } else {
13896     call = glnvg__allocCall(gl);
13897     if (call is null) return;
13898     call.type = GLNVG_AFFINE;
13899   }
13900   call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6];
13901 }
13902 
13903 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false;
13904 
13905 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc {
13906   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
13907   if (!gl.inFrame) assert(0, "NanoVega: internal driver error");
13908   try {
13909     import core.thread : Thread;
13910     static if (__VERSION__ < 2076) {
13911       DGNoThrowNoGC(() {
13912         if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13913       })();
13914     } else {
13915       if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread");
13916     }
13917   } catch (Exception e) {}
13918   scope(exit) gl.inFrame = false;
13919 
13920   glnvg__resetError!true(gl);
13921   {
13922     int vv = 0;
13923     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv);
13924     if (glGetError() || vv < 0) vv = 0;
13925     gl.mainFBO = cast(uint)vv;
13926   }
13927 
13928   enum ShaderType { None, Fill, Clip }
13929   auto lastShader = ShaderType.None;
13930   if (gl.ncalls > 0) {
13931     gl.msp = 1;
13932     gl.maskStack.ptr[0] = GLMaskState.DontMask;
13933 
13934     // Setup require GL state.
13935     glUseProgram(gl.shader.prog);
13936 
13937     glActiveTexture(GL_TEXTURE1);
13938     glBindTexture(GL_TEXTURE_2D, 0);
13939     glActiveTexture(GL_TEXTURE0);
13940     glnvg__resetFBOClipTextureCache(gl);
13941 
13942     //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
13943     static if (NANOVG_GL_USE_STATE_FILTER) {
13944       gl.blendFunc.simple = true;
13945       gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM;
13946     }
13947     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case
13948     glEnable(GL_CULL_FACE);
13949     glCullFace(GL_BACK);
13950     glFrontFace(GL_CCW);
13951     glEnable(GL_BLEND);
13952     glDisable(GL_DEPTH_TEST);
13953     glDisable(GL_SCISSOR_TEST);
13954     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
13955     glStencilMask(0xffffffff);
13956     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
13957     glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
13958     glActiveTexture(GL_TEXTURE0);
13959     glBindTexture(GL_TEXTURE_2D, 0);
13960     static if (NANOVG_GL_USE_STATE_FILTER) {
13961       gl.boundTexture = 0;
13962       gl.stencilMask = 0xffffffff;
13963       gl.stencilFunc = GL_ALWAYS;
13964       gl.stencilFuncRef = 0;
13965       gl.stencilFuncMask = 0xffffffff;
13966     }
13967     glnvg__checkError(gl, "OpenGL setup");
13968 
13969     // Upload vertex data
13970     glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf);
13971     // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy)
13972     glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW);
13973     glEnableVertexAttribArray(0);
13974     glEnableVertexAttribArray(1);
13975     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0);
13976     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof));
13977     glnvg__checkError(gl, "vertex data uploading");
13978 
13979     // Set view and texture just once per frame.
13980     glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0);
13981     if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) {
13982       //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); }
13983       glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1);
13984     }
13985     if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13986     glnvg__checkError(gl, "render shader setup");
13987 
13988     // Reset affine transformations.
13989     glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr);
13990     glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4);
13991     glnvg__checkError(gl, "affine setup");
13992 
13993     // set clip shaders params
13994     // fill
13995     glUseProgram(gl.shaderFillFBO.prog);
13996     glnvg__checkError(gl, "clip shaders setup (fill 0)");
13997     if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
13998     glnvg__checkError(gl, "clip shaders setup (fill 1)");
13999     // copy
14000     glUseProgram(gl.shaderCopyFBO.prog);
14001     glnvg__checkError(gl, "clip shaders setup (copy 0)");
14002     if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr);
14003     glnvg__checkError(gl, "clip shaders setup (copy 1)");
14004     //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0);
14005     glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0);
14006     glnvg__checkError(gl, "clip shaders setup (copy 2)");
14007     // restore render shader
14008     glUseProgram(gl.shader.prog);
14009 
14010     //{ 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]); }
14011 
14012     gl.lastAffine.identity;
14013 
14014     foreach (int i; 0..gl.ncalls) {
14015       GLNVGcall* call = &gl.calls[i];
14016       switch (call.type) {
14017         case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break;
14018         case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break;
14019         case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break;
14020         case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break;
14021         case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break;
14022         // clip region management
14023         case GLNVG_PUSHCLIP:
14024           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]); }
14025           if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend");
14026           if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) {
14027             gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask;
14028           } else {
14029             gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized;
14030           }
14031           // no need to reset FBO cache here, as nothing was changed
14032           break;
14033         case GLNVG_POPCLIP:
14034           if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend");
14035           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]); }
14036           --gl.msp;
14037           assert(gl.msp > 0);
14038           //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); }
14039           // check popped item
14040           final switch (gl.maskStack.ptr[gl.msp]) {
14041             case GLMaskState.DontMask:
14042               // if last FBO was "don't mask", reset cache if current is not "don't mask"
14043               if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14044                 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14045                 glnvg__resetFBOClipTextureCache(gl);
14046               }
14047               break;
14048             case GLMaskState.Uninitialized:
14049               // if last FBO texture was uninitialized, it means that nothing was changed,
14050               // so we can keep using cached FBO
14051               break;
14052             case GLMaskState.Initialized:
14053               // if last FBO was initialized, it means that something was definitely changed
14054               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14055               glnvg__resetFBOClipTextureCache(gl);
14056               break;
14057             case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error");
14058           }
14059           break;
14060         case GLNVG_RESETCLIP:
14061           // mark current mask as "don't mask"
14062           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]); }
14063           if (gl.msp > 0) {
14064             if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) {
14065               gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask;
14066               version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("  +++ need to reset FBO cache\n"); }
14067               glnvg__resetFBOClipTextureCache(gl);
14068             }
14069           }
14070           break;
14071         case GLNVG_CLIP_DDUMP_ON:
14072           version(nanovega_debug_clipping) nanovegaClipDebugDump = true;
14073           break;
14074         case GLNVG_CLIP_DDUMP_OFF:
14075           version(nanovega_debug_clipping) nanovegaClipDebugDump = false;
14076           break;
14077         case GLNVG_NONE: break;
14078         default:
14079           {
14080             import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type);
14081           }
14082           assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)");
14083       }
14084       // and free texture, why not
14085       glnvg__deleteTexture(gl, call.image);
14086     }
14087 
14088     glDisableVertexAttribArray(0);
14089     glDisableVertexAttribArray(1);
14090     glDisable(GL_CULL_FACE);
14091     glBindBuffer(GL_ARRAY_BUFFER, 0);
14092     glUseProgram(0);
14093     glnvg__bindTexture(gl, 0);
14094   }
14095 
14096   // this will do all necessary cleanup
14097   glnvg__renderCancelInternal(gl, false); // no need to clear textures
14098 }
14099 
14100 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc {
14101   int count = 0;
14102   foreach (int i; 0..npaths) {
14103     count += paths[i].nfill;
14104     count += paths[i].nstroke;
14105   }
14106   return count;
14107 }
14108 
14109 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc {
14110   GLNVGcall* ret = null;
14111   if (gl.ncalls+1 > gl.ccalls) {
14112     GLNVGcall* calls;
14113     int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate
14114     calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls);
14115     if (calls is null) return null;
14116     gl.calls = calls;
14117     gl.ccalls = ccalls;
14118   }
14119   ret = &gl.calls[gl.ncalls++];
14120   memset(ret, 0, GLNVGcall.sizeof);
14121   return ret;
14122 }
14123 
14124 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14125   int ret = 0;
14126   if (gl.npaths+n > gl.cpaths) {
14127     GLNVGpath* paths;
14128     int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate
14129     paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths);
14130     if (paths is null) return -1;
14131     gl.paths = paths;
14132     gl.cpaths = cpaths;
14133   }
14134   ret = gl.npaths;
14135   gl.npaths += n;
14136   return ret;
14137 }
14138 
14139 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14140   int ret = 0;
14141   if (gl.nverts+n > gl.cverts) {
14142     NVGVertex* verts;
14143     int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate
14144     verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts);
14145     if (verts is null) return -1;
14146     gl.verts = verts;
14147     gl.cverts = cverts;
14148   }
14149   ret = gl.nverts;
14150   gl.nverts += n;
14151   return ret;
14152 }
14153 
14154 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc {
14155   int ret = 0, structSize = gl.fragSize;
14156   if (gl.nuniforms+n > gl.cuniforms) {
14157     ubyte* uniforms;
14158     int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate
14159     uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms);
14160     if (uniforms is null) return -1;
14161     gl.uniforms = uniforms;
14162     gl.cuniforms = cuniforms;
14163   }
14164   ret = gl.nuniforms*structSize;
14165   gl.nuniforms += n;
14166   return ret;
14167 }
14168 
14169 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc {
14170   return cast(GLNVGfragUniforms*)&gl.uniforms[i];
14171 }
14172 
14173 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc {
14174   vtx.x = x;
14175   vtx.y = y;
14176   vtx.u = u;
14177   vtx.v = v;
14178 }
14179 
14180 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 {
14181   if (npaths < 1) return;
14182 
14183   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14184   GLNVGcall* call = glnvg__allocCall(gl);
14185   NVGVertex* quad;
14186   GLNVGfragUniforms* frag;
14187   int maxverts, offset;
14188 
14189   if (call is null) return;
14190 
14191   call.type = GLNVG_FILL;
14192   call.evenOdd = evenOdd;
14193   call.clipmode = clipmode;
14194   //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); }
14195   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14196   call.triangleCount = 4;
14197   call.pathOffset = glnvg__allocPaths(gl, npaths);
14198   if (call.pathOffset == -1) goto error;
14199   call.pathCount = npaths;
14200   call.image = paint.image.id;
14201   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14202 
14203   if (npaths == 1 && paths[0].convex) {
14204     call.type = GLNVG_CONVEXFILL;
14205     call.triangleCount = 0; // Bounding box fill quad not needed for convex fill
14206   }
14207 
14208   // Allocate vertices for all the paths.
14209   maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount;
14210   offset = glnvg__allocVerts(gl, maxverts);
14211   if (offset == -1) goto error;
14212 
14213   foreach (int i; 0..npaths) {
14214     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14215     const(NVGpath)* path = &paths[i];
14216     memset(copy, 0, GLNVGpath.sizeof);
14217     if (path.nfill > 0) {
14218       copy.fillOffset = offset;
14219       copy.fillCount = path.nfill;
14220       memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill);
14221       offset += path.nfill;
14222     }
14223     if (path.nstroke > 0) {
14224       copy.strokeOffset = offset;
14225       copy.strokeCount = path.nstroke;
14226       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14227       offset += path.nstroke;
14228     }
14229   }
14230 
14231   // Setup uniforms for draw calls
14232   if (call.type == GLNVG_FILL) {
14233     import core.stdc.string : memcpy;
14234     // Quad
14235     call.triangleOffset = offset;
14236     quad = &gl.verts[call.triangleOffset];
14237     glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
14238     glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
14239     glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
14240     glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
14241     // Get uniform
14242     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14243     if (call.uniformOffset == -1) goto error;
14244     // Simple shader for stencil
14245     frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14246     memset(frag, 0, (*frag).sizeof);
14247     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14248     memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof);
14249     frag.strokeThr = -1.0f;
14250     frag.type = NSVG_SHADER_SIMPLE;
14251     // Fill shader
14252     //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f);
14253   } else {
14254     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14255     if (call.uniformOffset == -1) goto error;
14256     // Fill shader
14257     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f);
14258   }
14259 
14260   return;
14261 
14262 error:
14263   // We get here if call alloc was ok, but something else is not.
14264   // Roll back the last call to prevent drawing it.
14265   if (gl.ncalls > 0) --gl.ncalls;
14266 }
14267 
14268 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 {
14269   if (npaths < 1) return;
14270 
14271   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14272   GLNVGcall* call = glnvg__allocCall(gl);
14273   int maxverts, offset;
14274 
14275   if (call is null) return;
14276 
14277   call.type = GLNVG_STROKE;
14278   call.clipmode = clipmode;
14279   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14280   call.pathOffset = glnvg__allocPaths(gl, npaths);
14281   if (call.pathOffset == -1) goto error;
14282   call.pathCount = npaths;
14283   call.image = paint.image.id;
14284   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14285 
14286   // Allocate vertices for all the paths.
14287   maxverts = glnvg__maxVertCount(paths, npaths);
14288   offset = glnvg__allocVerts(gl, maxverts);
14289   if (offset == -1) goto error;
14290 
14291   foreach (int i; 0..npaths) {
14292     GLNVGpath* copy = &gl.paths[call.pathOffset+i];
14293     const(NVGpath)* path = &paths[i];
14294     memset(copy, 0, GLNVGpath.sizeof);
14295     if (path.nstroke) {
14296       copy.strokeOffset = offset;
14297       copy.strokeCount = path.nstroke;
14298       memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke);
14299       offset += path.nstroke;
14300     }
14301   }
14302 
14303   if (gl.flags&NVGContextFlag.StencilStrokes) {
14304     // Fill shader
14305     call.uniformOffset = glnvg__allocFragUniforms(gl, 2);
14306     if (call.uniformOffset == -1) goto error;
14307     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14308     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f);
14309   } else {
14310     // Fill shader
14311     call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14312     if (call.uniformOffset == -1) goto error;
14313     glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
14314   }
14315 
14316   return;
14317 
14318 error:
14319   // We get here if call alloc was ok, but something else is not.
14320   // Roll back the last call to prevent drawing it.
14321   if (gl.ncalls > 0) --gl.ncalls;
14322 }
14323 
14324 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc {
14325   if (nverts < 1) return;
14326 
14327   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14328   GLNVGcall* call = glnvg__allocCall(gl);
14329   GLNVGfragUniforms* frag;
14330 
14331   if (call is null) return;
14332 
14333   call.type = GLNVG_TRIANGLES;
14334   call.clipmode = clipmode;
14335   call.blendFunc = glnvg__buildBlendFunc(compositeOperation);
14336   call.image = paint.image.id;
14337   if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image);
14338 
14339   // Allocate vertices for all the paths.
14340   call.triangleOffset = glnvg__allocVerts(gl, nverts);
14341   if (call.triangleOffset == -1) goto error;
14342   call.triangleCount = nverts;
14343 
14344   memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts);
14345 
14346   // Fill shader
14347   call.uniformOffset = glnvg__allocFragUniforms(gl, 1);
14348   if (call.uniformOffset == -1) goto error;
14349   frag = nvg__fragUniformPtr(gl, call.uniformOffset);
14350   glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
14351   frag.type = NSVG_SHADER_IMG;
14352 
14353   return;
14354 
14355 error:
14356   // We get here if call alloc was ok, but something else is not.
14357   // Roll back the last call to prevent drawing it.
14358   if (gl.ncalls > 0) --gl.ncalls;
14359 }
14360 
14361 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc {
14362   GLNVGcontext* gl = cast(GLNVGcontext*)uptr;
14363   if (gl is null) return;
14364 
14365   glnvg__killFBOs(gl);
14366   glnvg__deleteShader(&gl.shader);
14367   glnvg__deleteShader(&gl.shaderFillFBO);
14368   glnvg__deleteShader(&gl.shaderCopyFBO);
14369 
14370   if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf);
14371 
14372   foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) {
14373     if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) {
14374       assert(tex.tex != 0);
14375       glDeleteTextures(1, &tex.tex);
14376     }
14377   }
14378   free(gl.textures);
14379 
14380   free(gl.paths);
14381   free(gl.verts);
14382   free(gl.uniforms);
14383   free(gl.calls);
14384 
14385   free(gl);
14386 }
14387 
14388 
14389 /** Creates NanoVega contexts for OpenGL2+.
14390  *
14391  * Specify creation flags as additional arguments, like this:
14392  * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);`
14393  *
14394  * If you won't specify any flags, defaults will be used:
14395  * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`.
14396  *
14397  * Group: context_management
14398  */
14399 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc {
14400   version(aliced) {
14401     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA;
14402   } else {
14403     enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes;
14404   }
14405   uint flags = 0;
14406   if (flagList.length != 0) {
14407     foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags);
14408   } else {
14409     flags = DefaultFlags;
14410   }
14411   NVGparams params = void;
14412   NVGContext ctx = null;
14413   version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?
14414   version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL();
14415   GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof);
14416   if (gl is null) goto error;
14417   memset(gl, 0, GLNVGcontext.sizeof);
14418 
14419   memset(&params, 0, params.sizeof);
14420   params.renderCreate = &glnvg__renderCreate;
14421   params.renderCreateTexture = &glnvg__renderCreateTexture;
14422   params.renderTextureIncRef = &glnvg__renderTextureIncRef;
14423   params.renderDeleteTexture = &glnvg__renderDeleteTexture;
14424   params.renderUpdateTexture = &glnvg__renderUpdateTexture;
14425   params.renderGetTextureSize = &glnvg__renderGetTextureSize;
14426   params.renderViewport = &glnvg__renderViewport;
14427   params.renderCancel = &glnvg__renderCancel;
14428   params.renderFlush = &glnvg__renderFlush;
14429   params.renderPushClip = &glnvg__renderPushClip;
14430   params.renderPopClip = &glnvg__renderPopClip;
14431   params.renderResetClip = &glnvg__renderResetClip;
14432   params.renderFill = &glnvg__renderFill;
14433   params.renderStroke = &glnvg__renderStroke;
14434   params.renderTriangles = &glnvg__renderTriangles;
14435   params.renderSetAffine = &glnvg__renderSetAffine;
14436   params.renderDelete = &glnvg__renderDelete;
14437   params.userPtr = gl;
14438   params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false);
14439   if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) {
14440     params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA);
14441   } else {
14442     params.fontAA = NVG_INVERT_FONT_AA;
14443   }
14444 
14445   gl.flags = flags;
14446   gl.freetexid = -1;
14447 
14448   ctx = createInternal(&params);
14449   if (ctx is null) goto error;
14450 
14451   static if (__VERSION__ < 2076) {
14452     DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })();
14453   } else {
14454     try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {}
14455   }
14456 
14457   return ctx;
14458 
14459 error:
14460   // 'gl' is freed by nvgDeleteInternal.
14461   if (ctx !is null) ctx.deleteInternal();
14462   return null;
14463 }
14464 
14465 /// Using  OpenGL texture id creates GLNVGtexture and return its id.
14466 /// Group: images
14467 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14468   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14469   GLNVGtexture* tex = glnvg__allocTexture(gl);
14470 
14471   if (tex is null) return 0;
14472 
14473   tex.type = NVGtexture.RGBA;
14474   tex.tex = textureId;
14475   tex.flags = imageFlags;
14476   tex.width = w;
14477   tex.height = h;
14478 
14479   return tex.id;
14480 }
14481 
14482 /// Create NVGImage from OpenGL texture id.
14483 /// Group: images
14484 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc {
14485   auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags);
14486 
14487   NVGImage res;
14488   if (id > 0) {
14489     res.id = id;
14490     version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); }
14491     res.ctx = ctx;
14492     ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount
14493   }
14494   return res;
14495 }
14496 
14497 /// Returns OpenGL texture id for NanoVega image.
14498 /// Group: images
14499 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc {
14500   GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr;
14501   GLNVGtexture* tex = glnvg__findTexture(gl, image);
14502   return tex.tex;
14503 }
14504 
14505 
14506 // ////////////////////////////////////////////////////////////////////////// //
14507 private:
14508 
14509 static if (NanoVegaHasFontConfig) {
14510   version(nanovg_builtin_fontconfig_bindings) {
14511     pragma(lib, "fontconfig");
14512 
14513     private extern(C) nothrow @trusted @nogc {
14514       enum FC_FILE = "file"; /* String */
14515       alias FcBool = int;
14516       alias FcChar8 = char;
14517       struct FcConfig;
14518       struct FcPattern;
14519       alias FcMatchKind = int;
14520       enum : FcMatchKind {
14521         FcMatchPattern,
14522         FcMatchFont,
14523         FcMatchScan
14524       }
14525       alias FcResult = int;
14526       enum : FcResult {
14527         FcResultMatch,
14528         FcResultNoMatch,
14529         FcResultTypeMismatch,
14530         FcResultNoId,
14531         FcResultOutOfMemory
14532       }
14533       FcBool FcInit ();
14534       FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind);
14535       void FcDefaultSubstitute (FcPattern* pattern);
14536       FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind);
14537       FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result);
14538       FcPattern* FcNameParse (const(FcChar8)* name);
14539       void FcPatternDestroy (FcPattern* p);
14540       FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s);
14541     }
14542   }
14543 
14544   __gshared bool fontconfigAvailable = false;
14545   // initialize fontconfig
14546   shared static this () {
14547     if (FcInit()) {
14548       fontconfigAvailable = true;
14549     } else {
14550       import core.stdc.stdio : stderr, fprintf;
14551       stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n");
14552     }
14553   }
14554 }
14555 
14556 
14557 // ////////////////////////////////////////////////////////////////////////// //
14558 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511])
14559 
14560 private static immutable ubyte[7641] baphometPath = [
14561   0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,
14562   0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff,
14563   0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00,
14564   0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00,
14565   0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00,
14566   0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14567   0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87,
14568   0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35,
14569   0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,
14570   0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87,
14571   0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd,
14572   0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43,
14573   0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7,
14574   0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42,
14575   0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56,
14576   0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43,
14577   0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29,
14578   0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75,
14579   0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89,
14580   0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec,
14581   0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50,
14582   0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e,
14583   0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43,
14584   0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb,
14585   0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e,
14586   0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92,
14587   0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9,
14588   0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64,
14589   0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91,
14590   0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43,
14591   0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0,
14592   0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82,
14593   0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea,
14594   0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46,
14595   0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43,
14596   0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88,
14597   0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4,
14598   0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90,
14599   0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf,
14600   0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43,
14601   0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64,
14602   0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2,
14603   0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f,
14604   0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb,
14605   0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43,
14606   0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea,
14607   0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e,
14608   0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43,
14609   0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85,
14610   0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5,
14611   0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43,
14612   0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94,
14613   0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32,
14614   0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c,
14615   0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62,
14616   0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad,
14617   0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43,
14618   0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33,
14619   0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65,
14620   0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2,
14621   0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71,
14622   0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43,
14623   0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36,
14624   0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d,
14625   0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a,
14626   0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5,
14627   0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43,
14628   0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51,
14629   0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21,
14630   0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2,
14631   0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e,
14632   0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43,
14633   0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68,
14634   0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39,
14635   0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba,
14636   0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce,
14637   0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43,
14638   0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a,
14639   0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c,
14640   0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73,
14641   0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16,
14642   0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43,
14643   0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85,
14644   0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39,
14645   0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90,
14646   0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf,
14647   0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43,
14648   0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f,
14649   0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22,
14650   0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7,
14651   0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf,
14652   0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43,
14653   0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d,
14654   0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c,
14655   0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd,
14656   0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86,
14657   0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42,
14658   0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9,
14659   0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee,
14660   0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33,
14661   0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9,
14662   0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43,
14663   0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97,
14664   0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81,
14665   0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0,
14666   0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80,
14667   0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e,
14668   0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7,
14669   0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43,
14670   0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e,
14671   0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43,
14672   0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08,
14673   0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02,
14674   0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31,
14675   0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83,
14676   0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b,
14677   0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48,
14678   0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43,
14679   0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d,
14680   0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51,
14681   0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08,
14682   0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc,
14683   0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a,
14684   0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f,
14685   0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43,
14686   0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e,
14687   0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84,
14688   0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33,
14689   0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48,
14690   0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83,
14691   0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f,
14692   0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43,
14693   0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02,
14694   0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6,
14695   0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c,
14696   0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73,
14697   0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57,
14698   0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e,
14699   0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20,
14700   0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1,
14701   0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87,
14702   0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a,
14703   0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e,
14704   0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43,
14705   0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38,
14706   0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e,
14707   0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82,
14708   0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43,
14709   0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64,
14710   0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff,
14711   0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee,
14712   0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82,
14713   0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79,
14714   0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85,
14715   0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31,
14716   0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43,
14717   0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88,
14718   0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86,
14719   0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02,
14720   0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce,
14721   0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43,
14722   0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43,
14723   0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83,
14724   0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00,
14725   0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec,
14726   0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c,
14727   0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39,
14728   0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2,
14729   0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43,
14730   0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43,
14731   0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72,
14732   0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38,
14733   0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31,
14734   0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66,
14735   0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43,
14736   0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19,
14737   0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73,
14738   0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd,
14739   0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a,
14740   0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74,
14741   0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09,
14742   0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d,
14743   0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43,
14744   0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96,
14745   0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08,
14746   0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43,
14747   0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38,
14748   0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09,
14749   0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83,
14750   0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43,
14751   0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93,
14752   0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08,
14753   0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43,
14754   0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31,
14755   0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09,
14756   0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93,
14757   0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43,
14758   0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f,
14759   0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08,
14760   0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43,
14761   0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4,
14762   0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09,
14763   0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2,
14764   0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43,
14765   0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94,
14766   0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08,
14767   0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43,
14768   0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77,
14769   0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09,
14770   0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30,
14771   0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43,
14772   0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92,
14773   0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08,
14774   0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43,
14775   0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31,
14776   0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09,
14777   0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67,
14778   0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43,
14779   0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90,
14780   0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08,
14781   0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43,
14782   0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31,
14783   0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93,
14784   0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31,
14785   0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14,
14786   0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43,
14787   0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d,
14788   0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e,
14789   0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c,
14790   0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42,
14791   0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2,
14792   0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22,
14793   0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57,
14794   0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43,
14795   0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d,
14796   0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42,
14797   0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74,
14798   0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91,
14799   0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43,
14800   0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48,
14801   0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2,
14802   0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a,
14803   0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3,
14804   0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21,
14805   0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43,
14806   0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c,
14807   0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95,
14808   0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72,
14809   0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3,
14810   0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,
14811   0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d,
14812   0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b,
14813   0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73,
14814   0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41,
14815   0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,
14816   0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08,
14817   0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42,
14818   0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7,
14819   0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06,
14820   0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80,
14821   0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9,
14822   0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53,
14823   0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f,
14824   0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed,
14825   0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4,
14826   0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90,
14827   0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35,
14828   0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43,
14829   0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec,
14830   0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43,
14831   0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f,
14832   0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43,
14833   0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85,
14834   0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,
14835   0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,
14836   0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f,
14837   0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91,
14838   0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08,
14839   0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43,
14840   0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1,
14841   0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef,
14842   0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee,
14843   0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08,
14844   0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43,
14845   0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1,
14846   0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e,
14847   0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e,
14848   0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43,
14849   0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66,
14850   0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29,
14851   0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd,
14852   0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf,
14853   0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56,
14854   0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c,
14855   0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4,
14856   0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4,
14857   0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41,
14858   0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43,
14859   0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42,
14860   0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1,
14861   0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72,
14862   0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30,
14863   0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43,
14864   0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4,
14865   0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,
14866   0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43,
14867   0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1,
14868   0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08,
14869   0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58,
14870   0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42,
14871   0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd,
14872   0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7,
14873   0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4,
14874   0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43,
14875   0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90,
14876   0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31,
14877   0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3,
14878   0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93,
14879   0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1,
14880   0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10,
14881   0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43,
14882   0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee,
14883   0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf,
14884   0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48,
14885   0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43,
14886   0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43,
14887   0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac,
14888   0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f,
14889   0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08,
14890   0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43,
14891   0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97,
14892   0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca,
14893   0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61,
14894   0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff,
14895   0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f,
14896   0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61,
14897   0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94,
14898   0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc,
14899   0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69,
14900   0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43,
14901   0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43,
14902   0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,
14903   0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14,
14904   0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3,
14905   0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43,
14906   0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5,
14907   0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42,
14908   0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42,
14909   0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a,
14910   0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47,
14911   0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08,
14912   0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43,
14913   0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f,
14914   0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed,
14915   0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea,
14916   0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42,
14917   0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08,
14918   0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed,
14919   0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b,
14920   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55,
14921   0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed,
14922   0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b,
14923   0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4,
14924   0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95,
14925   0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d,
14926   0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e,
14927   0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42,
14928   0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8,
14929   0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce,
14930   0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40,
14931   0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c,
14932   0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42,
14933   0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd,
14934   0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,
14935   0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4,
14936   0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42,
14937   0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42,
14938   0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8,
14939   0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5,
14940   0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e,
14941   0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42,
14942   0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,
14943   0x09,];
14944 
14945 private struct ThePath {
14946 public:
14947   enum Command {
14948     Bounds, // always first, has 4 args (x0, y0, x1, y1)
14949     StrokeMode,
14950     FillMode,
14951     StrokeFillMode,
14952     NormalStroke,
14953     ThinStroke,
14954     MoveTo,
14955     LineTo,
14956     CubicTo, // cubic bezier
14957     EndPath,
14958   }
14959 
14960 public:
14961   const(ubyte)[] path;
14962   uint ppos;
14963 
14964 public:
14965   this (const(void)[] apath) pure nothrow @trusted @nogc {
14966     path = cast(const(ubyte)[])apath;
14967   }
14968 
14969   @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); }
14970 
14971   Command getCommand () nothrow @trusted @nogc {
14972     pragma(inline, true);
14973     if (ppos >= cast(uint)path.length) assert(0, "invalid path");
14974     return cast(Command)(path.ptr[ppos++]);
14975   }
14976 
14977   // number of (x,y) pairs for this command
14978   static int argCount (in Command cmd) nothrow @safe @nogc {
14979     version(aliced) pragma(inline, true);
14980          if (cmd == Command.Bounds) return 2;
14981     else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1;
14982     else if (cmd == Command.CubicTo) return 3;
14983     else return 0;
14984   }
14985 
14986   void skipArgs (int argc) nothrow @trusted @nogc {
14987     pragma(inline, true);
14988     ppos += cast(uint)(float.sizeof*2*argc);
14989   }
14990 
14991   float getFloat () nothrow @trusted @nogc {
14992     pragma(inline, true);
14993     if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path");
14994     version(LittleEndian) {
14995       float res = *cast(const(float)*)(&path.ptr[ppos]);
14996       ppos += cast(uint)float.sizeof;
14997       return res;
14998     } else {
14999       static assert(float.sizeof == 4);
15000       uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24);
15001       ppos += cast(uint)float.sizeof;
15002       return *cast(const(float)*)(&xp);
15003     }
15004   }
15005 }
15006 
15007 // this will add baphomet's background path to the current NanoVega path, so you can fill it.
15008 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15009   if (nvg is null) return;
15010 
15011   auto path = ThePath(baphometPath);
15012 
15013   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15014   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15015 
15016   bool inPath = false;
15017   while (!path.empty) {
15018     auto cmd = path.getCommand();
15019     switch (cmd) {
15020       case ThePath.Command.MoveTo:
15021         inPath = true;
15022         immutable float ex = getScaledX();
15023         immutable float ey = getScaledY();
15024         nvg.moveTo(ex, ey);
15025         break;
15026       case ThePath.Command.LineTo:
15027         inPath = true;
15028         immutable float ex = getScaledX();
15029         immutable float ey = getScaledY();
15030         nvg.lineTo(ex, ey);
15031         break;
15032       case ThePath.Command.CubicTo: // cubic bezier
15033         inPath = true;
15034         immutable float x1 = getScaledX();
15035         immutable float y1 = getScaledY();
15036         immutable float x2 = getScaledX();
15037         immutable float y2 = getScaledY();
15038         immutable float ex = getScaledX();
15039         immutable float ey = getScaledY();
15040         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15041         break;
15042       case ThePath.Command.EndPath:
15043         if (inPath) return;
15044         break;
15045       default:
15046         path.skipArgs(path.argCount(cmd));
15047         break;
15048     }
15049   }
15050 }
15051 
15052 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it.
15053 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 {
15054   // pupils starts with "fill-and-stroke" mode
15055   if (nvg is null) return;
15056 
15057   auto path = ThePath(baphometPath);
15058 
15059   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15060   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15061 
15062   bool inPath = false;
15063   bool pupLeft = true;
15064   while (!path.empty) {
15065     auto cmd = path.getCommand();
15066     switch (cmd) {
15067       case ThePath.Command.StrokeFillMode: inPath = true; break;
15068       case ThePath.Command.MoveTo:
15069         if (!inPath) goto default;
15070         static if (!left) { if (pupLeft) goto default; }
15071         static if (!right) { if (!pupLeft) goto default; }
15072         immutable float ex = getScaledX();
15073         immutable float ey = getScaledY();
15074         nvg.moveTo(ex, ey);
15075         break;
15076       case ThePath.Command.LineTo:
15077         if (!inPath) goto default;
15078         static if (!left) { if (pupLeft) goto default; }
15079         static if (!right) { if (!pupLeft) goto default; }
15080         immutable float ex = getScaledX();
15081         immutable float ey = getScaledY();
15082         nvg.lineTo(ex, ey);
15083         break;
15084       case ThePath.Command.CubicTo: // cubic bezier
15085         if (!inPath) goto default;
15086         static if (!left) { if (pupLeft) goto default; }
15087         static if (!right) { if (!pupLeft) goto default; }
15088         immutable float x1 = getScaledX();
15089         immutable float y1 = getScaledY();
15090         immutable float x2 = getScaledX();
15091         immutable float y2 = getScaledY();
15092         immutable float ex = getScaledX();
15093         immutable float ey = getScaledY();
15094         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15095         break;
15096       case ThePath.Command.EndPath:
15097         if (inPath) {
15098           if (pupLeft) pupLeft = false; else return;
15099         }
15100         break;
15101       default:
15102         path.skipArgs(path.argCount(cmd));
15103         break;
15104     }
15105   }
15106 }
15107 
15108 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes
15109 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc {
15110   template hasChar(char ch, string s) {
15111          static if (s.length == 0) enum hasChar = false;
15112     else static if (s[0] == ch) enum hasChar = true;
15113     else enum hasChar = hasChar!(ch, s[1..$]);
15114   }
15115   enum AllowStroke = hasChar!('s', mode);
15116   enum AllowFill = hasChar!('f', mode);
15117   enum AllowWidth = hasChar!('w', mode);
15118   enum Contour = hasChar!('c', mode);
15119   //static assert(AllowWidth || AllowFill);
15120 
15121   if (nvg is null) return;
15122 
15123   auto path = ThePath(baphometPath);
15124 
15125   float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); }
15126   float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); }
15127 
15128   int mode = 0;
15129   int sw = ThePath.Command.NormalStroke;
15130   nvg.beginPath();
15131   while (!path.empty) {
15132     auto cmd = path.getCommand();
15133     switch (cmd) {
15134       case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break;
15135       case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break;
15136       case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break;
15137       case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break;
15138       case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break;
15139       case ThePath.Command.MoveTo:
15140         immutable float ex = getScaledX();
15141         immutable float ey = getScaledY();
15142         nvg.moveTo(ex, ey);
15143         break;
15144       case ThePath.Command.LineTo:
15145         immutable float ex = getScaledX();
15146         immutable float ey = getScaledY();
15147         nvg.lineTo(ex, ey);
15148         break;
15149       case ThePath.Command.CubicTo: // cubic bezier
15150         immutable float x1 = getScaledX();
15151         immutable float y1 = getScaledY();
15152         immutable float x2 = getScaledX();
15153         immutable float y2 = getScaledY();
15154         immutable float ex = getScaledX();
15155         immutable float ey = getScaledY();
15156         nvg.bezierTo(x1, y1, x2, y2, ex, ey);
15157         break;
15158       case ThePath.Command.EndPath:
15159         if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) {
15160           static if (AllowFill || Contour) {
15161             static if (Contour) {
15162               if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); }
15163             } else {
15164               nvg.fill();
15165             }
15166           }
15167         }
15168         if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) {
15169           static if (AllowStroke || Contour) {
15170             static if (AllowWidth) {
15171                    if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1;
15172               else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5;
15173               else assert(0, "wtf?!");
15174             }
15175             nvg.stroke();
15176           }
15177         }
15178         nvg.newPath();
15179         break;
15180       default:
15181         path.skipArgs(path.argCount(cmd));
15182         break;
15183     }
15184   }
15185   nvg.newPath();
15186 }